From 2bc2305f22fc348a6984acbb73265e6dd9f09b9e Mon Sep 17 00:00:00 2001 From: Laan Tungir Date: Fri, 8 May 2026 11:36:34 -0400 Subject: [PATCH] v0.0.16 - Add .fips identity-cache fallback for URL signer resolution and fix Write menu Exit hotkey underline --- src/main.c | 52 ++++++++++------- src/menu_write.c | 2 +- src/nsigner_client.c | 136 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 167 insertions(+), 23 deletions(-) diff --git a/src/main.c b/src/main.c index ba37640..01ed3c2 100644 --- a/src/main.c +++ b/src/main.c @@ -11,8 +11,8 @@ */ #define NT_VERSION_MAJOR 0 #define NT_VERSION_MINOR 0 -#define NT_VERSION_PATCH 15 -#define NT_VERSION "v0.0.15" +#define NT_VERSION_PATCH 16 +#define NT_VERSION "v0.0.16" #include #include @@ -183,31 +183,41 @@ int main(int argc, char *argv[]) { nt_set_app_info("NOSTR TERMINAL", NT_VERSION); nt_set_window_title("Nostr Terminal"); - /* Splash screen */ + /* Splash screen (no header/footer, centered text) */ { - TuiFrame frame = nt_frame("> Splash"); - TuiStatus status = {"Press any key to continue"}; - WINDOW *body = (WINDOW *)tuin_body_window(); int h = 0; int w = 0; const char *title = "NOSTR TERMINAL"; - int len = (int)strlen(title); - int col; - int row; + const char *version = NT_VERSION; + int title_len = (int)strlen(title); + int version_len = (int)strlen(version); + int title_col; + int version_col; + int title_row; - tuin_render_header(&frame); - getmaxyx(body, h, w); - col = (w - len) / 2; - if (col < 0) { - col = 0; + getmaxyx(stdscr, h, w); + + title_col = (w - title_len) / 2; + if (title_col < 0) { + title_col = 0; } - row = h / 2; - werase(body); - mvwprintw(body, row, col, "%s", title); - mvwprintw(body, row + 1, col, "%s", NT_VERSION); - wnoutrefresh(body); - tuin_render_footer(&status); - doupdate(); + + version_col = (w - version_len) / 2; + if (version_col < 0) { + version_col = 0; + } + + title_row = (h / 2) - 1; + if (title_row < 0) { + title_row = 0; + } + + werase(stdscr); + mvprintw(title_row, title_col, "%s", title); + if (title_row + 1 < h) { + mvprintw(title_row + 1, version_col, "%s", version); + } + refresh(); tuin_get_key(); } diff --git a/src/menu_write.c b/src/menu_write.c index ba9401d..d194b88 100644 --- a/src/menu_write.c +++ b/src/menu_write.c @@ -56,7 +56,7 @@ void menu_write(void) { {NT_HK("T", "weet it"), 't'}, {NT_HK("B", "log post (kind 30023)"), 'b'}, {NT_HK("D", "iary entry (kind 30024)"), 'd'}, - {NT_HK("X", "Exit without saving"), 'x'}, + {"E" NT_HK("x", "it without saving"), 'x'}, }; TuiFrame frame = nt_frame("> Main Menu > Write"); TuiStatus status = nt_status(); diff --git a/src/nsigner_client.c b/src/nsigner_client.c index 73a0a85..2179bc9 100644 --- a/src/nsigner_client.c +++ b/src/nsigner_client.c @@ -337,6 +337,125 @@ static int nsigner_parse_url(const char *url, char *host_out, size_t host_out_sz return NT_NSIGNER_OK; } +static int nsigner_host_has_fips_suffix(const char *host) { + size_t n; + + if (!host) { + return 0; + } + + n = strlen(host); + return (n > 5 && strcmp(host + (n - 5), ".fips") == 0); +} + +static int nsigner_extract_npub_from_fips_host(const char *host, + char *npub_out, + size_t npub_out_sz) { + const char *dot; + size_t n; + + if (!host || !npub_out || npub_out_sz == 0 || !nsigner_host_has_fips_suffix(host)) { + return -1; + } + + dot = strstr(host, ".fips"); + if (!dot || dot == host) { + return -1; + } + + n = (size_t)(dot - host); + if (n == 0 || n >= npub_out_sz) { + return -1; + } + + memcpy(npub_out, host, n); + npub_out[n] = '\0'; + return 0; +} + +static int nsigner_lookup_identity_cache_ipv6(const char *npub, + char *ipv6_out, + size_t ipv6_out_sz) { + FILE *fp; + char chunk[512]; + char *json_buf = NULL; + size_t used = 0; + size_t cap = 0; + cJSON *root = NULL; + cJSON *entries; + int rc = -1; + + if (!npub || !ipv6_out || ipv6_out_sz == 0) { + return -1; + } + + fp = popen("fipsctl show identity-cache 2>/dev/null", "r"); + if (!fp) { + return -1; + } + + while (fgets(chunk, sizeof(chunk), fp)) { + size_t need = strlen(chunk); + if (used + need + 1 > cap) { + size_t new_cap = (cap == 0) ? 2048 : (cap * 2); + char *tmp; + while (new_cap < used + need + 1) { + new_cap *= 2; + } + tmp = (char *)realloc(json_buf, new_cap); + if (!tmp) { + free(json_buf); + pclose(fp); + return -1; + } + json_buf = tmp; + cap = new_cap; + } + memcpy(json_buf + used, chunk, need); + used += need; + json_buf[used] = '\0'; + } + + pclose(fp); + if (!json_buf || used == 0) { + free(json_buf); + return -1; + } + + root = cJSON_Parse(json_buf); + free(json_buf); + if (!root) { + return -1; + } + + entries = cJSON_GetObjectItemCaseSensitive(root, "entries"); + if (cJSON_IsArray(entries)) { + cJSON *entry; + cJSON_ArrayForEach(entry, entries) { + cJSON *npub_item = cJSON_GetObjectItemCaseSensitive(entry, "npub"); + cJSON *ipv6_item; + if (!cJSON_IsString(npub_item) || !npub_item->valuestring) { + continue; + } + if (strcmp(npub_item->valuestring, npub) != 0) { + continue; + } + + ipv6_item = cJSON_GetObjectItemCaseSensitive(entry, "ipv6_addr"); + if (cJSON_IsString(ipv6_item) && ipv6_item->valuestring && + ipv6_item->valuestring[0] != '\0' && + strlen(ipv6_item->valuestring) < ipv6_out_sz) { + memcpy(ipv6_out, ipv6_item->valuestring, strlen(ipv6_item->valuestring) + 1); + rc = 0; + break; + } + } + } + + cJSON_Delete(root); + return rc; +} + static int nsigner_connect_url(const char *url) { struct addrinfo hints; struct addrinfo *res = NULL; @@ -356,7 +475,22 @@ static int nsigner_connect_url(const char *url) { snprintf(port_str, sizeof(port_str), "%d", port); if (getaddrinfo(host, port_str, &hints, &res) != 0) { - return -1; + char npub[128]; + char ipv6_addr[128]; + + if (nsigner_extract_npub_from_fips_host(host, npub, sizeof(npub)) == 0 && + nsigner_lookup_identity_cache_ipv6(npub, ipv6_addr, sizeof(ipv6_addr)) == 0) { + struct addrinfo numeric_hints; + memset(&numeric_hints, 0, sizeof(numeric_hints)); + numeric_hints.ai_family = AF_UNSPEC; + numeric_hints.ai_socktype = SOCK_STREAM; + numeric_hints.ai_flags = AI_NUMERICHOST; + if (getaddrinfo(ipv6_addr, port_str, &numeric_hints, &res) != 0) { + return -1; + } + } else { + return -1; + } } for (ai = res; ai; ai = ai->ai_next) {