mirror of
https://oauth2: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@github.com/m1ngsama/TNT.git
synced 2026-06-26 05:44:38 +08:00
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.
This commit is contained in:
parent
eead27544c
commit
d9382882d1
9 changed files with 87 additions and 267 deletions
|
|
@ -166,6 +166,8 @@ ssh -p 2222 operator@chat.m1ng.space post "service notice"
|
|||
ssh -p 2222 chat.m1ng.space post "/me deploys v2.0"
|
||||
```
|
||||
|
||||
**`post` identity**: the message is attributed to the SSH login name (the `user@` part of the URL, falling back to `anonymous`). In the default anonymous-access configuration there is no identity check, so any client can post as any name. Set `TNT_ACCESS_TOKEN` if you need authenticated posting.
|
||||
|
||||
## Development
|
||||
|
||||
### Building
|
||||
|
|
|
|||
12
TODO.md
12
TODO.md
|
|
@ -1,12 +0,0 @@
|
|||
# TODO
|
||||
|
||||
## Maintenance
|
||||
- [x] Replace deprecated `libssh` functions in `src/ssh_server.c`:
|
||||
- ~~`ssh_message_auth_password`~~ → `auth_password_function` callback (✓ completed)
|
||||
- ~~`ssh_message_channel_request_pty_width/height`~~ → `channel_pty_request_function` callback (✓ completed)
|
||||
- Migrated to callback-based server API as of libssh 0.9+
|
||||
|
||||
## Future Features
|
||||
- [x] Implement robust command handling for non-interactive SSH exec requests.
|
||||
- Basic exec support completed (handles `exit` command)
|
||||
- All tests passing
|
||||
|
|
@ -1,5 +1,22 @@
|
|||
# Changelog
|
||||
|
||||
## 2026-05-16 - Internal cleanup
|
||||
|
||||
### Fixed
|
||||
- `message_load()` now holds `g_message_file_lock` for the duration of the read.
|
||||
Previously `:last [N]` could race with `message_save()` and observe a
|
||||
half-written line.
|
||||
- `constant_time_strcmp()` accumulates the length difference in `size_t` instead
|
||||
of `unsigned char`. The old code lost the length-mismatch signal when the
|
||||
two lengths differed by a multiple of 256.
|
||||
|
||||
### Changed
|
||||
- `buffer_appendf()` and `buffer_append_bytes()` moved to `common.c`; the two
|
||||
identical copies in `ssh_server.c` and `tui.c` have been removed.
|
||||
- Removed `TODO.md` (both items completed) and `docs/README.old` (superseded by
|
||||
the root `README.md`).
|
||||
- Trimmed the auto-generated 2025 entry block from this changelog.
|
||||
|
||||
## 2026-04-23 - Chat UX Commands and MOTD
|
||||
|
||||
### Added
|
||||
|
|
@ -108,73 +125,3 @@ All changes maintain backward compatibility. Server remains open by default.
|
|||
### Changed
|
||||
- Improved error handling throughout
|
||||
- Better memory management in message loading
|
||||
|
||||
## 2025
|
||||
- Ongoing development and improvements
|
||||
- Bug fixes and optimizations
|
||||
- Feature enhancements
|
||||
- Optimize performance (2025-01-10)
|
||||
- Code cleanup (2025-01-15)
|
||||
- Code cleanup (2025-01-17)
|
||||
- Add minor improvements (2025-01-22)
|
||||
- Code cleanup (2025-01-28)
|
||||
- Fix edge cases (2025-02-03)
|
||||
- Update documentation (2025-02-06)
|
||||
- Fix edge cases (2025-02-07)
|
||||
- Add minor improvements (2025-02-26)
|
||||
- Update dependencies (2025-02-27)
|
||||
- Fix edge cases (2025-03-01)
|
||||
- Fix bugs and improve stability (2025-03-06)
|
||||
- Fix bugs and improve stability (2025-03-12)
|
||||
- Minor fixes (2025-03-17)
|
||||
- Add minor improvements (2025-03-18)
|
||||
- Refactor code structure (2025-03-24)
|
||||
- Update dependencies (2025-03-27)
|
||||
- Improve error handling (2025-03-28)
|
||||
- Improve error handling (2025-04-03)
|
||||
- Update documentation (2025-04-07)
|
||||
- Update documentation (2025-04-13)
|
||||
- Code cleanup (2025-04-15)
|
||||
- Fix bugs and improve stability (2025-04-16)
|
||||
- Add minor improvements (2025-04-17)
|
||||
- Minor fixes (2025-04-23)
|
||||
- Code cleanup (2025-04-24)
|
||||
- Fix edge cases (2025-04-25)
|
||||
- Refactor code structure (2025-05-13)
|
||||
- Fix edge cases (2025-05-14)
|
||||
- Minor fixes (2025-06-03)
|
||||
- Code cleanup (2025-06-05)
|
||||
- Add minor improvements (2025-06-10)
|
||||
- Fix bugs and improve stability (2025-06-18)
|
||||
- Update dependencies (2025-06-24)
|
||||
- Optimize performance (2025-06-30)
|
||||
- Update documentation (2025-07-07)
|
||||
- Refactor code structure (2025-07-17)
|
||||
- Fix bugs and improve stability (2025-07-19)
|
||||
- Refactor code structure (2025-07-21)
|
||||
- Code cleanup (2025-07-27)
|
||||
- Code cleanup (2025-08-04)
|
||||
- Minor fixes (2025-08-28)
|
||||
- Improve error handling (2025-09-05)
|
||||
- Update documentation (2025-09-09)
|
||||
- Code cleanup (2025-09-15)
|
||||
- Fix bugs and improve stability (2025-09-19)
|
||||
- Update documentation (2025-09-25)
|
||||
- Fix bugs and improve stability (2025-10-06)
|
||||
- Fix bugs and improve stability (2025-10-13)
|
||||
- Fix bugs and improve stability (2025-10-16)
|
||||
- Optimize performance (2025-10-17)
|
||||
- Add minor improvements (2025-10-22)
|
||||
- Code cleanup (2025-10-26)
|
||||
- Add minor improvements (2025-10-28)
|
||||
- Fix edge cases (2025-10-29)
|
||||
- Fix bugs and improve stability (2025-10-30)
|
||||
- Optimize performance (2025-11-04)
|
||||
- Improve error handling (2025-11-07)
|
||||
- Update documentation (2025-11-12)
|
||||
- Fix bugs and improve stability (2025-11-14)
|
||||
- Update documentation (2025-11-17)
|
||||
- Add minor improvements (2025-11-18)
|
||||
- Refactor code structure (2025-11-19)
|
||||
- Fix bugs and improve stability (2025-11-20)
|
||||
- Minor fixes (2025-11-24)
|
||||
|
|
|
|||
|
|
@ -1,98 +0,0 @@
|
|||
TNT(1) User Commands TNT(1)
|
||||
|
||||
NAME
|
||||
tnt - terminal chat server with vim-style interface
|
||||
|
||||
SYNOPSIS
|
||||
tnt [-p port] [-h]
|
||||
|
||||
DESCRIPTION
|
||||
TNT (TNT's Not Tunnel) is a lightweight SSH chat server.
|
||||
Supports vim-style navigation and message history browsing.
|
||||
|
||||
INSTALLATION
|
||||
curl -sSL https://raw.githubusercontent.com/m1ngsama/TNT/main/install.sh | sh
|
||||
|
||||
Or download binary from:
|
||||
https://github.com/m1ngsama/TNT/releases
|
||||
|
||||
USAGE
|
||||
Start server:
|
||||
tnt # Listen on port 2222
|
||||
tnt -p 3333 # Listen on port 3333
|
||||
PORT=3333 tnt # Same as above
|
||||
|
||||
Connect as client:
|
||||
ssh -p 2222 localhost
|
||||
|
||||
OPTIONS
|
||||
-p port Listen on specified port (default: 2222)
|
||||
-h Display help message
|
||||
|
||||
MODES
|
||||
INSERT Type and send messages (default mode)
|
||||
ESC Switch to NORMAL mode
|
||||
Enter Send message
|
||||
Backspace Delete character
|
||||
|
||||
NORMAL Browse message history
|
||||
i Return to INSERT mode
|
||||
: Enter COMMAND mode
|
||||
j/k Scroll down/up
|
||||
g/G Jump to top/bottom
|
||||
? Show help
|
||||
|
||||
COMMAND Execute commands
|
||||
:list List online users
|
||||
:help Show available commands
|
||||
ESC Return to NORMAL mode
|
||||
|
||||
ENVIRONMENT
|
||||
PORT Server port (default: 2222)
|
||||
|
||||
FILES
|
||||
messages.log Message history (append-only)
|
||||
host_key SSH host key (auto-generated)
|
||||
|
||||
EXAMPLES
|
||||
Start server on port 3000:
|
||||
PORT=3000 tnt
|
||||
|
||||
Connect and set username:
|
||||
ssh -p 2222 localhost
|
||||
# Enter username when prompted
|
||||
|
||||
Production deployment with systemd:
|
||||
sudo cp tnt.service /etc/systemd/system/
|
||||
sudo systemctl enable --now tnt
|
||||
|
||||
DIAGNOSTICS
|
||||
Build with debug symbols:
|
||||
make debug
|
||||
|
||||
Check for memory leaks:
|
||||
make asan
|
||||
ASAN_OPTIONS=detect_leaks=1 ./tnt
|
||||
|
||||
Run tests:
|
||||
./test_basic.sh
|
||||
./test_stress.sh 50 120
|
||||
|
||||
BUGS
|
||||
Report bugs at: https://github.com/m1ngsama/TNT/issues
|
||||
|
||||
SEE ALSO
|
||||
ssh(1), sshd(8)
|
||||
|
||||
Project documentation:
|
||||
HACKING Developer guide
|
||||
DEPLOYMENT.md Production setup
|
||||
CICD.md CI/CD workflow
|
||||
|
||||
AUTHOR
|
||||
Written by m1ng.
|
||||
|
||||
LICENSE
|
||||
MIT License. See LICENSE file for details.
|
||||
|
||||
TNT 1.0 December 2025 TNT(1)
|
||||
|
|
@ -54,4 +54,12 @@ const char* tnt_state_dir(void);
|
|||
int tnt_ensure_state_dir(void);
|
||||
int tnt_state_path(char *buffer, size_t buf_size, const char *filename);
|
||||
|
||||
/* Bounded string buffer builders. Both append to `buffer[*pos..]`, advance
|
||||
* `*pos`, and always keep the buffer NUL-terminated. They never write past
|
||||
* `buf_size - 1` and become no-ops once the buffer is full. */
|
||||
void buffer_append_bytes(char *buffer, size_t buf_size, size_t *pos,
|
||||
const char *data, size_t len);
|
||||
void buffer_appendf(char *buffer, size_t buf_size, size_t *pos,
|
||||
const char *fmt, ...);
|
||||
|
||||
#endif /* COMMON_H */
|
||||
|
|
|
|||
42
src/common.c
42
src/common.c
|
|
@ -1,5 +1,6 @@
|
|||
#include "common.h"
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#ifndef PATH_MAX
|
||||
|
|
@ -84,3 +85,44 @@ int tnt_state_path(char *buffer, size_t buf_size, const char *filename) {
|
|||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void buffer_append_bytes(char *buffer, size_t buf_size, size_t *pos,
|
||||
const char *data, size_t len) {
|
||||
size_t available;
|
||||
size_t to_copy;
|
||||
|
||||
if (!buffer || !pos || !data || len == 0 || buf_size == 0 ||
|
||||
*pos >= buf_size - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
available = (buf_size - 1) - *pos;
|
||||
to_copy = (len < available) ? len : available;
|
||||
memcpy(buffer + *pos, data, to_copy);
|
||||
*pos += to_copy;
|
||||
buffer[*pos] = '\0';
|
||||
}
|
||||
|
||||
void buffer_appendf(char *buffer, size_t buf_size, size_t *pos,
|
||||
const char *fmt, ...) {
|
||||
va_list args;
|
||||
int written;
|
||||
|
||||
if (!buffer || !pos || !fmt || buf_size == 0 || *pos >= buf_size - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
va_start(args, fmt);
|
||||
written = vsnprintf(buffer + *pos, buf_size - *pos, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
if (written < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((size_t)written >= buf_size - *pos) {
|
||||
*pos = buf_size - 1;
|
||||
} else {
|
||||
*pos += (size_t)written;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,9 @@ void message_init(void) {
|
|||
/* Nothing to initialize for now */
|
||||
}
|
||||
|
||||
/* Load messages from log file - Optimized for large files */
|
||||
/* Load messages from log file - Optimized for large files.
|
||||
* Holds g_message_file_lock for the duration of the read so concurrent
|
||||
* message_save() calls from chat threads cannot interleave a partial line. */
|
||||
int message_load(message_t **messages, int max_messages) {
|
||||
char log_path[PATH_MAX];
|
||||
|
||||
|
|
@ -46,9 +48,12 @@ int message_load(message_t **messages, int max_messages) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_message_file_lock);
|
||||
|
||||
FILE *fp = fopen(log_path, "r");
|
||||
if (!fp) {
|
||||
/* File doesn't exist yet, no messages */
|
||||
pthread_mutex_unlock(&g_message_file_lock);
|
||||
*messages = msg_array;
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -56,6 +61,7 @@ int message_load(message_t **messages, int max_messages) {
|
|||
/* Seek to end */
|
||||
if (fseek(fp, 0, SEEK_END) != 0) {
|
||||
fclose(fp);
|
||||
pthread_mutex_unlock(&g_message_file_lock);
|
||||
*messages = msg_array;
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -63,6 +69,7 @@ int message_load(message_t **messages, int max_messages) {
|
|||
long file_size = ftell(fp);
|
||||
if (file_size <= 0) {
|
||||
fclose(fp);
|
||||
pthread_mutex_unlock(&g_message_file_lock);
|
||||
*messages = msg_array;
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -175,6 +182,7 @@ read_messages:;
|
|||
}
|
||||
|
||||
fclose(fp);
|
||||
pthread_mutex_unlock(&g_message_file_lock);
|
||||
*messages = msg_array;
|
||||
return count;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,60 +68,24 @@ static int g_rate_limit_enabled = 1;
|
|||
static char g_access_token[256] = "";
|
||||
static int g_idle_timeout = DEFAULT_IDLE_TIMEOUT;
|
||||
|
||||
static void buffer_appendf(char *buffer, size_t buf_size, size_t *pos,
|
||||
const char *fmt, ...) {
|
||||
va_list args;
|
||||
int written;
|
||||
|
||||
if (!buffer || !pos || !fmt || buf_size == 0 || *pos >= buf_size - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
va_start(args, fmt);
|
||||
written = vsnprintf(buffer + *pos, buf_size - *pos, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
if (written < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((size_t)written >= buf_size - *pos) {
|
||||
*pos = buf_size - 1;
|
||||
} else {
|
||||
*pos += (size_t)written;
|
||||
}
|
||||
}
|
||||
|
||||
static void buffer_append_bytes(char *buffer, size_t buf_size, size_t *pos,
|
||||
const char *data, size_t len) {
|
||||
size_t available;
|
||||
size_t to_copy;
|
||||
|
||||
if (!buffer || !pos || !data || len == 0 || buf_size == 0 ||
|
||||
*pos >= buf_size - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
available = (buf_size - 1) - *pos;
|
||||
to_copy = (len < available) ? len : available;
|
||||
memcpy(buffer + *pos, data, to_copy);
|
||||
*pos += to_copy;
|
||||
buffer[*pos] = '\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. */
|
||||
* 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 unsigned char result = (unsigned char)(len_a ^ len_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;
|
||||
result |= ca ^ (unsigned char)b[i];
|
||||
byte_diff |= ca ^ (unsigned char)b[i];
|
||||
}
|
||||
return result == 0;
|
||||
return length_diff == 0 && byte_diff == 0;
|
||||
}
|
||||
|
||||
/* Safe integer parse from environment variable; returns fallback on error. */
|
||||
|
|
|
|||
41
src/tui.c
41
src/tui.c
|
|
@ -2,7 +2,6 @@
|
|||
#include "ssh_server.h"
|
||||
#include "chat_room.h"
|
||||
#include "utf8.h"
|
||||
#include <stdarg.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static bool is_join_leave_msg(const message_t *msg) {
|
||||
|
|
@ -111,46 +110,6 @@ static void format_message_colored(const message_t *msg, char *buffer,
|
|||
}
|
||||
}
|
||||
|
||||
static void buffer_append_bytes(char *buffer, size_t buf_size, size_t *pos,
|
||||
const char *data, size_t len) {
|
||||
size_t available;
|
||||
size_t to_copy;
|
||||
|
||||
if (!buffer || !pos || !data || len == 0 || buf_size == 0 || *pos >= buf_size - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
available = (buf_size - 1) - *pos;
|
||||
to_copy = (len < available) ? len : available;
|
||||
memcpy(buffer + *pos, data, to_copy);
|
||||
*pos += to_copy;
|
||||
buffer[*pos] = '\0';
|
||||
}
|
||||
|
||||
static void buffer_appendf(char *buffer, size_t buf_size, size_t *pos,
|
||||
const char *fmt, ...) {
|
||||
va_list args;
|
||||
int written;
|
||||
|
||||
if (!buffer || !pos || !fmt || buf_size == 0 || *pos >= buf_size - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
va_start(args, fmt);
|
||||
written = vsnprintf(buffer + *pos, buf_size - *pos, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
if (written < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((size_t)written >= buf_size - *pos) {
|
||||
*pos = buf_size - 1;
|
||||
} else {
|
||||
*pos += (size_t)written;
|
||||
}
|
||||
}
|
||||
|
||||
/* Clear the screen */
|
||||
void tui_clear_screen(client_t *client) {
|
||||
if (!client || !client->connected) return;
|
||||
|
|
|
|||
Loading…
Reference in a new issue