mirror of
https://oauth2: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@github.com/m1ngsama/TNT.git
synced 2026-06-26 05:44:38 +08:00
refactor: extract bootstrap module (PR2-M4)
Move the per-connection SSH bootstrap pipeline -- key exchange, auth,
channel open + PTY/shell-or-exec request, and the hand-off into a
client_t -- out of ssh_server.c into a dedicated module.
Migrated to bootstrap.{c,h}:
- session_context_t (now private to bootstrap.c)
- accepted_session_t (declared in bootstrap.h, the IPC envelope from
the accept loop into the bootstrap thread)
- TNT_ACCESS_TOKEN handling: g_access_token + bootstrap_init()
- constant_time_strcmp (auth-only utility)
- bootstrap_peer_ip (peer IP read from libssh fd)
- auth_password / auth_none / auth_pubkey
- destroy_session_context, cleanup_failed_session
- channel_open_request_session, channel_pty_request,
channel_pty_window_change, channel_shell_request, channel_exec_request
- setup_session_channel_callbacks
- bootstrap_run (formerly bootstrap_client_session, the pthread entry)
Stayed in ssh_server.c:
- accept loop in ssh_server_start (now calls bootstrap_peer_ip and
pthread_create(bootstrap_run))
- ssh_server_init (now calls ratelimit_init() + bootstrap_init() +
reads only g_idle_timeout / TNT_BIND_ADDR / TNT_SSH_LOG_LEVEL)
- client_send/printf/addref/release, notify_mentions
- client_channel_window_change/eof/close (post-bootstrap, target client_t)
- client_install_channel_callbacks (renamed from
install_client_channel_callbacks, now non-static and exposed via
ssh_server.h so bootstrap.c can install them on the new client_t)
- read_username, handle_key, client_handle_session (will move to
input.c in PR2-M5)
- setup_host_key, ssh_server_start_time
Two helpers also lifted: sanitize_terminal_size moved to common.c (used
by the bootstrap PTY callback and the post-bootstrap window-change
callback), and is_valid_username already lived there from M2.
ssh_server.c shrinks from 1513 to 1026 lines (-487).
Behaviour is preserved: implementations are byte-for-byte the same.
This commit is contained in:
parent
8aa34c75b8
commit
b5f9a17290
6 changed files with 569 additions and 499 deletions
39
include/bootstrap.h
Normal file
39
include/bootstrap.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#ifndef BOOTSTRAP_H
|
||||
#define BOOTSTRAP_H
|
||||
|
||||
#include "ssh_server.h" /* for client_t and the libssh / arpa includes */
|
||||
|
||||
/* Hand-off envelope between the accept loop and the bootstrap thread.
|
||||
* The accept loop allocates one of these per accepted session, fills it,
|
||||
* and pthread_create()s a detached bootstrap_run() with this pointer.
|
||||
* bootstrap_run() owns the struct and the embedded ssh_session, and frees
|
||||
* both before returning. */
|
||||
typedef struct {
|
||||
ssh_session session;
|
||||
char client_ip[INET6_ADDRSTRLEN];
|
||||
} accepted_session_t;
|
||||
|
||||
/* Read TNT_ACCESS_TOKEN from the environment. Idempotent. Call once
|
||||
* during startup, before bootstrap_run() can fire on any accepted
|
||||
* session. */
|
||||
void bootstrap_init(void);
|
||||
|
||||
/* Read the peer IP off an accepted ssh_session into ip_buf. Sets ip_buf
|
||||
* to "unknown" when the address family is unrecognised or getpeername()
|
||||
* fails. ip_buf must be at least INET6_ADDRSTRLEN bytes. */
|
||||
void bootstrap_peer_ip(ssh_session session, char *ip_buf, size_t buf_size);
|
||||
|
||||
/* pthread entry point for the per-connection bootstrap thread.
|
||||
*
|
||||
* Steps performed before handing control to client_handle_session():
|
||||
* 1. SSH key exchange
|
||||
* 2. auth (password / none / pubkey, with rate-limit feedback)
|
||||
* 3. channel open + PTY/shell-or-exec request
|
||||
* 4. construct a client_t and install its lifetime channel callbacks
|
||||
*
|
||||
* On any failure path the connection is torn down and ratelimit /
|
||||
* connection counters are released; client_handle_session() is never
|
||||
* invoked. Always returns NULL. */
|
||||
void *bootstrap_run(void *arg);
|
||||
|
||||
#endif /* BOOTSTRAP_H */
|
||||
|
|
@ -72,4 +72,9 @@ int env_int(const char *name, int fallback, int min_val, int max_val);
|
|||
* author), and the :nick command. */
|
||||
bool is_valid_username(const char *username);
|
||||
|
||||
/* Clamp a terminal size to sensible bounds (1..500 cols, 1..200 rows).
|
||||
* Replaces zero/negative/oversize values with 80x24. Used by the PTY
|
||||
* request callback and the window-change callback. */
|
||||
void sanitize_terminal_size(int *width, int *height);
|
||||
|
||||
#endif /* COMMON_H */
|
||||
|
|
|
|||
|
|
@ -58,6 +58,14 @@ int client_printf(client_t *client, const char *fmt, ...);
|
|||
void client_addref(client_t *client);
|
||||
void client_release(client_t *client);
|
||||
|
||||
/* Install the post-bootstrap channel callbacks (window-change, eof, close)
|
||||
* that target this client_t. Caller MUST have already added one
|
||||
* client_addref() to keep the client alive across in-flight callback
|
||||
* invocations; the matching client_release() happens during cleanup in
|
||||
* client_handle_session(). Returns 0 on success, -1 on failure (in which
|
||||
* case the caller still owns both refs and must release them). */
|
||||
int client_install_channel_callbacks(client_t *client);
|
||||
|
||||
/* Bell-notify any clients whose @username appears in the broadcast content,
|
||||
* skipping the sender. Defined in ssh_server.c (will move to a dedicated
|
||||
* client.c during PR2-M6). */
|
||||
|
|
|
|||
492
src/bootstrap.c
Normal file
492
src/bootstrap.c
Normal file
|
|
@ -0,0 +1,492 @@
|
|||
#include "bootstrap.h"
|
||||
#include "common.h"
|
||||
#include "ratelimit.h"
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <libssh/callbacks.h>
|
||||
#include <libssh/libssh.h>
|
||||
#include <libssh/server.h>
|
||||
#include <netinet/in.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <time.h>
|
||||
|
||||
/* Per-connection bootstrap state. Kept private to this translation unit:
|
||||
* its lifetime ends inside bootstrap_run() once a client_t takes over. */
|
||||
typedef struct {
|
||||
char client_ip[INET6_ADDRSTRLEN];
|
||||
char requested_user[MAX_USERNAME_LEN];
|
||||
int pty_width;
|
||||
int pty_height;
|
||||
char exec_command[MAX_EXEC_COMMAND_LEN];
|
||||
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;
|
||||
|
||||
/* Configured access token; empty string means "no auth required". */
|
||||
static char g_access_token[256] = "";
|
||||
|
||||
void bootstrap_init(void) {
|
||||
const char *token_env = getenv("TNT_ACCESS_TOKEN");
|
||||
if (token_env != NULL) {
|
||||
strncpy(g_access_token, token_env, sizeof(g_access_token) - 1);
|
||||
g_access_token[sizeof(g_access_token) - 1] = '\0';
|
||||
} else {
|
||||
g_access_token[0] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void bootstrap_peer_ip(ssh_session session, char *ip_buf, size_t buf_size) {
|
||||
int fd = ssh_get_fd(session);
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addr_len = sizeof(addr);
|
||||
|
||||
if (getpeername(fd, (struct sockaddr *)&addr, &addr_len) == 0) {
|
||||
if (addr.ss_family == AF_INET) {
|
||||
struct sockaddr_in *s = (struct sockaddr_in *)&addr;
|
||||
inet_ntop(AF_INET, &s->sin_addr, ip_buf, buf_size);
|
||||
} else if (addr.ss_family == AF_INET6) {
|
||||
struct sockaddr_in6 *s = (struct sockaddr_in6 *)&addr;
|
||||
inet_ntop(AF_INET6, &s->sin6_addr, ip_buf, buf_size);
|
||||
} else {
|
||||
strncpy(ip_buf, "unknown", buf_size - 1);
|
||||
}
|
||||
} else {
|
||||
strncpy(ip_buf, "unknown", buf_size - 1);
|
||||
}
|
||||
ip_buf[buf_size - 1] = '\0';
|
||||
}
|
||||
|
||||
/* Constant-time string comparison to prevent timing side-channel attacks.
|
||||
* Always iterates over the full length of the secret (b) to avoid leaking
|
||||
* its length. When the input (a) is shorter, compares against zero bytes;
|
||||
* the length mismatch is folded into the result separately.
|
||||
*
|
||||
* Note: the length-diff is accumulated in size_t to avoid the bug where a
|
||||
* narrower type (e.g. unsigned char) would collapse pairs like (300, 44) to
|
||||
* 0 because 300 ^ 44 == 256 ^ (44 ^ 44) == 256 which truncates to 0. */
|
||||
static bool constant_time_strcmp(const char *a, const char *b) {
|
||||
size_t len_a = strlen(a);
|
||||
size_t len_b = strlen(b);
|
||||
volatile size_t length_diff = len_a ^ len_b;
|
||||
volatile unsigned char byte_diff = 0;
|
||||
for (size_t i = 0; i < len_b; i++) {
|
||||
unsigned char ca = (i < len_a) ? (unsigned char)a[i] : 0;
|
||||
byte_diff |= ca ^ (unsigned char)b[i];
|
||||
}
|
||||
return length_diff == 0 && byte_diff == 0;
|
||||
}
|
||||
|
||||
/* Password authentication callback */
|
||||
static int auth_password(ssh_session session, const char *user,
|
||||
const char *password, void *userdata) {
|
||||
session_context_t *ctx = (session_context_t *)userdata;
|
||||
|
||||
if (user && user[0] != '\0') {
|
||||
strncpy(ctx->requested_user, user, sizeof(ctx->requested_user) - 1);
|
||||
ctx->requested_user[sizeof(ctx->requested_user) - 1] = '\0';
|
||||
}
|
||||
|
||||
ctx->auth_attempts++;
|
||||
|
||||
/* Limit auth attempts */
|
||||
if (ctx->auth_attempts > 3) {
|
||||
ratelimit_record_auth_failure(ctx->client_ip);
|
||||
fprintf(stderr, "Too many auth attempts from %s\n", ctx->client_ip);
|
||||
ssh_disconnect(session);
|
||||
return SSH_AUTH_DENIED;
|
||||
}
|
||||
|
||||
/* If access token is configured, require it */
|
||||
if (g_access_token[0] != '\0') {
|
||||
if (password && constant_time_strcmp(password, g_access_token)) {
|
||||
/* Token matches */
|
||||
ctx->auth_success = true;
|
||||
return SSH_AUTH_SUCCESS;
|
||||
} else {
|
||||
/* Wrong token — IP blocking handles brute force, no sleep needed here
|
||||
* (sleeping in a libssh callback blocks the entire accept loop). */
|
||||
ratelimit_record_auth_failure(ctx->client_ip);
|
||||
return SSH_AUTH_DENIED;
|
||||
}
|
||||
} else {
|
||||
/* No token configured, accept any password */
|
||||
ctx->auth_success = true;
|
||||
return SSH_AUTH_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
/* Passwordless (none) authentication callback */
|
||||
static int auth_none(ssh_session session, const char *user, void *userdata) {
|
||||
(void)session; /* Unused */
|
||||
session_context_t *ctx = (session_context_t *)userdata;
|
||||
|
||||
if (user && user[0] != '\0') {
|
||||
strncpy(ctx->requested_user, user, sizeof(ctx->requested_user) - 1);
|
||||
ctx->requested_user[sizeof(ctx->requested_user) - 1] = '\0';
|
||||
}
|
||||
|
||||
/* If access token is configured, reject passwordless */
|
||||
if (g_access_token[0] != '\0') {
|
||||
return SSH_AUTH_DENIED;
|
||||
} else {
|
||||
/* No token configured, allow passwordless */
|
||||
ctx->auth_success = true;
|
||||
return SSH_AUTH_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
/* Public key authentication callback */
|
||||
static int auth_pubkey(ssh_session session, const char *user,
|
||||
struct ssh_key_struct *pubkey, char signature_state,
|
||||
void *userdata) {
|
||||
(void)session;
|
||||
(void)pubkey;
|
||||
session_context_t *ctx = (session_context_t *)userdata;
|
||||
|
||||
if (user && user[0] != '\0') {
|
||||
strncpy(ctx->requested_user, user, sizeof(ctx->requested_user) - 1);
|
||||
ctx->requested_user[sizeof(ctx->requested_user) - 1] = '\0';
|
||||
}
|
||||
|
||||
/* Reject if access token is required (pubkey auth not supported with tokens) */
|
||||
if (g_access_token[0] != '\0') {
|
||||
return SSH_AUTH_DENIED;
|
||||
}
|
||||
|
||||
/* SSH_PUBLICKEY_STATE_NONE = key offer (no signature yet).
|
||||
* Return SUCCESS to tell libssh "I accept this key, verify the signature."
|
||||
* SSH_PUBLICKEY_STATE_VALID = signature verified by libssh. */
|
||||
if (signature_state != SSH_PUBLICKEY_STATE_VALID) {
|
||||
return SSH_AUTH_SUCCESS;
|
||||
}
|
||||
|
||||
ctx->auth_success = true;
|
||||
return SSH_AUTH_SUCCESS;
|
||||
}
|
||||
|
||||
static void destroy_session_context(session_context_t *ctx) {
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx->channel_cb) {
|
||||
free(ctx->channel_cb);
|
||||
}
|
||||
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
static void cleanup_failed_session(ssh_session session, session_context_t *ctx) {
|
||||
if (ctx && ctx->channel) {
|
||||
if (ctx->channel_cb) {
|
||||
ssh_remove_channel_callbacks(ctx->channel, ctx->channel_cb);
|
||||
}
|
||||
ssh_channel_close(ctx->channel);
|
||||
ssh_channel_free(ctx->channel);
|
||||
ctx->channel = NULL;
|
||||
}
|
||||
|
||||
if (session) {
|
||||
ssh_disconnect(session);
|
||||
ssh_free(session);
|
||||
}
|
||||
|
||||
if (ctx) {
|
||||
ratelimit_release_ip(ctx->client_ip);
|
||||
}
|
||||
destroy_session_context(ctx);
|
||||
ratelimit_decrement_total();
|
||||
}
|
||||
|
||||
static void setup_session_channel_callbacks(ssh_channel channel,
|
||||
session_context_t *ctx);
|
||||
|
||||
/* Channel open callback */
|
||||
static ssh_channel channel_open_request_session(ssh_session session, void *userdata) {
|
||||
session_context_t *ctx = (session_context_t *)userdata;
|
||||
ssh_channel channel;
|
||||
|
||||
channel = ssh_channel_new(session);
|
||||
if (channel == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Store channel in context for main loop */
|
||||
ctx->channel = channel;
|
||||
|
||||
/* Set up channel-specific callbacks (PTY, shell, exec) */
|
||||
setup_session_channel_callbacks(channel, ctx);
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
/* PTY request callback */
|
||||
static int channel_pty_request(ssh_session session, ssh_channel channel,
|
||||
const char *term, int width, int height,
|
||||
int pxwidth, int pxheight, void *userdata) {
|
||||
(void)session; /* Unused */
|
||||
(void)channel; /* Unused */
|
||||
(void)term; /* Unused */
|
||||
(void)pxwidth; /* Unused */
|
||||
(void)pxheight; /* Unused */
|
||||
|
||||
session_context_t *ctx = (session_context_t *)userdata;
|
||||
|
||||
/* Store terminal dimensions */
|
||||
ctx->pty_width = width;
|
||||
ctx->pty_height = height;
|
||||
|
||||
sanitize_terminal_size(&ctx->pty_width, &ctx->pty_height);
|
||||
|
||||
return SSH_OK;
|
||||
}
|
||||
|
||||
static int channel_pty_window_change(ssh_session session, ssh_channel channel,
|
||||
int width, int height,
|
||||
int pxwidth, int pxheight,
|
||||
void *userdata) {
|
||||
(void)session;
|
||||
(void)channel;
|
||||
(void)pxwidth;
|
||||
(void)pxheight;
|
||||
|
||||
session_context_t *ctx = (session_context_t *)userdata;
|
||||
|
||||
ctx->pty_width = width;
|
||||
ctx->pty_height = height;
|
||||
sanitize_terminal_size(&ctx->pty_width, &ctx->pty_height);
|
||||
|
||||
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_session_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_pty_window_change_function = channel_pty_window_change;
|
||||
ctx->channel_cb->channel_exec_request_function = channel_exec_request;
|
||||
|
||||
ssh_set_channel_callbacks(channel, ctx->channel_cb);
|
||||
}
|
||||
|
||||
void *bootstrap_run(void *arg) {
|
||||
accepted_session_t *accepted = (accepted_session_t *)arg;
|
||||
ssh_session session;
|
||||
session_context_t *ctx = NULL;
|
||||
ssh_event event = NULL;
|
||||
struct ssh_server_callbacks_struct server_cb;
|
||||
ssh_channel channel;
|
||||
client_t *client = NULL;
|
||||
bool timed_out = false;
|
||||
time_t start_time;
|
||||
char accepted_ip[INET6_ADDRSTRLEN] = "";
|
||||
|
||||
if (!accepted) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
session = accepted->session;
|
||||
if (accepted->client_ip[0] != '\0') {
|
||||
snprintf(accepted_ip, sizeof(accepted_ip), "%s", accepted->client_ip);
|
||||
}
|
||||
free(accepted);
|
||||
|
||||
ctx = calloc(1, sizeof(session_context_t));
|
||||
if (!ctx) {
|
||||
ratelimit_release_ip(accepted_ip);
|
||||
ssh_disconnect(session);
|
||||
ssh_free(session);
|
||||
ratelimit_decrement_total();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (accepted_ip[0] != '\0') {
|
||||
snprintf(ctx->client_ip, sizeof(ctx->client_ip), "%s", accepted_ip);
|
||||
} else {
|
||||
bootstrap_peer_ip(session, ctx->client_ip, sizeof(ctx->client_ip));
|
||||
}
|
||||
ctx->pty_width = 80;
|
||||
ctx->pty_height = 24;
|
||||
ctx->exec_command[0] = '\0';
|
||||
ctx->requested_user[0] = '\0';
|
||||
ctx->auth_success = false;
|
||||
ctx->auth_attempts = 0;
|
||||
ctx->channel_ready = false;
|
||||
ctx->channel = NULL;
|
||||
ctx->channel_cb = NULL;
|
||||
|
||||
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.auth_pubkey_function = auth_pubkey;
|
||||
server_cb.channel_open_request_session_function = channel_open_request_session;
|
||||
ssh_set_server_callbacks(session, &server_cb);
|
||||
|
||||
if (ssh_handle_key_exchange(session) != SSH_OK) {
|
||||
fprintf(stderr, "Key exchange failed from %s: %s\n",
|
||||
ctx->client_ip, ssh_get_error(session));
|
||||
cleanup_failed_session(session, ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
event = ssh_event_new();
|
||||
if (!event) {
|
||||
fprintf(stderr, "Failed to create SSH event for %s\n", ctx->client_ip);
|
||||
cleanup_failed_session(session, ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (ssh_event_add_session(event, session) != SSH_OK) {
|
||||
fprintf(stderr, "Failed to add session to event loop for %s\n",
|
||||
ctx->client_ip);
|
||||
ssh_event_free(event);
|
||||
cleanup_failed_session(session, ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
start_time = time(NULL);
|
||||
while ((!ctx->auth_success || ctx->channel == NULL || !ctx->channel_ready) &&
|
||||
!timed_out) {
|
||||
int rc = ssh_event_dopoll(event, 1000);
|
||||
|
||||
if (rc == SSH_ERROR) {
|
||||
fprintf(stderr, "Event poll error from %s: %s\n",
|
||||
ctx->client_ip, ssh_get_error(session));
|
||||
break;
|
||||
}
|
||||
|
||||
if (time(NULL) - start_time > 10) {
|
||||
timed_out = true;
|
||||
}
|
||||
}
|
||||
|
||||
ssh_event_free(event);
|
||||
event = NULL;
|
||||
|
||||
if (!ctx->auth_success) {
|
||||
fprintf(stderr, "Authentication failed or timed out from %s\n",
|
||||
ctx->client_ip);
|
||||
cleanup_failed_session(session, ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
channel = ctx->channel;
|
||||
if (!channel || !ctx->channel_ready || timed_out) {
|
||||
fprintf(stderr, "Failed to open/setup channel from %s\n",
|
||||
ctx->client_ip);
|
||||
cleanup_failed_session(session, ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
client = calloc(1, sizeof(client_t));
|
||||
if (!client) {
|
||||
cleanup_failed_session(session, ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
client->session = session;
|
||||
client->channel = channel;
|
||||
int init_w = ctx->pty_width;
|
||||
int init_h = ctx->pty_height;
|
||||
sanitize_terminal_size(&init_w, &init_h);
|
||||
client->width = init_w;
|
||||
client->height = init_h;
|
||||
client->ref_count = 1;
|
||||
pthread_mutex_init(&client->ref_lock, NULL);
|
||||
pthread_mutex_init(&client->io_lock, NULL);
|
||||
|
||||
if (ctx->requested_user[0] != '\0') {
|
||||
strncpy(client->ssh_login, ctx->requested_user,
|
||||
sizeof(client->ssh_login) - 1);
|
||||
client->ssh_login[sizeof(client->ssh_login) - 1] = '\0';
|
||||
}
|
||||
if (ctx->client_ip[0] != '\0') {
|
||||
snprintf(client->client_ip, sizeof(client->client_ip), "%s",
|
||||
ctx->client_ip);
|
||||
}
|
||||
if (ctx->exec_command[0] != '\0') {
|
||||
strncpy(client->exec_command, ctx->exec_command,
|
||||
sizeof(client->exec_command) - 1);
|
||||
client->exec_command[sizeof(client->exec_command) - 1] = '\0';
|
||||
}
|
||||
|
||||
/* Add a ref for the channel callbacks (eof/close/window_change) so the
|
||||
* client_t outlives any in-flight callback invocation. */
|
||||
client_addref(client);
|
||||
|
||||
if (client_install_channel_callbacks(client) < 0) {
|
||||
/* Nullify session/channel ownership so client_release won't
|
||||
* double-free what cleanup_failed_session is about to free. */
|
||||
client->session = NULL;
|
||||
client->channel = NULL;
|
||||
client_release(client); /* drop the callback ref (2 → 1) */
|
||||
client_release(client); /* drop the main ref (1 → 0, frees client) */
|
||||
cleanup_failed_session(session, ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (ctx->channel_cb) {
|
||||
ssh_remove_channel_callbacks(channel, ctx->channel_cb);
|
||||
free(ctx->channel_cb);
|
||||
ctx->channel_cb = NULL;
|
||||
}
|
||||
destroy_session_context(ctx);
|
||||
|
||||
client_handle_session(client);
|
||||
return NULL;
|
||||
}
|
||||
13
src/common.c
13
src/common.c
|
|
@ -161,3 +161,16 @@ bool is_valid_username(const char *username) {
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
void sanitize_terminal_size(int *width, int *height) {
|
||||
if (!width || !height) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (*width <= 0 || *width > 500) {
|
||||
*width = 80;
|
||||
}
|
||||
if (*height <= 0 || *height > 200) {
|
||||
*height = 24;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
511
src/ssh_server.c
511
src/ssh_server.c
|
|
@ -1,4 +1,5 @@
|
|||
#include "ssh_server.h"
|
||||
#include "bootstrap.h"
|
||||
#include "commands.h"
|
||||
#include "exec.h"
|
||||
#include "ratelimit.h"
|
||||
|
|
@ -22,92 +23,17 @@
|
|||
static ssh_bind g_sshbind = NULL;
|
||||
static int g_listen_port = DEFAULT_PORT;
|
||||
|
||||
/* Session context for callback-based API */
|
||||
typedef struct {
|
||||
char client_ip[INET6_ADDRSTRLEN];
|
||||
char requested_user[MAX_USERNAME_LEN];
|
||||
int pty_width;
|
||||
int pty_height;
|
||||
char exec_command[MAX_EXEC_COMMAND_LEN];
|
||||
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;
|
||||
|
||||
typedef struct {
|
||||
ssh_session session;
|
||||
char client_ip[INET6_ADDRSTRLEN];
|
||||
} accepted_session_t;
|
||||
|
||||
static time_t g_server_start_time = 0;
|
||||
|
||||
time_t ssh_server_start_time(void) {
|
||||
return g_server_start_time;
|
||||
}
|
||||
|
||||
/* Configuration from environment variables. Rate-limiting / connection-count
|
||||
* config has moved to ratelimit.{c,h}; the two below stay here until the auth
|
||||
* and input modules are extracted in later PR2 steps. */
|
||||
static char g_access_token[256] = "";
|
||||
/* Configuration from environment variables. Rate-limiting moved to ratelimit.{c,h}
|
||||
* and the access token now lives in bootstrap.{c,h}; the idle timeout stays here
|
||||
* until input.c is extracted in PR2-M5. */
|
||||
static int g_idle_timeout = DEFAULT_IDLE_TIMEOUT;
|
||||
|
||||
/* Constant-time string comparison to prevent timing side-channel attacks.
|
||||
* Always iterates over the full length of the secret (b) to avoid leaking
|
||||
* its length. When the input (a) is shorter, compares against zero bytes;
|
||||
* the length mismatch is folded into the result separately.
|
||||
*
|
||||
* Note: the length-diff is accumulated in size_t to avoid the bug where a
|
||||
* narrower type (e.g. unsigned char) would collapse pairs like (300, 44) to
|
||||
* 0 because 300 ^ 44 == 256 ^ (44 ^ 44) == 256 which truncates to 0. */
|
||||
static bool constant_time_strcmp(const char *a, const char *b) {
|
||||
size_t len_a = strlen(a);
|
||||
size_t len_b = strlen(b);
|
||||
volatile size_t length_diff = len_a ^ len_b;
|
||||
volatile unsigned char byte_diff = 0;
|
||||
for (size_t i = 0; i < len_b; i++) {
|
||||
unsigned char ca = (i < len_a) ? (unsigned char)a[i] : 0;
|
||||
byte_diff |= ca ^ (unsigned char)b[i];
|
||||
}
|
||||
return length_diff == 0 && byte_diff == 0;
|
||||
}
|
||||
|
||||
/* Get client IP address */
|
||||
static void get_client_ip(ssh_session session, char *ip_buf, size_t buf_size) {
|
||||
int fd = ssh_get_fd(session);
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addr_len = sizeof(addr);
|
||||
|
||||
if (getpeername(fd, (struct sockaddr *)&addr, &addr_len) == 0) {
|
||||
if (addr.ss_family == AF_INET) {
|
||||
struct sockaddr_in *s = (struct sockaddr_in *)&addr;
|
||||
inet_ntop(AF_INET, &s->sin_addr, ip_buf, buf_size);
|
||||
} else if (addr.ss_family == AF_INET6) {
|
||||
struct sockaddr_in6 *s = (struct sockaddr_in6 *)&addr;
|
||||
inet_ntop(AF_INET6, &s->sin6_addr, ip_buf, buf_size);
|
||||
} else {
|
||||
strncpy(ip_buf, "unknown", buf_size - 1);
|
||||
}
|
||||
} else {
|
||||
strncpy(ip_buf, "unknown", buf_size - 1);
|
||||
}
|
||||
ip_buf[buf_size - 1] = '\0';
|
||||
}
|
||||
|
||||
static void sanitize_terminal_size(int *width, int *height) {
|
||||
if (!width || !height) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (*width <= 0 || *width > 500) {
|
||||
*width = 80;
|
||||
}
|
||||
if (*height <= 0 || *height > 200) {
|
||||
*height = 24;
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate or load SSH host key */
|
||||
static int setup_host_key(ssh_bind sshbind) {
|
||||
struct stat st;
|
||||
|
|
@ -890,7 +816,7 @@ cleanup:
|
|||
ssh_remove_channel_callbacks(client->channel, client->channel_cb);
|
||||
}
|
||||
|
||||
/* Release the callback reference (paired with addref before install_client_channel_callbacks) */
|
||||
/* Release the callback reference (paired with addref before client_install_channel_callbacks) */
|
||||
client_release(client);
|
||||
|
||||
/* Release the main reference - client will be freed when all refs are gone */
|
||||
|
|
@ -902,249 +828,6 @@ cleanup:
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/* Authentication callbacks for callback-based API */
|
||||
|
||||
/* Password authentication callback */
|
||||
static int auth_password(ssh_session session, const char *user,
|
||||
const char *password, void *userdata) {
|
||||
session_context_t *ctx = (session_context_t *)userdata;
|
||||
|
||||
if (user && user[0] != '\0') {
|
||||
strncpy(ctx->requested_user, user, sizeof(ctx->requested_user) - 1);
|
||||
ctx->requested_user[sizeof(ctx->requested_user) - 1] = '\0';
|
||||
}
|
||||
|
||||
ctx->auth_attempts++;
|
||||
|
||||
/* Limit auth attempts */
|
||||
if (ctx->auth_attempts > 3) {
|
||||
ratelimit_record_auth_failure(ctx->client_ip);
|
||||
fprintf(stderr, "Too many auth attempts from %s\n", ctx->client_ip);
|
||||
ssh_disconnect(session);
|
||||
return SSH_AUTH_DENIED;
|
||||
}
|
||||
|
||||
/* If access token is configured, require it */
|
||||
if (g_access_token[0] != '\0') {
|
||||
if (password && constant_time_strcmp(password, g_access_token)) {
|
||||
/* Token matches */
|
||||
ctx->auth_success = true;
|
||||
return SSH_AUTH_SUCCESS;
|
||||
} else {
|
||||
/* Wrong token — IP blocking handles brute force, no sleep needed here
|
||||
* (sleeping in a libssh callback blocks the entire accept loop). */
|
||||
ratelimit_record_auth_failure(ctx->client_ip);
|
||||
return SSH_AUTH_DENIED;
|
||||
}
|
||||
} else {
|
||||
/* No token configured, accept any password */
|
||||
ctx->auth_success = true;
|
||||
return SSH_AUTH_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
/* Passwordless (none) authentication callback */
|
||||
static int auth_none(ssh_session session, const char *user, void *userdata) {
|
||||
(void)session; /* Unused */
|
||||
session_context_t *ctx = (session_context_t *)userdata;
|
||||
|
||||
if (user && user[0] != '\0') {
|
||||
strncpy(ctx->requested_user, user, sizeof(ctx->requested_user) - 1);
|
||||
ctx->requested_user[sizeof(ctx->requested_user) - 1] = '\0';
|
||||
}
|
||||
|
||||
/* If access token is configured, reject passwordless */
|
||||
if (g_access_token[0] != '\0') {
|
||||
return SSH_AUTH_DENIED;
|
||||
} else {
|
||||
/* No token configured, allow passwordless */
|
||||
ctx->auth_success = true;
|
||||
return SSH_AUTH_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
/* Public key authentication callback */
|
||||
static int auth_pubkey(ssh_session session, const char *user,
|
||||
struct ssh_key_struct *pubkey, char signature_state,
|
||||
void *userdata) {
|
||||
(void)session;
|
||||
(void)pubkey;
|
||||
session_context_t *ctx = (session_context_t *)userdata;
|
||||
|
||||
if (user && user[0] != '\0') {
|
||||
strncpy(ctx->requested_user, user, sizeof(ctx->requested_user) - 1);
|
||||
ctx->requested_user[sizeof(ctx->requested_user) - 1] = '\0';
|
||||
}
|
||||
|
||||
/* Reject if access token is required (pubkey auth not supported with tokens) */
|
||||
if (g_access_token[0] != '\0') {
|
||||
return SSH_AUTH_DENIED;
|
||||
}
|
||||
|
||||
/* SSH_PUBLICKEY_STATE_NONE = key offer (no signature yet).
|
||||
* Return SUCCESS to tell libssh "I accept this key, verify the signature."
|
||||
* SSH_PUBLICKEY_STATE_VALID = signature verified by libssh. */
|
||||
if (signature_state != SSH_PUBLICKEY_STATE_VALID) {
|
||||
return SSH_AUTH_SUCCESS;
|
||||
}
|
||||
|
||||
ctx->auth_success = true;
|
||||
return SSH_AUTH_SUCCESS;
|
||||
}
|
||||
|
||||
static void destroy_session_context(session_context_t *ctx) {
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx->channel_cb) {
|
||||
free(ctx->channel_cb);
|
||||
}
|
||||
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
static void cleanup_failed_session(ssh_session session, session_context_t *ctx) {
|
||||
if (ctx && ctx->channel) {
|
||||
if (ctx->channel_cb) {
|
||||
ssh_remove_channel_callbacks(ctx->channel, ctx->channel_cb);
|
||||
}
|
||||
ssh_channel_close(ctx->channel);
|
||||
ssh_channel_free(ctx->channel);
|
||||
ctx->channel = NULL;
|
||||
}
|
||||
|
||||
if (session) {
|
||||
ssh_disconnect(session);
|
||||
ssh_free(session);
|
||||
}
|
||||
|
||||
if (ctx) {
|
||||
ratelimit_release_ip(ctx->client_ip);
|
||||
}
|
||||
destroy_session_context(ctx);
|
||||
ratelimit_decrement_total();
|
||||
}
|
||||
|
||||
static void setup_session_channel_callbacks(ssh_channel channel,
|
||||
session_context_t *ctx);
|
||||
static int install_client_channel_callbacks(client_t *client);
|
||||
|
||||
/* Channel open callback */
|
||||
static ssh_channel channel_open_request_session(ssh_session session, void *userdata) {
|
||||
session_context_t *ctx = (session_context_t *)userdata;
|
||||
ssh_channel channel;
|
||||
|
||||
channel = ssh_channel_new(session);
|
||||
if (channel == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Store channel in context for main loop */
|
||||
ctx->channel = channel;
|
||||
|
||||
/* Set up channel-specific callbacks (PTY, shell, exec) */
|
||||
setup_session_channel_callbacks(channel, ctx);
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
/* Channel callback functions */
|
||||
|
||||
/* PTY request callback */
|
||||
static int channel_pty_request(ssh_session session, ssh_channel channel,
|
||||
const char *term, int width, int height,
|
||||
int pxwidth, int pxheight, void *userdata) {
|
||||
(void)session; /* Unused */
|
||||
(void)channel; /* Unused */
|
||||
(void)term; /* Unused */
|
||||
(void)pxwidth; /* Unused */
|
||||
(void)pxheight; /* Unused */
|
||||
|
||||
session_context_t *ctx = (session_context_t *)userdata;
|
||||
|
||||
/* Store terminal dimensions */
|
||||
ctx->pty_width = width;
|
||||
ctx->pty_height = height;
|
||||
|
||||
sanitize_terminal_size(&ctx->pty_width, &ctx->pty_height);
|
||||
|
||||
return SSH_OK;
|
||||
}
|
||||
|
||||
static int channel_pty_window_change(ssh_session session, ssh_channel channel,
|
||||
int width, int height,
|
||||
int pxwidth, int pxheight,
|
||||
void *userdata) {
|
||||
(void)session;
|
||||
(void)channel;
|
||||
(void)pxwidth;
|
||||
(void)pxheight;
|
||||
|
||||
session_context_t *ctx = (session_context_t *)userdata;
|
||||
|
||||
ctx->pty_width = width;
|
||||
ctx->pty_height = height;
|
||||
sanitize_terminal_size(&ctx->pty_width, &ctx->pty_height);
|
||||
|
||||
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_session_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_pty_window_change_function = channel_pty_window_change;
|
||||
ctx->channel_cb->channel_exec_request_function = channel_exec_request;
|
||||
|
||||
ssh_set_channel_callbacks(channel, ctx->channel_cb);
|
||||
}
|
||||
|
||||
static int client_channel_window_change(ssh_session session, ssh_channel channel,
|
||||
int width, int height,
|
||||
int pxwidth, int pxheight,
|
||||
|
|
@ -1190,7 +873,7 @@ static void client_channel_close(ssh_session session, ssh_channel channel,
|
|||
}
|
||||
}
|
||||
|
||||
static int install_client_channel_callbacks(client_t *client) {
|
||||
int client_install_channel_callbacks(client_t *client) {
|
||||
if (!client || !client->channel) {
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -1216,187 +899,17 @@ static int install_client_channel_callbacks(client_t *client) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void *bootstrap_client_session(void *arg) {
|
||||
accepted_session_t *accepted = (accepted_session_t *)arg;
|
||||
ssh_session session;
|
||||
session_context_t *ctx = NULL;
|
||||
ssh_event event = NULL;
|
||||
struct ssh_server_callbacks_struct server_cb;
|
||||
ssh_channel channel;
|
||||
client_t *client = NULL;
|
||||
bool timed_out = false;
|
||||
time_t start_time;
|
||||
char accepted_ip[INET6_ADDRSTRLEN] = "";
|
||||
|
||||
if (!accepted) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
session = accepted->session;
|
||||
if (accepted->client_ip[0] != '\0') {
|
||||
snprintf(accepted_ip, sizeof(accepted_ip), "%s", accepted->client_ip);
|
||||
}
|
||||
free(accepted);
|
||||
|
||||
ctx = calloc(1, sizeof(session_context_t));
|
||||
if (!ctx) {
|
||||
ratelimit_release_ip(accepted_ip);
|
||||
ssh_disconnect(session);
|
||||
ssh_free(session);
|
||||
ratelimit_decrement_total();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (accepted_ip[0] != '\0') {
|
||||
snprintf(ctx->client_ip, sizeof(ctx->client_ip), "%s", accepted_ip);
|
||||
} else {
|
||||
get_client_ip(session, ctx->client_ip, sizeof(ctx->client_ip));
|
||||
}
|
||||
ctx->pty_width = 80;
|
||||
ctx->pty_height = 24;
|
||||
ctx->exec_command[0] = '\0';
|
||||
ctx->requested_user[0] = '\0';
|
||||
ctx->auth_success = false;
|
||||
ctx->auth_attempts = 0;
|
||||
ctx->channel_ready = false;
|
||||
ctx->channel = NULL;
|
||||
ctx->channel_cb = NULL;
|
||||
|
||||
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.auth_pubkey_function = auth_pubkey;
|
||||
server_cb.channel_open_request_session_function = channel_open_request_session;
|
||||
ssh_set_server_callbacks(session, &server_cb);
|
||||
|
||||
if (ssh_handle_key_exchange(session) != SSH_OK) {
|
||||
fprintf(stderr, "Key exchange failed from %s: %s\n",
|
||||
ctx->client_ip, ssh_get_error(session));
|
||||
cleanup_failed_session(session, ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
event = ssh_event_new();
|
||||
if (!event) {
|
||||
fprintf(stderr, "Failed to create SSH event for %s\n", ctx->client_ip);
|
||||
cleanup_failed_session(session, ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (ssh_event_add_session(event, session) != SSH_OK) {
|
||||
fprintf(stderr, "Failed to add session to event loop for %s\n",
|
||||
ctx->client_ip);
|
||||
ssh_event_free(event);
|
||||
cleanup_failed_session(session, ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
start_time = time(NULL);
|
||||
while ((!ctx->auth_success || ctx->channel == NULL || !ctx->channel_ready) &&
|
||||
!timed_out) {
|
||||
int rc = ssh_event_dopoll(event, 1000);
|
||||
|
||||
if (rc == SSH_ERROR) {
|
||||
fprintf(stderr, "Event poll error from %s: %s\n",
|
||||
ctx->client_ip, ssh_get_error(session));
|
||||
break;
|
||||
}
|
||||
|
||||
if (time(NULL) - start_time > 10) {
|
||||
timed_out = true;
|
||||
}
|
||||
}
|
||||
|
||||
ssh_event_free(event);
|
||||
event = NULL;
|
||||
|
||||
if (!ctx->auth_success) {
|
||||
fprintf(stderr, "Authentication failed or timed out from %s\n",
|
||||
ctx->client_ip);
|
||||
cleanup_failed_session(session, ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
channel = ctx->channel;
|
||||
if (!channel || !ctx->channel_ready || timed_out) {
|
||||
fprintf(stderr, "Failed to open/setup channel from %s\n",
|
||||
ctx->client_ip);
|
||||
cleanup_failed_session(session, ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
client = calloc(1, sizeof(client_t));
|
||||
if (!client) {
|
||||
cleanup_failed_session(session, ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
client->session = session;
|
||||
client->channel = channel;
|
||||
int init_w = ctx->pty_width;
|
||||
int init_h = ctx->pty_height;
|
||||
sanitize_terminal_size(&init_w, &init_h);
|
||||
client->width = init_w;
|
||||
client->height = init_h;
|
||||
client->ref_count = 1;
|
||||
pthread_mutex_init(&client->ref_lock, NULL);
|
||||
pthread_mutex_init(&client->io_lock, NULL);
|
||||
|
||||
if (ctx->requested_user[0] != '\0') {
|
||||
strncpy(client->ssh_login, ctx->requested_user,
|
||||
sizeof(client->ssh_login) - 1);
|
||||
client->ssh_login[sizeof(client->ssh_login) - 1] = '\0';
|
||||
}
|
||||
if (ctx->client_ip[0] != '\0') {
|
||||
snprintf(client->client_ip, sizeof(client->client_ip), "%s",
|
||||
ctx->client_ip);
|
||||
}
|
||||
if (ctx->exec_command[0] != '\0') {
|
||||
strncpy(client->exec_command, ctx->exec_command,
|
||||
sizeof(client->exec_command) - 1);
|
||||
client->exec_command[sizeof(client->exec_command) - 1] = '\0';
|
||||
}
|
||||
|
||||
/* Add a ref for the channel callbacks (eof/close/window_change) so the
|
||||
* client_t outlives any in-flight callback invocation. */
|
||||
client_addref(client);
|
||||
|
||||
if (install_client_channel_callbacks(client) < 0) {
|
||||
/* Nullify session/channel ownership so client_release won't
|
||||
* double-free what cleanup_failed_session is about to free. */
|
||||
client->session = NULL;
|
||||
client->channel = NULL;
|
||||
client_release(client); /* drop the callback ref (2 → 1) */
|
||||
client_release(client); /* drop the main ref (1 → 0, frees client) */
|
||||
cleanup_failed_session(session, ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (ctx->channel_cb) {
|
||||
ssh_remove_channel_callbacks(channel, ctx->channel_cb);
|
||||
free(ctx->channel_cb);
|
||||
ctx->channel_cb = NULL;
|
||||
}
|
||||
destroy_session_context(ctx);
|
||||
|
||||
client_handle_session(client);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Initialize SSH server */
|
||||
int ssh_server_init(int port) {
|
||||
/* Initialize rate-limit / connection-count subsystem */
|
||||
ratelimit_init();
|
||||
|
||||
/* Auth / session config (will move into auth.c and input.c in later PR2 steps) */
|
||||
/* Initialize bootstrap (reads TNT_ACCESS_TOKEN) */
|
||||
bootstrap_init();
|
||||
|
||||
/* Idle timeout stays here until input.c is extracted in PR2-M5 */
|
||||
g_idle_timeout = env_int("TNT_IDLE_TIMEOUT", DEFAULT_IDLE_TIMEOUT, 0, 86400);
|
||||
const char *token_env = getenv("TNT_ACCESS_TOKEN");
|
||||
if (token_env != NULL) {
|
||||
strncpy(g_access_token, token_env, sizeof(g_access_token) - 1);
|
||||
g_access_token[sizeof(g_access_token) - 1] = '\0';
|
||||
}
|
||||
g_listen_port = port;
|
||||
g_server_start_time = time(NULL);
|
||||
|
||||
|
|
@ -1469,7 +982,7 @@ int ssh_server_start(int unused) {
|
|||
continue;
|
||||
}
|
||||
|
||||
get_client_ip(session, client_ip, sizeof(client_ip));
|
||||
bootstrap_peer_ip(session, client_ip, sizeof(client_ip));
|
||||
|
||||
/* Check total connection limit */
|
||||
if (!ratelimit_check_and_increment_total()) {
|
||||
|
|
@ -1499,7 +1012,7 @@ int ssh_server_start(int unused) {
|
|||
snprintf(accepted->client_ip, sizeof(accepted->client_ip), "%s",
|
||||
client_ip);
|
||||
|
||||
if (pthread_create(&thread, &attr, bootstrap_client_session, accepted) != 0) {
|
||||
if (pthread_create(&thread, &attr, bootstrap_run, accepted) != 0) {
|
||||
fprintf(stderr, "Thread creation failed: %s\n", strerror(errno));
|
||||
free(accepted);
|
||||
ratelimit_release_ip(client_ip);
|
||||
|
|
|
|||
Loading…
Reference in a new issue