From 44372c0108f0250570ce521213a43df88807c830 Mon Sep 17 00:00:00 2001 From: Laan Tungir Date: Fri, 8 May 2026 11:36:26 -0400 Subject: [PATCH] v0.0.27 - Refine TUI login flow, fix menu hotkey underlining, and pin connection info header --- run.sh | 33 ++++++++ src/main.c | 229 +++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 247 insertions(+), 15 deletions(-) create mode 100755 run.sh diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..d5ad8eb --- /dev/null +++ b/run.sh @@ -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" "$@" diff --git a/src/main.c b/src/main.c index 3c4918c..38588d3 100644 --- a/src/main.c +++ b/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) */ #define NSIGNER_VERSION_MAJOR 0 #define NSIGNER_VERSION_MINOR 0 -#define NSIGNER_VERSION_PATCH 26 -#define NSIGNER_VERSION "v0.0.26" +#define NSIGNER_VERSION_PATCH 27 +#define NSIGNER_VERSION "v0.0.27" /* NSIGNER_HEADERLESS_DECLS_END */ @@ -509,6 +509,7 @@ int transport_recv_framed(int fd, char **out_payload, size_t max_size); #include #include #include +#include #include #include #include @@ -524,6 +525,7 @@ int transport_recv_framed(int fd, char **out_payload, size_t max_size); #define NSIGNER_DEFAULT_SOCKET_NAME "nsigner" #define ACTIVITY_LOG_CAP 16 +#define CONNECTION_INFO_CAP 8 typedef struct { char lines[ACTIVITY_LOG_CAP][256]; @@ -546,6 +548,8 @@ typedef struct { } mnemonic_source_t; 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[] = { {"^_q^:/x quit", 'q'}, @@ -570,6 +574,28 @@ static void render_status(const role_table_t *role_table, int derived_count, 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) { (void)sig; 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) { int fd; struct sockaddr_un addr; @@ -895,8 +1041,25 @@ static void render_status(const role_table_t *role_table, const char *socket_name) { 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])) }; + TuiSize size = tui_terminal_size(); + int left_col = tui_menu_left_col(&frame, size.width); 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, sizeof(status_buf), @@ -906,9 +1069,7 @@ static void render_status(const role_table_t *role_table, (socket_name != NULL) ? socket_name : "(none)", derived_count, g_auto_approve ? "ON" : "OFF"); - status.text = status_buf; - - tui_render_screen(&frame, &menu, &status); + tui_print("%s", status_buf); tui_print("^*Roles^:"); 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); } @@ -1085,6 +1246,16 @@ static int prompt_load_mnemonic_tui(mnemonic_state_t *mnemonic) { idx++; 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) { memset(phrase, 0, sizeof(phrase)); @@ -1603,14 +1774,42 @@ int main(int argc, char *argv[]) { (void)signal(SIGINT, 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); - } else if (listen_mode == NSIGNER_LISTEN_TCP) { - printf("System is ready and waiting for connections on %s.\n", listen_target); - } else if (listen_mode == NSIGNER_LISTEN_QREXEC) { - printf("System is ready and waiting for a qrexec request.\n"); - } else { - printf("System is ready and waiting for a stdio request.\n"); + { + char fips_ipv6[128]; + char fips_npub[256]; + char listen_port[16]; + int have_fips_identity = + (lookup_local_fips_identity(fips_ipv6, sizeof(fips_ipv6), fips_npub, sizeof(fips_npub)) == 0); + + 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 ''", 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);