TNT/src/client.c
m1ngsama 7897d8820e refactor: extract client module (PR2-M6)
Move client_t I/O and lifecycle out of ssh_server.c into a dedicated
module.  ssh_server.c is now down to the listening socket, host key
setup, ssh_server_init / ssh_server_start, and the accept loop.

Migrated to client.{c,h}:
- client_send, client_printf
- client_addref, client_release (and the ssh_session / ssh_channel /
  channel_cb teardown that fires on the final release)
- client_install_channel_callbacks
- client_channel_window_change / _eof / _close (the post-bootstrap
  callbacks that target the client_t)

The client_t struct definition stays in ssh_server.h so we don't have
to revisit every existing #include chain.  bootstrap, commands, exec,
input, and tui pick up the new client.h alongside their existing
ssh_server.h include.

ssh_server.c shrinks from 402 to 249 lines (-153).

Final per-module size after PR2:
  ssh_server.c   249  accept loop + ssh_server_init/start + host key
  bootstrap.c    493  per-connection SSH handshake / auth / channel
  client.c       162  client_t I/O + lifecycle + channel callbacks
  input.c        637  username read + handle_key + main loop +
                      notify_mentions + idle timeout
  commands.c     269  vim ':' command dispatcher
  exec.c         453  SSH exec subcommand dispatcher
  ratelimit.c    197  IP rate limit + connection counters
  tui.c          614  screen rendering
  chat_room.c    151  room + client list
  message.c      350  message log load/save/search
  utf8.c         250  UTF-8 width / validation
  common.c       133  buffer_*, env_int, is_valid_username,
                      sanitize_terminal_size, tnt_state_*
  main.c         109  process entry

Total: ~4 000 lines of C across 13 files, no file over 650 lines, every
file is single-purpose.  Behaviour is preserved: the migrated code is
byte-for-byte identical.
2026-05-17 10:16:27 +08:00

162 lines
4.3 KiB
C

#include "client.h"
#include "common.h"
#include <libssh/callbacks.h>
#include <libssh/libssh.h>
#include <libssh/server.h>
#include <pthread.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
/* Send data to client via SSH channel */
int client_send(client_t *client, const char *data, size_t len) {
size_t total = 0;
if (!client || !data) return -1;
pthread_mutex_lock(&client->io_lock);
if (!client->connected || !client->channel) {
pthread_mutex_unlock(&client->io_lock);
return -1;
}
while (total < len) {
size_t remaining = len - total;
uint32_t chunk = (remaining > 32768) ? 32768 : (uint32_t)remaining;
int sent = ssh_channel_write(client->channel, data + total, chunk);
if (sent <= 0) {
pthread_mutex_unlock(&client->io_lock);
return -1;
}
total += (size_t)sent;
}
pthread_mutex_unlock(&client->io_lock);
return 0;
}
void client_addref(client_t *client) {
if (!client) return;
pthread_mutex_lock(&client->ref_lock);
client->ref_count++;
pthread_mutex_unlock(&client->ref_lock);
}
void client_release(client_t *client) {
if (!client) return;
pthread_mutex_lock(&client->ref_lock);
client->ref_count--;
int count = client->ref_count;
pthread_mutex_unlock(&client->ref_lock);
if (count == 0) {
/* Safe to free now */
if (client->channel && client->channel_cb) {
ssh_remove_channel_callbacks(client->channel, client->channel_cb);
}
if (client->channel) {
ssh_channel_close(client->channel);
ssh_channel_free(client->channel);
}
if (client->session) {
ssh_disconnect(client->session);
ssh_free(client->session);
}
if (client->channel_cb) {
free(client->channel_cb);
}
pthread_mutex_destroy(&client->io_lock);
pthread_mutex_destroy(&client->ref_lock);
free(client);
}
}
/* Send formatted string to client */
int client_printf(client_t *client, const char *fmt, ...) {
char buffer[2048];
va_list args;
va_start(args, fmt);
int len = vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
/* Check for buffer overflow or encoding error */
if (len < 0 || len >= (int)sizeof(buffer)) {
return -1;
}
return client_send(client, buffer, len);
}
static int client_channel_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;
client_t *client = (client_t *)userdata;
if (!client) {
return SSH_ERROR;
}
int w = width;
int h = height;
sanitize_terminal_size(&w, &h);
client->width = w;
client->height = h;
client->redraw_pending = true;
return SSH_OK;
}
static void client_channel_eof(ssh_session session, ssh_channel channel,
void *userdata) {
(void)session;
(void)channel;
client_t *client = (client_t *)userdata;
if (client) {
client->connected = false;
}
}
static void client_channel_close(ssh_session session, ssh_channel channel,
void *userdata) {
(void)session;
(void)channel;
client_t *client = (client_t *)userdata;
if (client) {
client->connected = false;
}
}
int client_install_channel_callbacks(client_t *client) {
if (!client || !client->channel) {
return -1;
}
client->channel_cb = calloc(1, sizeof(struct ssh_channel_callbacks_struct));
if (!client->channel_cb) {
return -1;
}
ssh_callbacks_init(client->channel_cb);
client->channel_cb->userdata = client;
client->channel_cb->channel_eof_function = client_channel_eof;
client->channel_cb->channel_close_function = client_channel_close;
client->channel_cb->channel_pty_window_change_function =
client_channel_window_change;
if (ssh_set_channel_callbacks(client->channel, client->channel_cb) != SSH_OK) {
free(client->channel_cb);
client->channel_cb = NULL;
return -1;
}
return 0;
}