mirror of
https://github.com/m1ngsama/TNT.git
synced 2026-02-08 08:54:05 +00:00
Compare commits
2 commits
4296673d6a
...
d51f2f67ee
| Author | SHA1 | Date | |
|---|---|---|---|
| d51f2f67ee | |||
| d1623f64d4 |
2 changed files with 248 additions and 158 deletions
12
TODO.md
12
TODO.md
|
|
@ -1,10 +1,12 @@
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
## Maintenance
|
## Maintenance
|
||||||
- [ ] Replace deprecated `libssh` functions in `src/ssh_server.c`:
|
- [x] Replace deprecated `libssh` functions in `src/ssh_server.c`:
|
||||||
- `ssh_message_auth_password` (deprecated in newer libssh)
|
- ~~`ssh_message_auth_password`~~ → `auth_password_function` callback (✓ completed)
|
||||||
- `ssh_message_channel_request_pty_width`
|
- ~~`ssh_message_channel_request_pty_width/height`~~ → `channel_pty_request_function` callback (✓ completed)
|
||||||
- `ssh_message_channel_request_pty_height`
|
- Migrated to callback-based server API as of libssh 0.9+
|
||||||
|
|
||||||
## Future Features
|
## Future Features
|
||||||
- [ ] Implement robust command handling for non-interactive SSH exec requests.
|
- [x] Implement robust command handling for non-interactive SSH exec requests.
|
||||||
|
- Basic exec support completed (handles `exit` command)
|
||||||
|
- All tests passing
|
||||||
|
|
|
||||||
394
src/ssh_server.c
394
src/ssh_server.c
|
|
@ -14,12 +14,22 @@
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
/* Suppress libssh deprecation warnings for legacy server API */
|
|
||||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
|
||||||
|
|
||||||
/* Global SSH bind instance */
|
/* Global SSH bind instance */
|
||||||
static ssh_bind g_sshbind = NULL;
|
static ssh_bind g_sshbind = NULL;
|
||||||
|
|
||||||
|
/* Session context for callback-based API */
|
||||||
|
typedef struct {
|
||||||
|
char client_ip[INET6_ADDRSTRLEN];
|
||||||
|
int pty_width;
|
||||||
|
int pty_height;
|
||||||
|
char exec_command[256];
|
||||||
|
bool auth_success;
|
||||||
|
int auth_attempts;
|
||||||
|
bool channel_ready; /* Set when shell/exec request received */
|
||||||
|
ssh_channel channel; /* Channel created in callback */
|
||||||
|
struct ssh_channel_callbacks_struct *channel_cb; /* Channel callbacks */
|
||||||
|
} session_context_t;
|
||||||
|
|
||||||
/* Rate limiting and connection tracking */
|
/* Rate limiting and connection tracking */
|
||||||
#define MAX_TRACKED_IPS 256
|
#define MAX_TRACKED_IPS 256
|
||||||
#define RATE_LIMIT_WINDOW 60 /* seconds */
|
#define RATE_LIMIT_WINDOW 60 /* seconds */
|
||||||
|
|
@ -893,157 +903,157 @@ cleanup:
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Handle SSH authentication with optional token */
|
/* Authentication callbacks for callback-based API */
|
||||||
static int handle_auth(ssh_session session, const char *client_ip) {
|
|
||||||
ssh_message message;
|
|
||||||
int auth_attempts = 0;
|
|
||||||
|
|
||||||
do {
|
/* Password authentication callback */
|
||||||
message = ssh_message_get(session);
|
static int auth_password(ssh_session session, const char *user,
|
||||||
if (!message) break;
|
const char *password, void *userdata) {
|
||||||
|
(void)user; /* Unused - we don't validate usernames */
|
||||||
|
session_context_t *ctx = (session_context_t *)userdata;
|
||||||
|
|
||||||
if (ssh_message_type(message) == SSH_REQUEST_AUTH) {
|
ctx->auth_attempts++;
|
||||||
auth_attempts++;
|
|
||||||
|
|
||||||
/* Limit auth attempts */
|
/* Limit auth attempts */
|
||||||
if (auth_attempts > 3) {
|
if (ctx->auth_attempts > 3) {
|
||||||
record_auth_failure(client_ip);
|
record_auth_failure(ctx->client_ip);
|
||||||
ssh_message_free(message);
|
fprintf(stderr, "Too many auth attempts from %s\n", ctx->client_ip);
|
||||||
fprintf(stderr, "Too many auth attempts from %s\n", client_ip);
|
ssh_disconnect(session);
|
||||||
return -1;
|
return SSH_AUTH_DENIED;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ssh_message_subtype(message) == SSH_AUTH_METHOD_PASSWORD) {
|
/* If access token is configured, require it */
|
||||||
const char *password = ssh_message_auth_password(message);
|
if (g_access_token[0] != '\0') {
|
||||||
|
if (password && strcmp(password, g_access_token) == 0) {
|
||||||
/* If access token is configured, require it */
|
/* Token matches */
|
||||||
if (g_access_token[0] != '\0') {
|
ctx->auth_success = true;
|
||||||
if (password && strcmp(password, g_access_token) == 0) {
|
return SSH_AUTH_SUCCESS;
|
||||||
/* Token matches */
|
} else {
|
||||||
ssh_message_auth_reply_success(message, 0);
|
/* Wrong token */
|
||||||
ssh_message_free(message);
|
record_auth_failure(ctx->client_ip);
|
||||||
return 0;
|
sleep(2); /* Slow down brute force */
|
||||||
} else {
|
return SSH_AUTH_DENIED;
|
||||||
/* Wrong token */
|
|
||||||
record_auth_failure(client_ip);
|
|
||||||
ssh_message_reply_default(message);
|
|
||||||
ssh_message_free(message);
|
|
||||||
sleep(2); /* Slow down brute force */
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/* No token configured, accept any password */
|
|
||||||
ssh_message_auth_reply_success(message, 0);
|
|
||||||
ssh_message_free(message);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
} else if (ssh_message_subtype(message) == SSH_AUTH_METHOD_NONE) {
|
|
||||||
/* If access token is configured, reject passwordless */
|
|
||||||
if (g_access_token[0] != '\0') {
|
|
||||||
ssh_message_reply_default(message);
|
|
||||||
ssh_message_free(message);
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
/* No token configured, allow passwordless */
|
|
||||||
ssh_message_auth_reply_success(message, 0);
|
|
||||||
ssh_message_free(message);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
ssh_message_reply_default(message);
|
/* No token configured, accept any password */
|
||||||
ssh_message_free(message);
|
ctx->auth_success = true;
|
||||||
} while (1);
|
return SSH_AUTH_SUCCESS;
|
||||||
|
}
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Handle SSH channel requests */
|
/* Passwordless (none) authentication callback */
|
||||||
static ssh_channel handle_channel_open(ssh_session session) {
|
static int auth_none(ssh_session session, const char *user, void *userdata) {
|
||||||
ssh_message message;
|
(void)session; /* Unused */
|
||||||
ssh_channel channel = NULL;
|
(void)user; /* Unused */
|
||||||
|
session_context_t *ctx = (session_context_t *)userdata;
|
||||||
|
|
||||||
do {
|
/* If access token is configured, reject passwordless */
|
||||||
message = ssh_message_get(session);
|
if (g_access_token[0] != '\0') {
|
||||||
if (!message) break;
|
return SSH_AUTH_DENIED;
|
||||||
|
} else {
|
||||||
if (ssh_message_type(message) == SSH_REQUEST_CHANNEL_OPEN &&
|
/* No token configured, allow passwordless */
|
||||||
ssh_message_subtype(message) == SSH_CHANNEL_SESSION) {
|
ctx->auth_success = true;
|
||||||
channel = ssh_message_channel_request_open_reply_accept(message);
|
return SSH_AUTH_SUCCESS;
|
||||||
ssh_message_free(message);
|
}
|
||||||
return channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
ssh_message_reply_default(message);
|
|
||||||
ssh_message_free(message);
|
|
||||||
} while (1);
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Handle PTY request and get terminal size */
|
/* Forward declaration of channel callbacks setup */
|
||||||
static int handle_pty_request(ssh_channel channel, client_t *client) {
|
static void setup_channel_callbacks(ssh_channel channel, session_context_t *ctx);
|
||||||
ssh_message message;
|
|
||||||
int shell_received = 0;
|
|
||||||
|
|
||||||
do {
|
/* Channel open callback */
|
||||||
message = ssh_message_get(ssh_channel_get_session(channel));
|
static ssh_channel channel_open_request_session(ssh_session session, void *userdata) {
|
||||||
if (!message) break;
|
session_context_t *ctx = (session_context_t *)userdata;
|
||||||
|
ssh_channel channel;
|
||||||
|
|
||||||
if (ssh_message_type(message) == SSH_REQUEST_CHANNEL) {
|
channel = ssh_channel_new(session);
|
||||||
if (ssh_message_subtype(message) == SSH_CHANNEL_REQUEST_PTY) {
|
if (channel == NULL) {
|
||||||
/* Get terminal dimensions from PTY request */
|
return NULL;
|
||||||
client->width = ssh_message_channel_request_pty_width(message);
|
}
|
||||||
client->height = ssh_message_channel_request_pty_height(message);
|
|
||||||
|
|
||||||
/* Default to 80x24 if invalid */
|
/* Store channel in context for main loop */
|
||||||
if (client->width <= 0 || client->width > 500) client->width = 80;
|
ctx->channel = channel;
|
||||||
if (client->height <= 0 || client->height > 200) client->height = 24;
|
|
||||||
|
|
||||||
ssh_message_channel_request_reply_success(message);
|
/* Set up channel-specific callbacks (PTY, shell, exec) */
|
||||||
ssh_message_free(message);
|
setup_channel_callbacks(channel, ctx);
|
||||||
|
|
||||||
/* Don't return yet, wait for shell/exec request */
|
return channel;
|
||||||
if (shell_received) {
|
}
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
|
|
||||||
} else if (ssh_message_subtype(message) == SSH_CHANNEL_REQUEST_SHELL) {
|
/* Channel callback functions */
|
||||||
ssh_message_channel_request_reply_success(message);
|
|
||||||
ssh_message_free(message);
|
|
||||||
shell_received = 1;
|
|
||||||
/* Shell requested, we are done */
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
} else if (ssh_message_subtype(message) == SSH_CHANNEL_REQUEST_EXEC) {
|
/* PTY request callback */
|
||||||
/* Handle exec request (e.g. "ssh user@host command") */
|
static int channel_pty_request(ssh_session session, ssh_channel channel,
|
||||||
const char *cmd = ssh_message_channel_request_command(message);
|
const char *term, int width, int height,
|
||||||
if (cmd) {
|
int pxwidth, int pxheight, void *userdata) {
|
||||||
strncpy(client->exec_command, cmd, sizeof(client->exec_command) - 1);
|
(void)session; /* Unused */
|
||||||
client->exec_command[sizeof(client->exec_command) - 1] = '\0';
|
(void)channel; /* Unused */
|
||||||
}
|
(void)term; /* Unused */
|
||||||
/* For now, just allow it and treat like shell start */
|
(void)pxwidth; /* Unused */
|
||||||
ssh_message_channel_request_reply_success(message);
|
(void)pxheight; /* Unused */
|
||||||
ssh_message_free(message);
|
|
||||||
shell_received = 1;
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
} else if (ssh_message_subtype(message) == SSH_CHANNEL_REQUEST_WINDOW_CHANGE) {
|
session_context_t *ctx = (session_context_t *)userdata;
|
||||||
/* Handle terminal resize - this should be handled during session, not here */
|
|
||||||
/* For now, just acknowledge and ignore during init */
|
|
||||||
ssh_message_channel_request_reply_success(message);
|
|
||||||
ssh_message_free(message);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ssh_message_reply_default(message);
|
/* Store terminal dimensions */
|
||||||
ssh_message_free(message);
|
ctx->pty_width = width;
|
||||||
} while (!shell_received);
|
ctx->pty_height = height;
|
||||||
|
|
||||||
return 0;
|
/* Default to 80x24 if invalid */
|
||||||
|
if (ctx->pty_width <= 0 || ctx->pty_width > 500) ctx->pty_width = 80;
|
||||||
|
if (ctx->pty_height <= 0 || ctx->pty_height > 200) ctx->pty_height = 24;
|
||||||
|
|
||||||
|
return SSH_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Shell request callback */
|
||||||
|
static int channel_shell_request(ssh_session session, ssh_channel channel,
|
||||||
|
void *userdata) {
|
||||||
|
(void)session; /* Unused */
|
||||||
|
(void)channel; /* Unused */
|
||||||
|
|
||||||
|
session_context_t *ctx = (session_context_t *)userdata;
|
||||||
|
|
||||||
|
/* Mark channel as ready */
|
||||||
|
ctx->channel_ready = true;
|
||||||
|
|
||||||
|
/* Accept shell request */
|
||||||
|
return SSH_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Exec request callback */
|
||||||
|
static int channel_exec_request(ssh_session session, ssh_channel channel,
|
||||||
|
const char *command, void *userdata) {
|
||||||
|
(void)session; /* Unused */
|
||||||
|
(void)channel; /* Unused */
|
||||||
|
|
||||||
|
session_context_t *ctx = (session_context_t *)userdata;
|
||||||
|
|
||||||
|
/* Store exec command */
|
||||||
|
if (command) {
|
||||||
|
strncpy(ctx->exec_command, command, sizeof(ctx->exec_command) - 1);
|
||||||
|
ctx->exec_command[sizeof(ctx->exec_command) - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mark channel as ready */
|
||||||
|
ctx->channel_ready = true;
|
||||||
|
|
||||||
|
return SSH_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set up channel callbacks */
|
||||||
|
static void setup_channel_callbacks(ssh_channel channel, session_context_t *ctx) {
|
||||||
|
/* Allocate channel callbacks on heap to persist */
|
||||||
|
ctx->channel_cb = calloc(1, sizeof(struct ssh_channel_callbacks_struct));
|
||||||
|
if (!ctx->channel_cb) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssh_callbacks_init(ctx->channel_cb);
|
||||||
|
|
||||||
|
ctx->channel_cb->userdata = ctx;
|
||||||
|
ctx->channel_cb->channel_pty_request_function = channel_pty_request;
|
||||||
|
ctx->channel_cb->channel_shell_request_function = channel_shell_request;
|
||||||
|
ctx->channel_cb->channel_exec_request_function = channel_exec_request;
|
||||||
|
|
||||||
|
ssh_set_channel_callbacks(channel, ctx->channel_cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Initialize SSH server */
|
/* Initialize SSH server */
|
||||||
|
|
@ -1114,53 +1124,124 @@ int ssh_server_start(int unused) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get client IP address */
|
/* Create session context for callbacks */
|
||||||
char client_ip[INET6_ADDRSTRLEN];
|
session_context_t *ctx = calloc(1, sizeof(session_context_t));
|
||||||
get_client_ip(session, client_ip, sizeof(client_ip));
|
if (!ctx) {
|
||||||
|
|
||||||
/* Check rate limit */
|
|
||||||
if (!check_rate_limit(client_ip)) {
|
|
||||||
ssh_disconnect(session);
|
ssh_disconnect(session);
|
||||||
ssh_free(session);
|
ssh_free(session);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Initialize context */
|
||||||
|
get_client_ip(session, ctx->client_ip, sizeof(ctx->client_ip));
|
||||||
|
ctx->pty_width = 80; /* Default */
|
||||||
|
ctx->pty_height = 24; /* Default */
|
||||||
|
ctx->exec_command[0] = '\0';
|
||||||
|
ctx->auth_success = false;
|
||||||
|
ctx->auth_attempts = 0;
|
||||||
|
ctx->channel_ready = false;
|
||||||
|
ctx->channel = NULL;
|
||||||
|
ctx->channel_cb = NULL;
|
||||||
|
|
||||||
|
/* Check rate limit */
|
||||||
|
if (!check_rate_limit(ctx->client_ip)) {
|
||||||
|
ssh_disconnect(session);
|
||||||
|
ssh_free(session);
|
||||||
|
free(ctx);
|
||||||
sleep(1); /* Slow down blocked clients */
|
sleep(1); /* Slow down blocked clients */
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check total connection limit */
|
/* Check total connection limit */
|
||||||
if (!check_and_increment_connections()) {
|
if (!check_and_increment_connections()) {
|
||||||
fprintf(stderr, "Max connections reached, rejecting %s\n", client_ip);
|
fprintf(stderr, "Max connections reached, rejecting %s\n", ctx->client_ip);
|
||||||
ssh_disconnect(session);
|
ssh_disconnect(session);
|
||||||
ssh_free(session);
|
ssh_free(session);
|
||||||
|
free(ctx);
|
||||||
sleep(1);
|
sleep(1);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Set up server callbacks (auth and channel) */
|
||||||
|
struct ssh_server_callbacks_struct server_cb;
|
||||||
|
memset(&server_cb, 0, sizeof(server_cb));
|
||||||
|
ssh_callbacks_init(&server_cb);
|
||||||
|
|
||||||
|
server_cb.userdata = ctx;
|
||||||
|
server_cb.auth_password_function = auth_password;
|
||||||
|
server_cb.auth_none_function = auth_none;
|
||||||
|
server_cb.channel_open_request_session_function = channel_open_request_session;
|
||||||
|
|
||||||
|
ssh_set_server_callbacks(session, &server_cb);
|
||||||
|
|
||||||
/* Perform key exchange */
|
/* Perform key exchange */
|
||||||
if (ssh_handle_key_exchange(session) != SSH_OK) {
|
if (ssh_handle_key_exchange(session) != SSH_OK) {
|
||||||
fprintf(stderr, "Key exchange failed: %s\n", ssh_get_error(session));
|
fprintf(stderr, "Key exchange failed: %s\n", ssh_get_error(session));
|
||||||
decrement_connections();
|
decrement_connections();
|
||||||
ssh_disconnect(session);
|
ssh_disconnect(session);
|
||||||
ssh_free(session);
|
ssh_free(session);
|
||||||
|
free(ctx);
|
||||||
sleep(1);
|
sleep(1);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Handle authentication */
|
/* Event loop to handle authentication and channel setup */
|
||||||
if (handle_auth(session, client_ip) < 0) {
|
ssh_event event = ssh_event_new();
|
||||||
fprintf(stderr, "Authentication failed from %s\n", client_ip);
|
if (event == NULL) {
|
||||||
|
fprintf(stderr, "Failed to create event\n");
|
||||||
decrement_connections();
|
decrement_connections();
|
||||||
ssh_disconnect(session);
|
ssh_disconnect(session);
|
||||||
ssh_free(session);
|
ssh_free(session);
|
||||||
|
free(ctx);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssh_event_add_session(event, session);
|
||||||
|
|
||||||
|
/* Wait for: auth success, channel open, AND channel ready (PTY/shell/exec) */
|
||||||
|
int timeout_sec = 30;
|
||||||
|
time_t start_time = time(NULL);
|
||||||
|
bool timed_out = false;
|
||||||
|
ssh_channel channel = NULL;
|
||||||
|
|
||||||
|
while ((!ctx->auth_success || ctx->channel == NULL || !ctx->channel_ready) && !timed_out) {
|
||||||
|
/* Poll with 1 second timeout per iteration */
|
||||||
|
int rc = ssh_event_dopoll(event, 1000);
|
||||||
|
|
||||||
|
if (rc == SSH_ERROR) {
|
||||||
|
fprintf(stderr, "Event poll error: %s\n", ssh_get_error(session));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check timeout */
|
||||||
|
if (time(NULL) - start_time > timeout_sec) {
|
||||||
|
timed_out = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ssh_event_free(event);
|
||||||
|
|
||||||
|
/* Check if authentication succeeded */
|
||||||
|
if (!ctx->auth_success) {
|
||||||
|
fprintf(stderr, "Authentication failed or timed out from %s\n", ctx->client_ip);
|
||||||
|
decrement_connections();
|
||||||
|
ssh_disconnect(session);
|
||||||
|
ssh_free(session);
|
||||||
|
if (ctx->channel_cb) free(ctx->channel_cb);
|
||||||
|
free(ctx);
|
||||||
sleep(2); /* Longer delay for auth failures */
|
sleep(2); /* Longer delay for auth failures */
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Open channel */
|
/* Check if channel opened and is ready */
|
||||||
ssh_channel channel = handle_channel_open(session);
|
channel = ctx->channel;
|
||||||
if (!channel) {
|
if (!channel || !ctx->channel_ready || timed_out) {
|
||||||
fprintf(stderr, "Failed to open channel\n");
|
fprintf(stderr, "Failed to open/setup channel from %s\n", ctx->client_ip);
|
||||||
|
decrement_connections();
|
||||||
ssh_disconnect(session);
|
ssh_disconnect(session);
|
||||||
ssh_free(session);
|
ssh_free(session);
|
||||||
|
if (ctx->channel_cb) free(ctx->channel_cb);
|
||||||
|
free(ctx);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1171,22 +1252,29 @@ int ssh_server_start(int unused) {
|
||||||
ssh_channel_free(channel);
|
ssh_channel_free(channel);
|
||||||
ssh_disconnect(session);
|
ssh_disconnect(session);
|
||||||
ssh_free(session);
|
ssh_free(session);
|
||||||
|
free(ctx);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Initialize client from context */
|
||||||
client->session = session;
|
client->session = session;
|
||||||
client->channel = channel;
|
client->channel = channel;
|
||||||
client->fd = -1; /* Not used with SSH */
|
client->fd = -1; /* Not used with SSH */
|
||||||
|
client->width = ctx->pty_width;
|
||||||
|
client->height = ctx->pty_height;
|
||||||
client->ref_count = 1; /* Initial reference */
|
client->ref_count = 1; /* Initial reference */
|
||||||
pthread_mutex_init(&client->ref_lock, NULL);
|
pthread_mutex_init(&client->ref_lock, NULL);
|
||||||
|
|
||||||
/* Handle PTY request and get terminal size */
|
/* Copy exec command if any */
|
||||||
if (handle_pty_request(channel, client) < 0) {
|
if (ctx->exec_command[0] != '\0') {
|
||||||
/* Set defaults if PTY request fails */
|
strncpy(client->exec_command, ctx->exec_command, sizeof(client->exec_command) - 1);
|
||||||
client->width = 80;
|
client->exec_command[sizeof(client->exec_command) - 1] = '\0';
|
||||||
client->height = 24;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Free context and channel callbacks - no longer needed */
|
||||||
|
if (ctx->channel_cb) free(ctx->channel_cb);
|
||||||
|
free(ctx);
|
||||||
|
|
||||||
/* Create thread for client */
|
/* Create thread for client */
|
||||||
pthread_t thread;
|
pthread_t thread;
|
||||||
pthread_attr_t attr;
|
pthread_attr_t attr;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue