v0.0.27 - Refine TUI login flow, fix menu hotkey underlining, and pin connection info header
This commit is contained in:
33
run.sh
Executable file
33
run.sh
Executable file
@@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
BUILD_SCRIPT="$SCRIPT_DIR/build_static.sh"
|
||||||
|
BUILD_DIR="$SCRIPT_DIR/build"
|
||||||
|
|
||||||
|
HOST_UNAME="$(uname -m)"
|
||||||
|
case "$HOST_UNAME" in
|
||||||
|
x86_64)
|
||||||
|
OUTPUT_NAME="nsigner_static_x86_64"
|
||||||
|
;;
|
||||||
|
aarch64|arm64)
|
||||||
|
OUTPUT_NAME="nsigner_static_arm64"
|
||||||
|
;;
|
||||||
|
armv7l|armv7)
|
||||||
|
OUTPUT_NAME="nsigner_static_armv7"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "ERROR: Unsupported host architecture '$HOST_UNAME'"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
"$BUILD_SCRIPT"
|
||||||
|
|
||||||
|
BINARY_PATH="$BUILD_DIR/$OUTPUT_NAME"
|
||||||
|
if [[ ! -x "$BINARY_PATH" ]]; then
|
||||||
|
echo "ERROR: Compiled binary not found or not executable: $BINARY_PATH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$BINARY_PATH" "$@"
|
||||||
229
src/main.c
229
src/main.c
@@ -491,8 +491,8 @@ int socket_name_random(char *out, size_t out_len);
|
|||||||
/* Version information (auto-updated by build/version tooling) */
|
/* Version information (auto-updated by build/version tooling) */
|
||||||
#define NSIGNER_VERSION_MAJOR 0
|
#define NSIGNER_VERSION_MAJOR 0
|
||||||
#define NSIGNER_VERSION_MINOR 0
|
#define NSIGNER_VERSION_MINOR 0
|
||||||
#define NSIGNER_VERSION_PATCH 26
|
#define NSIGNER_VERSION_PATCH 27
|
||||||
#define NSIGNER_VERSION "v0.0.26"
|
#define NSIGNER_VERSION "v0.0.27"
|
||||||
|
|
||||||
|
|
||||||
/* NSIGNER_HEADERLESS_DECLS_END */
|
/* NSIGNER_HEADERLESS_DECLS_END */
|
||||||
@@ -509,6 +509,7 @@ int transport_recv_framed(int fd, char **out_payload, size_t max_size);
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
#include <stdarg.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -524,6 +525,7 @@ int transport_recv_framed(int fd, char **out_payload, size_t max_size);
|
|||||||
|
|
||||||
#define NSIGNER_DEFAULT_SOCKET_NAME "nsigner"
|
#define NSIGNER_DEFAULT_SOCKET_NAME "nsigner"
|
||||||
#define ACTIVITY_LOG_CAP 16
|
#define ACTIVITY_LOG_CAP 16
|
||||||
|
#define CONNECTION_INFO_CAP 8
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char lines[ACTIVITY_LOG_CAP][256];
|
char lines[ACTIVITY_LOG_CAP][256];
|
||||||
@@ -546,6 +548,8 @@ typedef struct {
|
|||||||
} mnemonic_source_t;
|
} mnemonic_source_t;
|
||||||
|
|
||||||
static mnemonic_source_kind_t g_startup_mnemonic_source_kind = MNEMONIC_SOURCE_TUI;
|
static mnemonic_source_kind_t g_startup_mnemonic_source_kind = MNEMONIC_SOURCE_TUI;
|
||||||
|
static char g_connection_info[CONNECTION_INFO_CAP][192];
|
||||||
|
static int g_connection_info_count = 0;
|
||||||
|
|
||||||
static const TuiMenuItem g_main_menu_items[] = {
|
static const TuiMenuItem g_main_menu_items[] = {
|
||||||
{"^_q^:/x quit", 'q'},
|
{"^_q^:/x quit", 'q'},
|
||||||
@@ -570,6 +574,28 @@ static void render_status(const role_table_t *role_table,
|
|||||||
int derived_count,
|
int derived_count,
|
||||||
const char *socket_name);
|
const char *socket_name);
|
||||||
|
|
||||||
|
static void connection_info_clear(void) {
|
||||||
|
g_connection_info_count = 0;
|
||||||
|
memset(g_connection_info, 0, sizeof(g_connection_info));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void connection_info_add(const char *fmt, ...) {
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
if (fmt == NULL || g_connection_info_count >= CONNECTION_INFO_CAP) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
va_start(ap, fmt);
|
||||||
|
(void)vsnprintf(g_connection_info[g_connection_info_count],
|
||||||
|
sizeof(g_connection_info[g_connection_info_count]),
|
||||||
|
fmt,
|
||||||
|
ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
g_connection_info_count++;
|
||||||
|
}
|
||||||
|
|
||||||
static void handle_signal(int sig) {
|
static void handle_signal(int sig) {
|
||||||
(void)sig;
|
(void)sig;
|
||||||
g_running = 0;
|
g_running = 0;
|
||||||
@@ -596,6 +622,126 @@ static int read_line_stdin(char *buf, size_t buf_sz) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int read_cmd_output_local(const char *cmd, char **out_buf) {
|
||||||
|
FILE *fp;
|
||||||
|
char chunk[512];
|
||||||
|
char *buf = NULL;
|
||||||
|
size_t used = 0;
|
||||||
|
size_t cap = 0;
|
||||||
|
|
||||||
|
if (cmd == NULL || out_buf == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_buf = NULL;
|
||||||
|
fp = popen(cmd, "r");
|
||||||
|
if (fp == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (fgets(chunk, sizeof(chunk), fp) != NULL) {
|
||||||
|
size_t n = strlen(chunk);
|
||||||
|
if (used + n + 1 > cap) {
|
||||||
|
size_t new_cap = (cap == 0) ? 2048 : cap * 2;
|
||||||
|
while (new_cap < used + n + 1) {
|
||||||
|
new_cap *= 2;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
char *tmp = (char *)realloc(buf, new_cap);
|
||||||
|
if (tmp == NULL) {
|
||||||
|
free(buf);
|
||||||
|
(void)pclose(fp);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
buf = tmp;
|
||||||
|
cap = new_cap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
memcpy(buf + used, chunk, n);
|
||||||
|
used += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)pclose(fp);
|
||||||
|
|
||||||
|
if (buf == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[used] = '\0';
|
||||||
|
*out_buf = buf;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lookup_local_fips_identity(char *out_ipv6,
|
||||||
|
size_t out_ipv6_sz,
|
||||||
|
char *out_npub,
|
||||||
|
size_t out_npub_sz) {
|
||||||
|
char *json = NULL;
|
||||||
|
cJSON *root = NULL;
|
||||||
|
cJSON *ipv6_item;
|
||||||
|
cJSON *npub_item;
|
||||||
|
|
||||||
|
if (out_ipv6 == NULL || out_ipv6_sz == 0 || out_npub == NULL || out_npub_sz == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_ipv6[0] = '\0';
|
||||||
|
out_npub[0] = '\0';
|
||||||
|
|
||||||
|
if (read_cmd_output_local("fipsctl show status 2>/dev/null", &json) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
root = cJSON_Parse(json);
|
||||||
|
free(json);
|
||||||
|
if (root == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ipv6_item = cJSON_GetObjectItemCaseSensitive(root, "ipv6_addr");
|
||||||
|
npub_item = cJSON_GetObjectItemCaseSensitive(root, "npub");
|
||||||
|
|
||||||
|
if (cJSON_IsString(ipv6_item) && ipv6_item->valuestring != NULL) {
|
||||||
|
strncpy(out_ipv6, ipv6_item->valuestring, out_ipv6_sz - 1);
|
||||||
|
out_ipv6[out_ipv6_sz - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cJSON_IsString(npub_item) && npub_item->valuestring != NULL) {
|
||||||
|
strncpy(out_npub, npub_item->valuestring, out_npub_sz - 1);
|
||||||
|
out_npub[out_npub_sz - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return (out_ipv6[0] != '\0' && out_npub[0] != '\0') ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int extract_listen_port(const char *listen_target, char *out_port, size_t out_port_sz) {
|
||||||
|
const char *port;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
if (listen_target == NULL || out_port == NULL || out_port_sz == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_port[0] = '\0';
|
||||||
|
|
||||||
|
port = strrchr(listen_target, ':');
|
||||||
|
if (port == NULL || *(port + 1) == '\0') {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
port++;
|
||||||
|
|
||||||
|
for (i = 0; port[i] != '\0'; ++i) {
|
||||||
|
if (!isdigit((unsigned char)port[i])) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
strncpy(out_port, port, out_port_sz - 1);
|
||||||
|
out_port[out_port_sz - 1] = '\0';
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int connect_abstract_socket(const char *name) {
|
static int connect_abstract_socket(const char *name) {
|
||||||
int fd;
|
int fd;
|
||||||
struct sockaddr_un addr;
|
struct sockaddr_un addr;
|
||||||
@@ -895,8 +1041,25 @@ static void render_status(const role_table_t *role_table,
|
|||||||
const char *socket_name) {
|
const char *socket_name) {
|
||||||
TuiFrame frame = { "n_signer", NSIGNER_VERSION, "> Main Menu" };
|
TuiFrame frame = { "n_signer", NSIGNER_VERSION, "> Main Menu" };
|
||||||
TuiMenu menu = { g_main_menu_items, (int)(sizeof(g_main_menu_items) / sizeof(g_main_menu_items[0])) };
|
TuiMenu menu = { g_main_menu_items, (int)(sizeof(g_main_menu_items) / sizeof(g_main_menu_items[0])) };
|
||||||
|
TuiSize size = tui_terminal_size();
|
||||||
|
int left_col = tui_menu_left_col(&frame, size.width);
|
||||||
char status_buf[256];
|
char status_buf[256];
|
||||||
TuiStatus status;
|
|
||||||
|
tui_clear_continuous(size.height);
|
||||||
|
tui_render_top_frame(&frame, size.width);
|
||||||
|
|
||||||
|
tui_print("^*Connections^:");
|
||||||
|
if (g_connection_info_count == 0) {
|
||||||
|
tui_print("(none)");
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < g_connection_info_count; ++i) {
|
||||||
|
tui_print("%s", g_connection_info[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tui_print("");
|
||||||
|
tui_render_menu(&menu, left_col);
|
||||||
|
tui_print("");
|
||||||
|
|
||||||
(void)snprintf(status_buf,
|
(void)snprintf(status_buf,
|
||||||
sizeof(status_buf),
|
sizeof(status_buf),
|
||||||
@@ -906,9 +1069,7 @@ static void render_status(const role_table_t *role_table,
|
|||||||
(socket_name != NULL) ? socket_name : "(none)",
|
(socket_name != NULL) ? socket_name : "(none)",
|
||||||
derived_count,
|
derived_count,
|
||||||
g_auto_approve ? "ON" : "OFF");
|
g_auto_approve ? "ON" : "OFF");
|
||||||
status.text = status_buf;
|
tui_print("%s", status_buf);
|
||||||
|
|
||||||
tui_render_screen(&frame, &menu, &status);
|
|
||||||
|
|
||||||
tui_print("^*Roles^:");
|
tui_print("^*Roles^:");
|
||||||
if (role_table == NULL || role_table->count == 0) {
|
if (role_table == NULL || role_table->count == 0) {
|
||||||
@@ -946,7 +1107,7 @@ static void render_status(const role_table_t *role_table,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tui_anchor_prompt(0, tui_menu_left_col(&frame, tui_terminal_size().width));
|
tui_anchor_prompt(0, left_col);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1085,6 +1246,16 @@ static int prompt_load_mnemonic_tui(mnemonic_state_t *mnemonic) {
|
|||||||
idx++;
|
idx++;
|
||||||
word = strtok_r(NULL, " ", &ctx);
|
word = strtok_r(NULL, " ", &ctx);
|
||||||
}
|
}
|
||||||
|
tui_print("");
|
||||||
|
tui_print("Press Enter after writing down your mnemonic to continue.");
|
||||||
|
printf("> ");
|
||||||
|
fflush(stdout);
|
||||||
|
if (read_line_stdin(mode, sizeof(mode)) != 0) {
|
||||||
|
fprintf(stderr, "Failed to read continue input\n");
|
||||||
|
memset(phrase, 0, sizeof(phrase));
|
||||||
|
memset(phrase_copy, 0, sizeof(phrase_copy));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (mnemonic_load(mnemonic, phrase) != 0) {
|
if (mnemonic_load(mnemonic, phrase) != 0) {
|
||||||
memset(phrase, 0, sizeof(phrase));
|
memset(phrase, 0, sizeof(phrase));
|
||||||
@@ -1603,14 +1774,42 @@ int main(int argc, char *argv[]) {
|
|||||||
(void)signal(SIGINT, handle_signal);
|
(void)signal(SIGINT, handle_signal);
|
||||||
(void)signal(SIGTERM, handle_signal);
|
(void)signal(SIGTERM, handle_signal);
|
||||||
|
|
||||||
if (listen_mode == NSIGNER_LISTEN_UNIX) {
|
{
|
||||||
printf("System is ready and waiting for connections on @%s.\n", socket_name);
|
char fips_ipv6[128];
|
||||||
} else if (listen_mode == NSIGNER_LISTEN_TCP) {
|
char fips_npub[256];
|
||||||
printf("System is ready and waiting for connections on %s.\n", listen_target);
|
char listen_port[16];
|
||||||
} else if (listen_mode == NSIGNER_LISTEN_QREXEC) {
|
int have_fips_identity =
|
||||||
printf("System is ready and waiting for a qrexec request.\n");
|
(lookup_local_fips_identity(fips_ipv6, sizeof(fips_ipv6), fips_npub, sizeof(fips_npub)) == 0);
|
||||||
} else {
|
|
||||||
printf("System is ready and waiting for a stdio request.\n");
|
connection_info_clear();
|
||||||
|
|
||||||
|
if (listen_mode == NSIGNER_LISTEN_UNIX) {
|
||||||
|
connection_info_add("listen: unix @%s", socket_name);
|
||||||
|
connection_info_add("client: nsigner --socket-name %s client '<json>'", socket_name);
|
||||||
|
printf("System is ready and waiting for connections on @%s.\n", socket_name);
|
||||||
|
} else if (listen_mode == NSIGNER_LISTEN_TCP) {
|
||||||
|
connection_info_add("listen: %s", listen_target);
|
||||||
|
printf("System is ready and waiting for connections on %s.\n", listen_target);
|
||||||
|
} else if (listen_mode == NSIGNER_LISTEN_QREXEC) {
|
||||||
|
connection_info_add("listen: qrexec");
|
||||||
|
printf("System is ready and waiting for a qrexec request.\n");
|
||||||
|
} else {
|
||||||
|
connection_info_add("listen: stdio");
|
||||||
|
printf("System is ready and waiting for a stdio request.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (have_fips_identity) {
|
||||||
|
connection_info_add("fips ipv6: %s", fips_ipv6);
|
||||||
|
connection_info_add("fips npub: %s", fips_npub);
|
||||||
|
printf("fips ipv6: %s\n", fips_ipv6);
|
||||||
|
printf("fips npub: %s\n", fips_npub);
|
||||||
|
|
||||||
|
if (listen_mode == NSIGNER_LISTEN_TCP &&
|
||||||
|
extract_listen_port(listen_target, listen_port, sizeof(listen_port)) == 0) {
|
||||||
|
connection_info_add("fips address: http://%s.fips:%s", fips_npub, listen_port);
|
||||||
|
printf("fips address: http://%s.fips:%s\n", fips_npub, listen_port);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user