Move IP rate-limiting, auth-failure tracking, and global connection
counting out of ssh_server.c into a dedicated module.
New API (include/ratelimit.h):
- ratelimit_init()
- ratelimit_check_ip() / ratelimit_release_ip()
- ratelimit_record_auth_failure()
- ratelimit_check_and_increment_total() / ratelimit_decrement_total()
- ratelimit_get_active_total() (replaces the direct g_total_connections
read that exec_command_stats was doing under g_conn_count_lock)
env_int() also moves up to common.{c,h} since multiple modules need it.
ssh_server.c drops from 2469 to 2200 lines. Behaviour is preserved:
the new functions are byte-for-byte the same implementations, only the
file boundary moved.
g_idle_timeout and g_access_token reads stay inline in ssh_server_init()
for now; they will follow the auth.c and input.c extractions later.
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.