WebSocket and UI improvements

- Fix WebSocket upgrade handshake in web interface
- Fix terminal session linking with WebSocket connections
- Add binary type setting for WebSocket messages
- Add terminal resize after session establishment
- Change 'WebSocket SSH Daemon' to 'WSSSHD control panel' in all headers
- Add GNOME-like window decorations to terminal interface
- Implement window controls: close (disconnect), maximize (fullscreen), minimize (exit fullscreen)
- Maintain window decorations in fullscreen mode
parent c9755708
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}WebSocket SSH Daemon{% endblock %}</title> <title>{% block title %}WSSSHD control panel{% endblock %}</title>
<link rel="icon" href="/favicon.ico" type="image/x-icon"> <link rel="icon" href="/favicon.ico" type="image/x-icon">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"> <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
...@@ -68,7 +68,7 @@ ...@@ -68,7 +68,7 @@
<nav class="navbar navbar-expand-lg navbar-dark bg-primary"> <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container"> <div class="container">
<a class="navbar-brand" href="{{ url_for('index') }}"> <a class="navbar-brand" href="{{ url_for('index') }}">
<i class="fas fa-terminal"></i> WebSocket SSH Daemon <i class="fas fa-terminal"></i> WSSSHD control panel
</a> </a>
<div class="navbar-nav ms-auto"> <div class="navbar-nav ms-auto">
{% if current_user.is_authenticated %} {% if current_user.is_authenticated %}
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard - WebSocket SSH Daemon</title> <title>Dashboard - WSSSHD control panel</title>
<link rel="icon" href="/favicon.ico" type="image/x-icon"> <link rel="icon" href="/favicon.ico" type="image/x-icon">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"> <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
<nav class="navbar navbar-expand-lg navbar-dark bg-primary"> <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container"> <div class="container">
<a class="navbar-brand" href="/"> <a class="navbar-brand" href="/">
<i class="fas fa-terminal"></i> WebSocket SSH Daemon</a> <i class="fas fa-terminal"></i> WSSSHD control panel</a>
<div class="navbar-nav ms-auto"> <div class="navbar-nav ms-auto">
<span class="navbar-text me-3">%s</span> <span class="navbar-text me-3">%s</span>
<a class="nav-link" href="/logout">Logout</a> <a class="nav-link" href="/logout">Logout</a>
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - WebSocket SSH Daemon</title> <title>Login - WSSSHD control panel</title>
<link rel="icon" href="/favicon.ico" type="image/x-icon"> <link rel="icon" href="/favicon.ico" type="image/x-icon">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"> <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
<nav class="navbar navbar-expand-lg navbar-dark bg-primary"> <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container"> <div class="container">
<a class="navbar-brand" href="/"> <a class="navbar-brand" href="/">
<i class="fas fa-terminal"></i> WebSocket SSH Daemon</a> <i class="fas fa-terminal"></i> WSSSHD control panel</a>
</div></nav> </div></nav>
<div class="container mt-4"> <div class="container mt-4">
<div class="row justify-content-center"> <div class="row justify-content-center">
......
This diff is collapsed.
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Users - WebSocket SSH Daemon</title> <title>Users - WSSSHD control panel</title>
<link rel="icon" href="/favicon.ico" type="image/x-icon"> <link rel="icon" href="/favicon.ico" type="image/x-icon">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"> <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
<nav class="navbar navbar-expand-lg navbar-dark bg-primary"> <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container"> <div class="container">
<a class="navbar-brand" href="/"> <a class="navbar-brand" href="/">
<i class="fas fa-terminal"></i> WebSocket SSH Daemon</a> <i class="fas fa-terminal"></i> WSSSHD control panel</a>
<div class="navbar-nav ms-auto"> <div class="navbar-nav ms-auto">
<span class="navbar-text me-3">%s</span> <span class="navbar-text me-3">%s</span>
<a class="nav-link" href="/logout">Logout</a> <a class="nav-link" href="/logout">Logout</a>
......
...@@ -231,6 +231,7 @@ char *terminal_get_output(terminal_session_t *session, size_t *len) { ...@@ -231,6 +231,7 @@ char *terminal_get_output(terminal_session_t *session, size_t *len) {
pthread_mutex_lock(&session->output_mutex); pthread_mutex_lock(&session->output_mutex);
if (session->output_used == 0) { if (session->output_used == 0) {
// For WebSocket connections, don't wait - return immediately
pthread_mutex_unlock(&session->output_mutex); pthread_mutex_unlock(&session->output_mutex);
*len = 0; *len = 0;
return NULL; return NULL;
......
This diff is collapsed.
...@@ -182,7 +182,12 @@ static bool ws_parse_frame_header(const uint8_t *buffer, size_t len, ws_frame_he ...@@ -182,7 +182,12 @@ static bool ws_parse_frame_header(const uint8_t *buffer, size_t len, ws_frame_he
bool ws_perform_handshake(ws_connection_t *conn) { bool ws_perform_handshake(ws_connection_t *conn) {
// Read HTTP request // Read HTTP request
char buffer[4096]; char buffer[4096];
int bytes_read = SSL_read(conn->ssl, buffer, sizeof(buffer) - 1); int bytes_read;
if (conn->ssl) {
bytes_read = SSL_read(conn->ssl, buffer, sizeof(buffer) - 1);
} else {
bytes_read = read(conn->sock_fd, buffer, sizeof(buffer) - 1);
}
if (bytes_read <= 0) return false; if (bytes_read <= 0) return false;
buffer[bytes_read] = '\0'; buffer[bytes_read] = '\0';
...@@ -251,7 +256,12 @@ bool ws_perform_handshake(ws_connection_t *conn) { ...@@ -251,7 +256,12 @@ bool ws_perform_handshake(ws_connection_t *conn) {
free(accept_key); free(accept_key);
int bytes_written = SSL_write(conn->ssl, response, strlen(response)); int bytes_written;
if (conn->ssl) {
bytes_written = SSL_write(conn->ssl, response, strlen(response));
} else {
bytes_written = write(conn->sock_fd, response, strlen(response));
}
if (bytes_written <= 0) return false; if (bytes_written <= 0) return false;
conn->state = WS_STATE_OPEN; conn->state = WS_STATE_OPEN;
...@@ -268,9 +278,7 @@ bool ws_send_frame(ws_connection_t *conn, uint8_t opcode, const void *data, size ...@@ -268,9 +278,7 @@ bool ws_send_frame(ws_connection_t *conn, uint8_t opcode, const void *data, size
return false; return false;
} }
if (!conn->ssl) { // Allow non-SSL connections for web interface
return false;
}
size_t header_len = 2; size_t header_len = 2;
if (len >= 126) { if (len >= 126) {
...@@ -318,23 +326,31 @@ bool ws_send_frame(ws_connection_t *conn, uint8_t opcode, const void *data, size ...@@ -318,23 +326,31 @@ bool ws_send_frame(ws_connection_t *conn, uint8_t opcode, const void *data, size
while (total_written < (int)frame_len && retry_count < max_retries) { while (total_written < (int)frame_len && retry_count < max_retries) {
int to_write = frame_len - total_written; int to_write = frame_len - total_written;
int written = SSL_write(conn->ssl, frame + total_written, to_write); int written;
if (written <= 0) { if (conn->ssl) {
int ssl_error = SSL_get_error(conn->ssl, written); written = SSL_write(conn->ssl, frame + total_written, to_write);
if (written <= 0) {
// Check for recoverable SSL errors int ssl_error = SSL_get_error(conn->ssl, written);
if ((ssl_error == SSL_ERROR_WANT_READ || ssl_error == SSL_ERROR_WANT_WRITE ||
ssl_error == SSL_ERROR_SSL || ssl_error == SSL_ERROR_SYSCALL) && retry_count < max_retries - 1) { // Check for recoverable SSL errors
retry_count++; if ((ssl_error == SSL_ERROR_WANT_READ || ssl_error == SSL_ERROR_WANT_WRITE ||
// Exponential backoff: wait longer between retries ssl_error == SSL_ERROR_SSL || ssl_error == SSL_ERROR_SYSCALL) && retry_count < max_retries - 1) {
usleep(10000 * (1 << retry_count)); // 10ms, 20ms, 40ms, 80ms retry_count++;
continue; // Retry the write operation // Exponential backoff: wait longer between retries
} else { usleep(10000 * (1 << retry_count)); // 10ms, 20ms, 40ms, 80ms
// Don't mark connection as closed on send failures - let receive failures handle connection closure continue; // Retry the write operation
} else {
// Don't mark connection as closed on send failures - let receive failures handle connection closure
free(frame);
return false;
}
}
} else {
written = write(conn->sock_fd, frame + total_written, to_write);
if (written <= 0) {
free(frame);
return false;
} }
free(frame);
return false;
} }
total_written += written; total_written += written;
retry_count = 0; // Reset retry count on successful write retry_count = 0; // Reset retry count on successful write
...@@ -354,9 +370,20 @@ bool ws_receive_frame(ws_connection_t *conn, uint8_t *opcode, void **data, size_ ...@@ -354,9 +370,20 @@ bool ws_receive_frame(ws_connection_t *conn, uint8_t *opcode, void **data, size_
// Read minimum frame header (2 bytes) to determine full header size // Read minimum frame header (2 bytes) to determine full header size
uint8_t header[14]; uint8_t header[14];
int bytes_read = SSL_read(conn->ssl, header, 2); int bytes_read;
if (bytes_read <= 0) { if (conn->ssl) {
return false; bytes_read = SSL_read(conn->ssl, header, 2);
if (bytes_read <= 0) {
return false;
}
} else {
bytes_read = read(conn->sock_fd, header, 2);
if (bytes_read < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
return false; // No data available, not an error
}
return false; // Real error
}
} }
if (bytes_read != 2) { if (bytes_read != 2) {
return false; return false;
...@@ -376,9 +403,19 @@ bool ws_receive_frame(ws_connection_t *conn, uint8_t *opcode, void **data, size_ ...@@ -376,9 +403,19 @@ bool ws_receive_frame(ws_connection_t *conn, uint8_t *opcode, void **data, size_
if (min_header_size > 2) { if (min_header_size > 2) {
int total_read = 0; int total_read = 0;
while (total_read < (int)(min_header_size - 2)) { while (total_read < (int)(min_header_size - 2)) {
bytes_read = SSL_read(conn->ssl, header + 2 + total_read, min_header_size - 2 - total_read); if (conn->ssl) {
if (bytes_read <= 0) { bytes_read = SSL_read(conn->ssl, header + 2 + total_read, min_header_size - 2 - total_read);
return false; if (bytes_read <= 0) {
return false;
}
} else {
bytes_read = read(conn->sock_fd, header + 2 + total_read, min_header_size - 2 - total_read);
if (bytes_read < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
return false; // No data available
}
return false; // Real error
}
} }
total_read += bytes_read; total_read += bytes_read;
} }
...@@ -388,9 +425,22 @@ bool ws_receive_frame(ws_connection_t *conn, uint8_t *opcode, void **data, size_ ...@@ -388,9 +425,22 @@ bool ws_receive_frame(ws_connection_t *conn, uint8_t *opcode, void **data, size_
bool masked = (header[1] & 0x80) != 0; bool masked = (header[1] & 0x80) != 0;
size_t total_header_size = min_header_size; size_t total_header_size = min_header_size;
if (masked) { if (masked) {
bytes_read = SSL_read(conn->ssl, header + min_header_size, 4); if (conn->ssl) {
if (bytes_read != 4) { bytes_read = SSL_read(conn->ssl, header + min_header_size, 4);
return false; if (bytes_read != 4) {
return false;
}
} else {
bytes_read = read(conn->sock_fd, header + min_header_size, 4);
if (bytes_read < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
return false; // No data available
}
return false; // Real error
}
if (bytes_read != 4) {
return false;
}
} }
total_header_size += 4; total_header_size += 4;
} }
...@@ -432,10 +482,29 @@ bool ws_receive_frame(ws_connection_t *conn, uint8_t *opcode, void **data, size_ ...@@ -432,10 +482,29 @@ bool ws_receive_frame(ws_connection_t *conn, uint8_t *opcode, void **data, size_
// Limit read size to prevent excessive blocking // Limit read size to prevent excessive blocking
size_t to_read = remaining > 8192 ? 8192 : remaining; size_t to_read = remaining > 8192 ? 8192 : remaining;
bytes_read = SSL_read(conn->ssl, (char *)*data + total_read, to_read); if (conn->ssl) {
if (bytes_read <= 0) { bytes_read = SSL_read(conn->ssl, (char *)*data + total_read, to_read);
free(*data); if (bytes_read <= 0) {
return false; free(*data);
return false;
}
} else {
bytes_read = read(conn->sock_fd, (char *)*data + total_read, to_read);
if (bytes_read < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// For non-blocking sockets, if we can't read the complete payload at once,
// this is an error since WebSocket frames should be complete
free(*data);
return false;
}
free(*data);
return false;
}
if (bytes_read == 0) {
// Connection closed
free(*data);
return false;
}
} }
total_read += bytes_read; total_read += bytes_read;
} }
...@@ -461,7 +530,7 @@ bool ws_receive_frame(ws_connection_t *conn, uint8_t *opcode, void **data, size_ ...@@ -461,7 +530,7 @@ bool ws_receive_frame(ws_connection_t *conn, uint8_t *opcode, void **data, size_
// Check if WebSocket connection is healthy // Check if WebSocket connection is healthy
bool ws_connection_is_healthy(ws_connection_t *conn) { bool ws_connection_is_healthy(ws_connection_t *conn) {
// Simple health check - just verify connection is open and has SSL context // Simple health check - just verify connection is open and has SSL context or socket
// Actual connection health is determined by send/receive operations // Actual connection health is determined by send/receive operations
return conn && conn->state == WS_STATE_OPEN && conn->ssl; return conn && conn->state == WS_STATE_OPEN && (conn->ssl || conn->sock_fd >= 0);
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment