Commit graph

23 commits

Author SHA1 Message Date
2610bba76d tui: dim date divider between messages from different days (M7-4)
When the rendered message snapshot crosses a date boundary, insert a
single dim grey divider line before the first message of each new day:

    ── 2026-05-14 ──────────────────────────────────────────
    16:00  alice: 早!
    16:01  bob: 早呀
    ── 2026-05-15 ──────────────────────────────────────────
    04:30  alice: 晚上一起吃饭?
    ...

The divider also fires for the *first* visible date, so the user always
knows what day the top of their viewport belongs to.

Dividers and messages share the fixed msg_height budget — dividers
don't push other content off the bottom.  In the worst case a viewport
with one message per day shows half the rows as dividers, which is the
intended trade for readability.

Real win for a long-lived public chat where messages span many days
and timestamps alone only show HH:MM.
2026-05-17 13:10:24 +08:00
fdef16ae8e tui: refined status / prompt line (M7-3)
NORMAL mode status line:
  before: "-- NORMAL -- (3/100)  ↓ 5 new"
  after : "[reverse-yellow NORMAL]  3 / 100   ▼ 5 new"
- the mode is now a small reverse-video yellow chip so it visually
  echoes the mode colour in the title bar
- position is dim grey with em-spaced slash, "▼" replaces "↓" so the
  unseen-message marker matches the same downward-triangle vocabulary
  the help screen uses

INSERT mode prompt:
  before: "> "
  after : "› " (U+203A, dim grey)
- single-glyph chevron is quieter than the ASCII ">", aligns with the
  thinner aesthetic of the new title bar
- applied both in tui_render_screen() and tui_render_input() so
  per-keystroke redraws match the initial paint

COMMAND mode prompt:
  before: ":foo"
  after : "[magenta :]foo"
- the leading colon picks up the COMMAND mode colour so it reads as
  "you are in command mode now" rather than as part of the typed text
2026-05-17 12:56:21 +08:00
e2b16cf477 tui: lighter, segmented title bar (M7-2)
Replace the full-width reverse-video title

    ▍ tester | 在线: 3 | 模式: INSERT | ? 帮助 ▎  (reversed)

with a single line of small chips on a normal background:

     tester · 在线 3 · INSERT                  ? 帮助

- username is bold white, online count is plain
- mode name carries its own colour, so glance + colour disambiguates
  it (INSERT cyan, NORMAL yellow, COMMAND magenta, HELP blue) without
  having to read the word
- separators are dim grey middle-dots so the eye groups segments
- mute marker shrinks from "[静音]" to a dim "静音" suffix when active
- hint "? 帮助" is right-aligned in dim grey, far from content

The horizontal rule below the chat already provides the visual divider
that the reverse-video bar used to provide, so the bar can drop the
heaviness without losing structure.
2026-05-17 12:54:27 +08:00
159073ec49 tui: framed welcome banner before username prompt (M7-1)
Replace the four lines of `==` rules + bilingual title with a centred
rounded box so the eye lands on a single visual element instead of two.

The new tui_render_welcome():
- centres horizontally and vertically (about a third of the way down)
- shows TNT + version, then 中文 subtitle, then English caption with
  decreasing visual weight (bold cyan / default / dim grey)
- falls back to a plain "TNT $version — anonymous chat over SSH" line
  when the terminal is too narrow for the frame
- declared in tui.h, called from input.c read_username()

read_username()'s prompt becomes a single short bilingual hint
("请输入用户名 (留空 anonymous): ") that lives below the box.
2026-05-17 10:48:27 +08:00
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
d9382882d1 chore: bug fixes and code cleanup
Fixes:
- message_load() now holds g_message_file_lock for the read, so :last [N]
  can no longer observe a half-written line while message_save() is
  flushing.
- constant_time_strcmp() accumulates the length difference in size_t.
  The old code truncated to unsigned char, which collapsed pairs whose
  lengths differed by a multiple of 256 down to 0 and lost the signal.

Refactor:
- buffer_appendf() / buffer_append_bytes() moved to common.c; the two
  identical copies in ssh_server.c and tui.c have been removed.

Docs / cleanup:
- README clarifies that exec 'post' uses the SSH login name as the
  author and that anonymous mode performs no identity check.
- Removed TODO.md (both items completed) and docs/README.old.
- Trimmed the auto-generated 2025 entry block from docs/CHANGELOG.md
  and added a 2026-05-16 entry summarising this change.
2026-05-16 22:44:41 +08:00
ed5fc43cbd feat: add :last, :search, :mute-joins commands and MOTD support
- :last [N]: show last N messages from log (max 50, default 10)
- :search <keyword>: case-insensitive full-text search across log history
- :mute-joins: per-client toggle to silence join/leave system messages,
  indicated by [静音] in the title bar
- MOTD: display motd.txt from state directory on connect before entering chat
- Add message_search() to message.c/h for log file scanning
- Update :help and tui help screen (EN/ZH) with new commands
2026-04-23 12:03:27 +08:00
bb77c77b8f feat: add @mention notifications, idle timeout, and online duration
- @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
2026-04-19 23:12:45 +08:00
ee3f89f10a feat: improve TUI visual experience with colors and indicators
- Color-code usernames using hash-based ANSI color assignment (6 colors)
- Style system messages (join/leave/rename) in gray with --> prefix
- Style /me action messages in italic cyan
- Show "↓ N new" indicator in yellow when scrolled up in NORMAL mode
- Compact timestamps from full date to HH:MM for more message space

Closes #44
2026-04-19 22:56:38 +08:00
b07348c6e1 docs: update all user-facing help text and documentation
- Add /me action command to all help surfaces (EN/ZH help screen,
  :help output, exec help, manpage, README)
- Add Ctrl+D/U/F/B page scrolling keys to help text (were only in manpage)
- Add :q/:quit/:exit disconnect command to help text
- Update README COMMAND mode section with all current commands
  (:nick, :msg, :w were missing)
- Remove redundant COMMAND MODE KEYS section from help text
  (merged into AVAILABLE COMMANDS for clarity)
- Compact help screen layout (j/k on one line, g/G on one line)
2026-04-19 22:08:59 +08:00
14789cd1c8 fix: guard terminal width/height in all TUI renderers and harden edge cases
- Replace all direct client->width/height reads with local variables
  clamped to minimums (width>=10, height>=4) across tui_render_screen,
  tui_render_input, tui_render_command_output, and tui_render_help
- Fix tui_render_input underflow when width < 3 (avail = max(rw-3, 1))
- Show username in title bar instead of generic label
- Add /me action message support in exec_command_post for scripting parity
- Reject empty usernames when loading messages from log file
2026-04-19 18:58:51 +08:00
e2990000e6 feat: add :nick command and /me action messages
- :nick/:name <name>: change username in-session with full validation,
  thread-safe update under write lock, and system broadcast
- /me <action>: IRC-style action messages displayed as "* user action"
- Updated help text (EN/ZH) and manpage with new commands

Closes #38
2026-04-19 18:34:02 +08:00
e10b43074c feat: consolidated improvements, manpage, and deployment prep
Bug fixes:
- Fix data race on client->width/height (now _Atomic int)
- Persist join/leave system messages via message_save()
- Make room_add_message static to enforce lock contract
- Fix execute_command mutating command_input directly
- Increase help_copy buffer from 4096 to 8192 for CJK safety

New features:
- Add :msg/:w whisper command for private messaging
- Add command history with UP/DOWN arrows in command mode
- Add Ctrl+D/U/F/B page scrolling in normal mode
- Add :q/:quit/:exit Vim-style disconnect

Unix community:
- Add tnt.1 manpage (roff format) with full documentation
- Add manpage install/uninstall to Makefile
2026-04-19 17:49:06 +08:00
9bbd5acd15 fix: resolve memory safety bugs and timing side-channel
- Fix use-after-free/double-free on install_client_channel_callbacks
  failure: nullify session/channel ownership before releasing refs so
  cleanup_failed_session does not double-free resources
- Fix constant_time_strcmp to always iterate over the full secret length,
  preventing timing leak of token length
- Fix data race on client->width/height by protecting window-change
  callback writes with io_lock
- Fix potential UTF-8 mid-sequence truncation in tui_render_input by
  sizing display buffer to MAX_MESSAGE_LEN
2026-04-19 14:08:31 +08:00
d745a8e1fe fix: address security vulnerabilities and design flaws from comprehensive audit
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
2026-04-15 10:13:17 +08:00
e473b26e0d refactor: stabilize SSH runtime and add exec interface 2026-03-10 18:52:20 +08:00
da81e17187 fix: resolve crash and hang causes found in production audit
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.
2026-03-06 01:58:56 +08:00
07fd7b1513
refactor: optimize rendering, log loading, and migrate to libssh callback API (#9)
This PR addresses critical performance bottlenecks, improves UX, and eliminates technical debt.

### Key Changes

**1. Performance Optimization:**
- **Startup**: Rewrote `message_load` to scan `messages.log` backwards from the end
  - Complexity reduced from O(FileSize) to O(MaxMessages)
  - Large log file startup: seconds → milliseconds
- **Rendering**: Optimized TUI rendering to use line clearing (`\033[K`) instead of full-screen clearing (`\033[2J`)
  - Eliminated visual flicker

**2. libssh API Migration:**
- Replaced deprecated message-based API with callback-based server implementation
- Removed `#pragma GCC diagnostic ignored "-Wdeprecated-declarations"`
- Ensures future libssh compatibility

**3. User Experience (Vim Mode):**
- Added `Ctrl+W` (Delete Word) and `Ctrl+U` (Delete Line) in Insert/Command modes
- Modified `Ctrl+C` behavior to safely switch modes instead of terminating connection
- Added support for `\n` as Enter key (fixing piped input issues)

**4. Project Structure:**
- Moved all test scripts to `tests/` directory
- Added `make test` target
- Updated CI/CD to run comprehensive test suite

### Verification
-  All tests passing (17/17)
-  CI passing on Ubuntu and macOS
-  AddressSanitizer clean
-  Valgrind clean (no memory leaks)
-  Zero compilation warnings

### Code Quality
**Rating:** 🟢 Good Taste
- Algorithm-driven optimization (not hacks)
- Simplified architecture (callback-based API)
- Zero breaking changes (all tests pass)
2026-02-07 23:17:55 +08:00
a5a62f057e fix(security): implement concurrency safety improvements
- Enhance room_broadcast() reference counting:
  * Check client state (connected, show_help, command_output) before rendering
  * Perform state check while holding client ref_lock
  * Prevents rendering to disconnected/invalid clients
  * Ensures safe cleanup when ref count reaches zero

- Fix tui_render_screen() message array TOCTOU:
  * Acquire all data (online count, message count, messages) in single lock
  * Create snapshot of messages to display
  * Calculate message range while holding lock
  * Render from snapshot without holding lock
  * Prevents inconsistencies from concurrent message additions
  * Eliminates race between two separate lock acquisitions

- Fix handle_key() scroll position TOCTOU:
  * Get message count atomically when calculating scroll bounds
  * Calculate max_scroll properly accounting for message height
  * Apply consistent bounds checking for 'j' (down) and 'G' (bottom)
  * Prevents out-of-bounds access from concurrent message changes

These changes address:
- Race condition in broadcast rendering to disconnecting clients
- TOCTOU between message count read and message access
- Scroll position bounds check race conditions

Prevents:
- Use-after-free in client cleanup
- Array out-of-bounds access
- Inconsistent UI rendering
- Crashes from concurrent message list modifications

Improves thread safety without introducing deadlocks by:
- Using snapshot approach to avoid long lock holds
- Acquiring data in consistent lock order
- Minimizing critical sections
2026-01-22 14:06:15 +08:00
298995aa53 Fix critical memory and concurrency bugs
Fixes three critical bugs that caused crashes after long-running:

1. Use-after-free race condition in room_broadcast()
   - Added reference counting to client_t structure
   - Increment ref_count before using client outside lock
   - Decrement and free only when ref_count reaches 0
   - Prevents accessing freed client memory during broadcast

2. strtok() data corruption in tui_render_command_output()
   - strtok() modifies original string by replacing delimiters
   - Now use a local copy before calling strtok()
   - Prevents corruption of client->command_output

3. Improved handle_key() consistency
   - Return bool to indicate if key was consumed
   - Fixes issue where mode-switch keys were processed twice

Thread safety changes:
- Added client->ref_count and client->ref_lock
- Added client_release() for safe cleanup
- room_broadcast() now properly increments/decrements refs

This fixes the primary cause of crashes during extended operation.
2025-11-30 09:00:00 +08:00
161fc904f3 Fix passwordless login and display alignment issues
- Allow SSH_AUTH_METHOD_NONE for passwordless authentication
- Replace all \n with \r\n in TUI rendering for proper line breaks
- Fixes messages appearing misaligned on terminal
2025-11-24 17:01:08 +08:00
a4d67be103 Replace telnet with SSH and fix full-screen display
- Implement SSH server using libssh for secure connections
- Replace insecure telnet with encrypted SSH protocol
- Add automatic terminal size detection via PTY requests
- Support dynamic window resize (SIGWINCH handling)
- Fix UI display bug by using SSH channel instead of fd
- Update tui_clear_screen to work with SSH connections
- Add RSA host key auto-generation on first run
- Update README with SSH instructions and security notes
- Add libssh dependency to Makefile with auto-detection
- Remove all telnet-related code

Security improvements:
- All traffic now encrypted
- Host key authentication
- No more plaintext transmission
2025-11-24 16:48:14 +08:00
63274b92ba Initial commit 2025-07-01 09:00:00 +08:00