533 lines
15 KiB
C
533 lines
15 KiB
C
#define _GNU_SOURCE
|
|
|
|
#include <arpa/inet.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
#include <sys/un.h>
|
|
#include <sys/wait.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#define VALID_MNEMONIC "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
|
#define LONG_INPUT_LEN 1024
|
|
|
|
static int g_failures = 0;
|
|
|
|
static void check_condition(const char *name, int condition) {
|
|
if (condition) {
|
|
printf("PASS: %s\n", name);
|
|
} else {
|
|
printf("FAIL: %s\n", name);
|
|
g_failures++;
|
|
}
|
|
}
|
|
|
|
static int sleep_ms(int ms) {
|
|
struct timespec ts;
|
|
ts.tv_sec = ms / 1000;
|
|
ts.tv_nsec = (long)(ms % 1000) * 1000000L;
|
|
return nanosleep(&ts, NULL);
|
|
}
|
|
|
|
static int write_full(int fd, const void *buf, size_t len) {
|
|
const unsigned char *p = (const unsigned char *)buf;
|
|
size_t off = 0;
|
|
|
|
while (off < len) {
|
|
ssize_t n = write(fd, p + off, len - off);
|
|
if (n < 0) {
|
|
if (errno == EINTR) {
|
|
continue;
|
|
}
|
|
return -1;
|
|
}
|
|
off += (size_t)n;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int connect_socket_retry(const char *name, int timeout_ms) {
|
|
int elapsed = 0;
|
|
|
|
while (elapsed < timeout_ms) {
|
|
int fd;
|
|
struct sockaddr_un addr;
|
|
socklen_t addr_len;
|
|
|
|
fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
if (fd < 0) {
|
|
return -1;
|
|
}
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sun_family = AF_UNIX;
|
|
addr.sun_path[0] = '\0';
|
|
strncpy(&addr.sun_path[1], name, sizeof(addr.sun_path) - 2);
|
|
addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
|
|
addr_len = (socklen_t)(sizeof(sa_family_t) + 1 + strlen(name));
|
|
|
|
if (connect(fd, (struct sockaddr *)&addr, addr_len) == 0) {
|
|
return fd;
|
|
}
|
|
|
|
close(fd);
|
|
sleep_ms(100);
|
|
elapsed += 100;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static pid_t spawn_with_stdin_and_fd(const char *const argv[],
|
|
const char *stdin_data,
|
|
int close_stdin_after_write,
|
|
const char *fd_data,
|
|
int fd_num,
|
|
int close_fd_after_write,
|
|
int *stdin_write_end,
|
|
int *fd_write_end,
|
|
int *capture_read_end) {
|
|
int stdin_pipe[2] = {-1, -1};
|
|
int fd_pipe[2] = {-1, -1};
|
|
int capture_pipe[2] = {-1, -1};
|
|
pid_t child;
|
|
|
|
if (pipe(stdin_pipe) != 0) {
|
|
return -1;
|
|
}
|
|
if (pipe(capture_pipe) != 0) {
|
|
close(stdin_pipe[0]);
|
|
close(stdin_pipe[1]);
|
|
return -1;
|
|
}
|
|
if (fd_data != NULL) {
|
|
if (pipe(fd_pipe) != 0) {
|
|
close(stdin_pipe[0]);
|
|
close(stdin_pipe[1]);
|
|
close(capture_pipe[0]);
|
|
close(capture_pipe[1]);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
child = fork();
|
|
if (child < 0) {
|
|
close(stdin_pipe[0]);
|
|
close(stdin_pipe[1]);
|
|
close(capture_pipe[0]);
|
|
close(capture_pipe[1]);
|
|
if (fd_pipe[0] >= 0) close(fd_pipe[0]);
|
|
if (fd_pipe[1] >= 0) close(fd_pipe[1]);
|
|
return -1;
|
|
}
|
|
|
|
if (child == 0) {
|
|
dup2(stdin_pipe[0], STDIN_FILENO);
|
|
dup2(capture_pipe[1], STDOUT_FILENO);
|
|
dup2(capture_pipe[1], STDERR_FILENO);
|
|
|
|
close(stdin_pipe[0]);
|
|
close(stdin_pipe[1]);
|
|
close(capture_pipe[0]);
|
|
close(capture_pipe[1]);
|
|
|
|
if (fd_data != NULL) {
|
|
dup2(fd_pipe[0], fd_num);
|
|
close(fd_pipe[0]);
|
|
close(fd_pipe[1]);
|
|
}
|
|
|
|
execv("./build/nsigner", (char *const *)argv);
|
|
_exit(127);
|
|
}
|
|
|
|
close(stdin_pipe[0]);
|
|
close(capture_pipe[1]);
|
|
|
|
if (fd_data != NULL) {
|
|
close(fd_pipe[0]);
|
|
}
|
|
|
|
if (stdin_data != NULL) {
|
|
(void)write_full(stdin_pipe[1], stdin_data, strlen(stdin_data));
|
|
if (close_stdin_after_write) {
|
|
close(stdin_pipe[1]);
|
|
stdin_pipe[1] = -1;
|
|
}
|
|
}
|
|
|
|
if (fd_data != NULL) {
|
|
(void)write_full(fd_pipe[1], fd_data, strlen(fd_data));
|
|
if (close_fd_after_write) {
|
|
close(fd_pipe[1]);
|
|
fd_pipe[1] = -1;
|
|
}
|
|
}
|
|
|
|
if (stdin_write_end != NULL) {
|
|
*stdin_write_end = stdin_pipe[1];
|
|
} else if (stdin_pipe[1] >= 0) {
|
|
close(stdin_pipe[1]);
|
|
}
|
|
|
|
if (fd_write_end != NULL) {
|
|
*fd_write_end = fd_pipe[1];
|
|
} else if (fd_pipe[1] >= 0) {
|
|
close(fd_pipe[1]);
|
|
}
|
|
|
|
if (capture_read_end != NULL) {
|
|
*capture_read_end = capture_pipe[0];
|
|
} else {
|
|
close(capture_pipe[0]);
|
|
}
|
|
|
|
return child;
|
|
}
|
|
|
|
static int read_capture_nonblocking(int fd, char *buf, size_t buf_sz) {
|
|
ssize_t n;
|
|
if (buf == NULL || buf_sz == 0) {
|
|
return -1;
|
|
}
|
|
n = read(fd, buf, buf_sz - 1);
|
|
if (n < 0) {
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
buf[0] = '\0';
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
buf[n] = '\0';
|
|
return 0;
|
|
}
|
|
|
|
static int wait_exit_timeout(pid_t pid, int timeout_ms, int *status) {
|
|
int elapsed = 0;
|
|
while (elapsed < timeout_ms) {
|
|
pid_t rc = waitpid(pid, status, WNOHANG);
|
|
if (rc == pid) {
|
|
return 0;
|
|
}
|
|
if (rc < 0) {
|
|
return -1;
|
|
}
|
|
sleep_ms(50);
|
|
elapsed += 50;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void test_stdin_happy_path(void) {
|
|
const char *const argv[] = {
|
|
"./build/nsigner", "--socket-name", "nsigner_test_mi_stdin_ok", "--mnemonic-stdin", NULL
|
|
};
|
|
int cap_fd = -1;
|
|
int status = 0;
|
|
int fd;
|
|
pid_t child = spawn_with_stdin_and_fd(argv,
|
|
VALID_MNEMONIC "\n",
|
|
1,
|
|
NULL,
|
|
-1,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
&cap_fd);
|
|
|
|
check_condition("spawn --mnemonic-stdin happy-path child", child > 0);
|
|
if (child <= 0) {
|
|
return;
|
|
}
|
|
|
|
fd = connect_socket_retry("nsigner_test_mi_stdin_ok", 5000);
|
|
check_condition("--mnemonic-stdin reaches running server", fd >= 0);
|
|
if (fd >= 0) {
|
|
close(fd);
|
|
}
|
|
|
|
(void)kill(child, SIGTERM);
|
|
(void)waitpid(child, &status, 0);
|
|
if (cap_fd >= 0) {
|
|
close(cap_fd);
|
|
}
|
|
}
|
|
|
|
static void test_stdin_eof_without_newline(void) {
|
|
const char *const argv[] = {
|
|
"./build/nsigner", "--socket-name", "nsigner_test_mi_stdin_eof", "--mnemonic-stdin", NULL
|
|
};
|
|
int cap_fd = -1;
|
|
int status = 0;
|
|
int fd;
|
|
pid_t child = spawn_with_stdin_and_fd(argv,
|
|
VALID_MNEMONIC,
|
|
1,
|
|
NULL,
|
|
-1,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
&cap_fd);
|
|
|
|
check_condition("spawn --mnemonic-stdin EOF-without-newline child", child > 0);
|
|
if (child <= 0) {
|
|
return;
|
|
}
|
|
|
|
fd = connect_socket_retry("nsigner_test_mi_stdin_eof", 5000);
|
|
check_condition("--mnemonic-stdin EOF without newline accepted", fd >= 0);
|
|
if (fd >= 0) {
|
|
close(fd);
|
|
}
|
|
|
|
(void)kill(child, SIGTERM);
|
|
(void)waitpid(child, &status, 0);
|
|
if (cap_fd >= 0) {
|
|
close(cap_fd);
|
|
}
|
|
}
|
|
|
|
static void test_stdin_invalid_mnemonic(void) {
|
|
const char *const argv[] = {
|
|
"./build/nsigner", "--mnemonic-stdin", NULL
|
|
};
|
|
int cap_fd = -1;
|
|
int status = 0;
|
|
char cap[512];
|
|
pid_t child = spawn_with_stdin_and_fd(argv,
|
|
"not a valid mnemonic\n",
|
|
1,
|
|
NULL,
|
|
-1,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
&cap_fd);
|
|
|
|
check_condition("spawn --mnemonic-stdin invalid child", child > 0);
|
|
if (child <= 0) {
|
|
return;
|
|
}
|
|
|
|
check_condition("--mnemonic-stdin invalid exits", wait_exit_timeout(child, 2000, &status) == 0);
|
|
check_condition("--mnemonic-stdin invalid exits non-zero", WIFEXITED(status) && WEXITSTATUS(status) != 0);
|
|
|
|
if (cap_fd >= 0) {
|
|
(void)read_capture_nonblocking(cap_fd, cap, sizeof(cap));
|
|
check_condition("--mnemonic-stdin invalid emits validation failure", strstr(cap, "mnemonic validation failed") != NULL);
|
|
close(cap_fd);
|
|
}
|
|
}
|
|
|
|
static void test_stdin_overflow(void) {
|
|
const char *const argv[] = {
|
|
"./build/nsigner", "--mnemonic-stdin", NULL
|
|
};
|
|
char long_input[LONG_INPUT_LEN + 2];
|
|
int i;
|
|
int cap_fd = -1;
|
|
int status = 0;
|
|
char cap[512];
|
|
pid_t child;
|
|
|
|
for (i = 0; i < LONG_INPUT_LEN; ++i) {
|
|
long_input[i] = 'a';
|
|
}
|
|
long_input[LONG_INPUT_LEN] = '\n';
|
|
long_input[LONG_INPUT_LEN + 1] = '\0';
|
|
|
|
child = spawn_with_stdin_and_fd(argv,
|
|
long_input,
|
|
1,
|
|
NULL,
|
|
-1,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
&cap_fd);
|
|
|
|
check_condition("spawn --mnemonic-stdin overflow child", child > 0);
|
|
if (child <= 0) {
|
|
return;
|
|
}
|
|
|
|
check_condition("--mnemonic-stdin overflow exits", wait_exit_timeout(child, 2000, &status) == 0);
|
|
check_condition("--mnemonic-stdin overflow exits non-zero", WIFEXITED(status) && WEXITSTATUS(status) != 0);
|
|
|
|
if (cap_fd >= 0) {
|
|
(void)read_capture_nonblocking(cap_fd, cap, sizeof(cap));
|
|
check_condition("--mnemonic-stdin overflow emits length error", strstr(cap, "exceeded maximum length") != NULL);
|
|
close(cap_fd);
|
|
}
|
|
}
|
|
|
|
static void test_fd_happy_path(void) {
|
|
const char *const argv[] = {
|
|
"./build/nsigner", "--socket-name", "nsigner_test_mi_fd_ok", "--mnemonic-fd", "3", NULL
|
|
};
|
|
int cap_fd = -1;
|
|
int status = 0;
|
|
int fd;
|
|
pid_t child = spawn_with_stdin_and_fd(argv,
|
|
"",
|
|
0,
|
|
VALID_MNEMONIC "\n",
|
|
3,
|
|
1,
|
|
NULL,
|
|
NULL,
|
|
&cap_fd);
|
|
|
|
check_condition("spawn --mnemonic-fd happy-path child", child > 0);
|
|
if (child <= 0) {
|
|
return;
|
|
}
|
|
|
|
fd = connect_socket_retry("nsigner_test_mi_fd_ok", 5000);
|
|
check_condition("--mnemonic-fd reaches running server", fd >= 0);
|
|
if (fd >= 0) {
|
|
close(fd);
|
|
}
|
|
|
|
(void)kill(child, SIGTERM);
|
|
(void)waitpid(child, &status, 0);
|
|
if (cap_fd >= 0) {
|
|
close(cap_fd);
|
|
}
|
|
}
|
|
|
|
static void test_mutual_exclusion_and_validation_errors(void) {
|
|
struct {
|
|
const char *name;
|
|
const char *const *argv;
|
|
const char *expect_substr;
|
|
} cases[] = {
|
|
{
|
|
"mutual exclusion stdin+fd",
|
|
(const char *const[]){"./build/nsigner", "--mnemonic-stdin", "--mnemonic-fd", "3", NULL},
|
|
"mutually exclusive"
|
|
},
|
|
{
|
|
"stdin with stdio listener",
|
|
(const char *const[]){"./build/nsigner", "--listen", "stdio", "--mnemonic-stdin", NULL},
|
|
"requires --listen unix or --listen tcp"
|
|
},
|
|
{
|
|
"mnemonic-fd 1 rejected",
|
|
(const char *const[]){"./build/nsigner", "--mnemonic-fd", "1", NULL},
|
|
"--mnemonic-fd 1 is not allowed"
|
|
},
|
|
{
|
|
"mnemonic-fd 0 rejected with stdio",
|
|
(const char *const[]){"./build/nsigner", "--listen", "stdio", "--mnemonic-fd", "0", NULL},
|
|
"cannot be used with --listen stdio"
|
|
}
|
|
};
|
|
|
|
size_t i;
|
|
for (i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) {
|
|
int cap_fd = -1;
|
|
int status = 0;
|
|
char cap[512];
|
|
pid_t child = spawn_with_stdin_and_fd(cases[i].argv,
|
|
"",
|
|
1,
|
|
NULL,
|
|
-1,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
&cap_fd);
|
|
|
|
check_condition(cases[i].name, child > 0);
|
|
if (child <= 0) {
|
|
continue;
|
|
}
|
|
|
|
check_condition("expected parse-time failure exits", wait_exit_timeout(child, 2000, &status) == 0);
|
|
check_condition("expected parse-time failure non-zero", WIFEXITED(status) && WEXITSTATUS(status) != 0);
|
|
|
|
if (cap_fd >= 0) {
|
|
(void)read_capture_nonblocking(cap_fd, cap, sizeof(cap));
|
|
check_condition("expected parse-time error message", strstr(cap, cases[i].expect_substr) != NULL);
|
|
close(cap_fd);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void test_lock_reunlock_falls_back_to_tui_after_fd_startup(void) {
|
|
const char *const argv[] = {
|
|
"./build/nsigner", "--socket-name", "nsigner_test_mi_lock", "--mnemonic-fd", "3", NULL
|
|
};
|
|
int stdin_write = -1;
|
|
int cap_fd = -1;
|
|
int status = 0;
|
|
int fd;
|
|
pid_t child = spawn_with_stdin_and_fd(argv,
|
|
"",
|
|
0,
|
|
VALID_MNEMONIC "\n",
|
|
3,
|
|
1,
|
|
&stdin_write,
|
|
NULL,
|
|
&cap_fd);
|
|
|
|
check_condition("spawn --mnemonic-fd lock test child", child > 0);
|
|
if (child <= 0) {
|
|
return;
|
|
}
|
|
|
|
fd = connect_socket_retry("nsigner_test_mi_lock", 5000);
|
|
check_condition("--mnemonic-fd lock test initial running server", fd >= 0);
|
|
if (fd >= 0) {
|
|
close(fd);
|
|
}
|
|
|
|
(void)write_full(stdin_write, "l\n" VALID_MNEMONIC "\n", strlen("l\n" VALID_MNEMONIC "\n"));
|
|
sleep_ms(800);
|
|
|
|
fd = connect_socket_retry("nsigner_test_mi_lock", 2000);
|
|
check_condition("lock+reunlock via TUI keeps server alive", fd >= 0);
|
|
if (fd >= 0) {
|
|
close(fd);
|
|
}
|
|
|
|
if (stdin_write >= 0) {
|
|
close(stdin_write);
|
|
}
|
|
|
|
(void)kill(child, SIGTERM);
|
|
(void)waitpid(child, &status, 0);
|
|
if (cap_fd >= 0) {
|
|
close(cap_fd);
|
|
}
|
|
}
|
|
|
|
int main(void) {
|
|
(void)signal(SIGPIPE, SIG_IGN);
|
|
|
|
test_stdin_happy_path();
|
|
test_stdin_eof_without_newline();
|
|
test_stdin_invalid_mnemonic();
|
|
test_stdin_overflow();
|
|
test_fd_happy_path();
|
|
test_mutual_exclusion_and_validation_errors();
|
|
test_lock_reunlock_falls_back_to_tui_after_fd_startup();
|
|
|
|
if (g_failures == 0) {
|
|
printf("ALL TESTS PASSED\n");
|
|
return 0;
|
|
}
|
|
|
|
printf("TESTS FAILED: %d\n", g_failures);
|
|
return 1;
|
|
}
|