- @mention: typing @username in a message sends bell char to that user
and highlights the message content in bold yellow for them
- Idle timeout: disconnect inactive clients after TNT_IDLE_TIMEOUT
seconds (default 1800 = 30min, 0 to disable)
- :list now shows connection duration per user (e.g. "alice (12m)")
- Document all three features in help text, manpage, and README
Closes#46
Critical fixes:
- C-1: Use atomic_bool for client->connected and redraw_pending to prevent
data races between callback and main threads
- C-2: Add reference counting for channel callbacks to prevent use-after-free
when callbacks fire during client cleanup
- C-3/M-7: Use ssh_channel_read_timeout (5s) for UTF-8 continuation bytes
to prevent thread blocking and stream desynchronization
High-severity fixes:
- H-1: Replace non-thread-safe setenv/tzset with timegm() in parse_rfc3339_utc
- H-2: Change room_get_message to return by value copy instead of interior pointer
- H-3: Log warning when rate-limit table evicts active IP entry
- H-4: Replace strcmp with constant-time comparison for access token validation
- H-5: Check signature_state in auth_pubkey to reject unsigned key offers
Medium/low fixes:
- M-1: Replace all atoi() with strtol() for proper error detection
- M-3: Move calloc outside rwlock in tui_render_screen to avoid blocking writers
- M-8: Fix off-by-one in rate limit threshold (> to >=)
- M-9: Trim partial UTF-8 sequences after snprintf truncation in message_format
- L-1: Validate continuation byte mask (0xC0==0x80) in utf8_decode
- D-3: Remove vestigial client_t.fd field
- L-3: Remove unreachable pthread_attr_destroy after infinite loop
Fixes#10.
Five bugs that caused the server to crash or become unresponsive:
1. Signal handler deadlock (main.c)
signal_handler called room_destroy (pthread_rwlock + free) and printf —
neither is async-signal-safe. If SIGTERM arrived while any thread held
g_room->lock, the process deadlocked permanently.
Fix: handler now only writes a message via write(2) and calls _exit(0).
Also remove close(g_listen_fd) which was closing stdin (fd 0), since
ssh_server_init returns 0 on success, not a real file descriptor.
2. NULL dereference in room_broadcast when room is empty (chat_room.c)
calloc(0, n) may return NULL per POSIX; memcpy on NULL is undefined.
Also: no NULL check after calloc for the OOM case.
Fix: early return if count == 0; check calloc return value.
3. Stack buffer overflow in tui_render_screen (tui.c)
char buffer[8192] overflows with tall terminals: 197 visible lines *
~1031 bytes/message ≈ 203 KiB. Title padding loop also lacked a
bounds check (buffer[pos++] = ' ' with no guard).
Fix: switch to malloc(65536) with buf_size used consistently.
Add bounds check to the title padding loop.
4. sleep() inside libssh auth callback (ssh_server.c)
auth_password is called from ssh_event_dopoll in the main thread.
sleep(2) there blocks the entire accept loop — one attacker with
repeated wrong passwords stalls all incoming connections.
IP blocking via record_auth_failure already handles brute force.
Fix: remove sleep(2) from auth_password.
5. Spurious sleep() calls in the main accept loop (ssh_server.c)
sleep(1/2) after rejecting rate-limited or over-limit connections
delays accepting the next legitimate connection for no benefit.
Fix: remove all sleep() from the accept loop error paths.