Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,21 @@ jobs:
- name: Install dependencies (macOS)
if: runner.os == 'macOS'
run: |
brew install libssh
brew install libssh coreutils

- name: Build
run: make

- name: Build with AddressSanitizer
run: make asan

- name: Run basic tests
- name: Run comprehensive tests
run: |
timeout 10 ./tnt &
sleep 2
pkill tnt || true
make test
cd tests
./test_security_features.sh
# Skipping anonymous access test in CI as it requires interactive pty handling which might be flaky
# ./test_anonymous_access.sh

- name: Check for memory leaks
if: runner.os == 'Linux'
Expand Down
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ $(OBJ_DIR):

clean:
rm -rf $(OBJ_DIR) $(TARGET)
rm -f tests/*.log tests/host_key* tests/messages.log
@echo "Clean complete"

install: $(TARGET)
Expand Down Expand Up @@ -69,6 +70,11 @@ check:
@command -v cppcheck >/dev/null 2>&1 && cppcheck --enable=warning,performance --quiet src/ || echo "cppcheck not installed"
@command -v clang-tidy >/dev/null 2>&1 && clang-tidy src/*.c -- -Iinclude $(INCLUDES) || echo "clang-tidy not installed"

# Test
test: all
@echo "Running tests..."
@cd tests && ./test_basic.sh

# Show build info
info:
@echo "Compiler: $(CC)"
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ tnt.service systemd unit
## Test

```sh
./test_basic.sh # functional
./test_stress.sh 50 # 50 clients
make test # run comprehensive test suite
# Individual tests are in tests/ directory
```

## Docs
Expand Down
12 changes: 12 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# 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
3 changes: 3 additions & 0 deletions include/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
#include <time.h>
#include <pthread.h>

/* Project Metadata */
#define TNT_VERSION "1.0.0"

/* Configuration constants */
#define DEFAULT_PORT 2222
#define MAX_MESSAGES 100
Expand Down
1 change: 1 addition & 0 deletions include/ssh_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ typedef struct client {
bool show_help;
char command_input[256];
char command_output[2048];
char exec_command[256];
pthread_t thread;
bool connected;
int ref_count; /* Reference count for safe cleanup */
Expand Down
3 changes: 3 additions & 0 deletions include/utf8.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ int utf8_strlen(const char *str);
/* Remove last UTF-8 character from string */
void utf8_remove_last_char(char *str);

/* Remove last word from string (mimic Ctrl+W) */
void utf8_remove_last_word(char *str);

/* Validate a UTF-8 byte sequence */
bool utf8_is_valid_sequence(const char *bytes, int len);

Expand Down
93 changes: 55 additions & 38 deletions src/message.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ void message_init(void) {
/* Nothing to initialize for now */
}

/* Load messages from log file */
/* Load messages from log file - Optimized for large files */
int message_load(message_t **messages, int max_messages) {
/* Always allocate the message array */
message_t *msg_array = calloc(max_messages, sizeof(message_t));
Expand All @@ -23,56 +23,75 @@ int message_load(message_t **messages, int max_messages) {
return 0;
}

char line[2048];
int count = 0;
/* Seek to end */
if (fseek(fp, 0, SEEK_END) != 0) {
fclose(fp);
*messages = msg_array;
return 0;
}

/* Use a ring buffer approach - keep only last max_messages */
/* First, count total lines and seek to appropriate position */
/* Use dynamic allocation to handle large log files */
long *file_pos = NULL;
int pos_capacity = 1000;
int line_count = 0;
int start_index = 0;

/* Allocate initial position array */
file_pos = malloc(pos_capacity * sizeof(long));
if (!file_pos) {
long file_size = ftell(fp);
if (file_size == 0) {
fclose(fp);
*messages = msg_array;
return 0;
}

/* Record file positions */
while (fgets(line, sizeof(line), fp)) {
/* Expand array if needed */
if (line_count >= pos_capacity) {
int new_capacity = pos_capacity * 2;
long *new_pos = realloc(file_pos, new_capacity * sizeof(long));
if (!new_pos) {
/* Out of memory, stop scanning */
break;
}
file_pos = new_pos;
pos_capacity = new_capacity;
/* Scan backwards to find the start position */
int newlines_found = 0;
long pos = file_size - 1;
/* Skip the very last byte if it's a newline */
if (pos >= 0) {
/* Read last char */
fseek(fp, pos, SEEK_SET);
if (fgetc(fp) == '\n') {
pos--;
}
file_pos[line_count++] = ftell(fp) - strlen(line);
}

/* Determine where to start reading */
if (line_count > max_messages) {
start_index = line_count - max_messages;
fseek(fp, file_pos[start_index], SEEK_SET);
} else {
fseek(fp, 0, SEEK_SET);
start_index = 0;
/* Read backwards in chunks for performance */
#define CHUNK_SIZE 4096
char chunk[CHUNK_SIZE];

while (pos >= 0 && newlines_found < max_messages) {
long read_size = (pos >= CHUNK_SIZE) ? CHUNK_SIZE : (pos + 1);
long read_pos = pos - read_size + 1;

fseek(fp, read_pos, SEEK_SET);
if (fread(chunk, 1, read_size, fp) != (size_t)read_size) {
break;
}

/* Scan chunk backwards */
for (int i = read_size - 1; i >= 0; i--) {
if (chunk[i] == '\n') {
newlines_found++;
if (newlines_found >= max_messages) {
/* Found our start point: one char after this newline */
fseek(fp, read_pos + i + 1, SEEK_SET);
goto read_messages;
}
}
}

pos -= read_size;
}

/* If we got here, we reached start of file or didn't find enough newlines */
fseek(fp, 0, SEEK_SET);

read_messages:;
char line[2048];
int count = 0;

/* Now read the messages */
/* Now read forward */
while (fgets(line, sizeof(line), fp) && count < max_messages) {
/* Check for oversized lines */
size_t line_len = strlen(line);
if (line_len >= sizeof(line) - 1) {
fprintf(stderr, "Warning: Skipping oversized line in messages.log\n");
/* Skip remainder of line */
int c;
while ((c = fgetc(fp)) != '\n' && c != EOF);
continue;
}

Expand Down Expand Up @@ -109,7 +128,6 @@ int message_load(message_t **messages, int max_messages) {
time_t msg_time = mktime(&tm);
time_t now = time(NULL);
if (msg_time > now + 86400 || msg_time < now - 31536000 * 10) {
/* Skip messages more than 1 day in future or 10 years in past */
continue;
}

Expand All @@ -121,7 +139,6 @@ int message_load(message_t **messages, int max_messages) {
count++;
}

free(file_pos);
fclose(fp);
*messages = msg_array;
return count;
Expand Down
Loading