Compare commits

...

2 Commits

3 changed files with 447 additions and 72 deletions

View File

@@ -105,21 +105,16 @@ These modes avoid putting mnemonic material in argv/environment and are designed
### 3.2 Running phase (status display + signer)
After unlock, the terminal becomes a live status and control console rendered by [`render_status()`](src/main.c). The top frame shows the program name and version; below it are the Connections, Roles, and Activity sections, followed by a single status line. Example layout (single Unix listener, one role derived):
After unlock, the terminal becomes a live status and control console rendered by [`render_status()`](src/main.c). The top frame shows the program name and version; below it are the Roles and Activity sections, followed by a single status line. Connection instructions are not shown by default — press `d` to display them on demand (see below). Example layout:
```text
n_signer v0.0.2 > Main Menu
Connections:
Server: unix @nsigner_hairy_dog
Client: nsigner --socket-name nsigner_hairy_dog client '<json>'
OTP pad: 333e9902db839d9d... (offset 288 / 1048576 bytes)
n_signer v0.0.53 > Main Menu
Roles:
Role Purpose Curve Selector
main nostr secp256k1 role:main
ops nostr secp256k1 nostr_index:7
backup bitcoin secp256k1 role_path:m/84'/0'/0'/0/5
Role Purpose Curve Derivation path
main nostr secp256k1 m/44'/1237'/0'/0/0
nostr_idx_1 nostr secp256k1 m/44'/1237'/1'/0/0
backup bitcoin secp256k1 m/84'/0'/0'/0/5
Activity (latest first):
16:03:11 allow caller=uid:1000 method=get_public_key role=main
@@ -128,16 +123,68 @@ Activity (latest first):
15:59:10 deny caller=uid:1001 method=sign_event error=unauthorized
session=unlocked (12 words) signer=nsigner_hairy_dog derived=3 auto-approve=OFF
l lock/reunlock
r refresh
a toggle auto-approve
d display connections
q/x quit
```
The signer's name (`nsigner_hairy_dog` in this example) appears in two places: the **Connections** section as the abstract socket address (`Server: unix @nsigner_hairy_dog`), and the **status line** at the bottom (`signer=nsigner_hairy_dog`). See [§7.1](#71-linux-desktop-abstract-namespace-unix-socket) for how the name is generated.
The **Derivation path** column shows the full BIP-44 path for each role's key. For `nostr_index` roles this is `m/44'/1237'/<n>'/0/0` (NIP-06); for `role_path` roles it's the explicit path.
When multiple transports are active (interactive transport selection), the Connections section lists each one with its client command. HTTP and FIPS/TCP entries show `curl` / `nsigner --listen ... client` examples respectively.
The signer's name (`nsigner_hairy_dog` in this example) appears in the **status line** at the bottom (`signer=nsigner_hairy_dog`). See [§7.1](#71-linux-desktop-abstract-namespace-unix-socket) for how the name is generated.
### Connection instructions (press `d`)
Pressing `d` clears the screen and shows each active transport as a titled block with the connection string and an example client command. Press any key to return to the status display.
```text
n_signer v0.0.53 > Connection Instructions
Unix socket:
@nsigner_hairy_dog
Example:
nsigner --socket-name nsigner_hairy_dog client '<json>'
Qrexec (bridge-source-trusted):
qrexec-client-vm <target_qube> qubes.NsignerRpc
FIPS:
http://npub10vt4scusw6lq27qw83nfwp5sqer492h0tnwa8ugqjvp6l4xuz2qsdycrwd.fips:11111
Example:
curl -X POST http://npub10vt4scusw6lq27qw83nfwp5sqer492h0tnwa8ugqjvp6l4xuz2qsdycrwd.fips:11111/
-H 'Content-Type: application/json'
-d '<json>'
HTTP:
http://127.0.0.1:11112
Example:
curl -X POST http://127.0.0.1:11112/
-H 'Content-Type: application/json'
-d '<json>'
OTP pad: 333e9902db839d9d... (offset 288 / 1048576 bytes)
session=unlocked (12 words) signer=nsigner_hairy_dog derived=3 auto-approve=OFF
Press any key to return
```
Hotkeys (active while the status display is shown):
- `a` — toggle auto-approve (prompt) for this session
- `r` — refresh the display
- `d` — display connection instructions (press any key to return)
- `l` — lock / re-unlock the session
- `q` — quit

View File

@@ -0,0 +1,151 @@
# Plan: Move Connection Instructions to On-Demand Display
## Problem
The Connections section in the running TUI is verbose and confusing. It mixes server addresses with client command examples, and when multiple transports are active (Unix + TCP + HTTP) it takes up a large chunk of the status screen, pushing the Roles and Activity sections down. The connection instructions are only needed once when a user wants to know how to reach the signer — they don't need to be permanently visible.
## Solution
Remove the Connections section from the default `render_status()` display. Add a new hotkey **`d`** (display connection instructions) that prints the connection info on demand, then returns to the normal status view on the next refresh/keystroke.
## Design
### Default status display (after change)
The Connections section is removed. The display shows: Roles, Activity, status line, hotkey menu. A one-line hint at the top reminds the user that `d` shows connection instructions.
```text
================================================================================
n_signer v0.0.53
================================================================================
> Main Menu
Press d for connection instructions
Roles
Role Purpose Curve Selector
-------------------- ------------ ------------ ------------
main nostr secp256k1 nostr_index
ops nostr secp256k1 nostr_index
backup bitcoin secp256k1 role_path
Activity (latest first)
16:03:11 allow caller=uid:1000 method=get_public_key role=main
16:02:44 prompt caller=uid:1000 method=sign_event role=ops
16:02:46 allow caller=uid:1000 method=sign_event role=ops
15:59:10 deny caller=uid:1001 method=sign_event error=unauthorized
session=unlocked (12 words) signer=nsigner_hairy_dog derived=3 auto-approve=OFF
l lock/reunlock
r refresh
a toggle auto-approve
d display connections
q/x quit
```
### On-demand connection display (press `d`)
When the user presses `d`, the screen clears and shows **only** the connection instructions, with clear spacing between transports. Each transport block shows the **connection string** (the address/endpoint to reach), a blank line, then an **Example:** with the client command indented beneath. No "Server:" / "Client:" labels — just the address and an example. A footer tells the user how to return.
```text
================================================================================
n_signer v0.0.53
================================================================================
> Connection Instructions
Unix socket
@nsigner_hairy_dog
Example:
nsigner --socket-name nsigner_hairy_dog client '<json>'
Qrexec (bridge-source-trusted):
qrexec-client-vm <target_qube> qubes.NsignerRpc
FIPS
http://npub10vt4scusw6lq27qw83nfwp5sqer492h0tnwa8ugqjvp6l4xuz2qsdycrwd.fips:11111
Example:
curl -X POST http://npub10vt4scusw6lq27qw83nfwp5sqer492h0tnwa8ugqjvp6l4xuz2qsdycrwd.fips:11111/ \
-H 'Content-Type: application/json' \
-d '<json>'
HTTP
http://127.0.0.1:11112
Example:
curl -X POST http://127.0.0.1:11112/ \
-H 'Content-Type: application/json' \
-d '<json>'
OTP pad: 333e9902db839d9d... (offset 288 / 1048576 bytes)
session=unlocked (12 words) signer=nsigner_hairy_dog derived=3 auto-approve=OFF
Press any key to return
```
### Key differences from current display
1. **Spacing between transports** — each transport gets its own titled block with blank lines separating it from the next, instead of a flat list of `Server:` / `Client:` lines.
2. **Connection string + example, no labels** — each block shows the bare connection string (e.g. `http://127.0.0.1:11112`), then an `Example:` with the client command indented beneath. No confusing "Server:" / "Client:" labels.
3. **On-demand only** — the default running display no longer shows connections at all, just a one-line hint. The user presses `d` when they need the instructions, reads them, then presses any key to return.
4. **OTP pad status** — shown at the bottom of the connection display (it's connection-related: which pad is bound and how much has been consumed).
## Implementation Steps
1. **[`src/main.c`](src/main.c) — `render_status()`** (line 1451):
- Remove the Connections section (lines 1464-1471).
- Add a one-line hint after the top frame: `tui_print("Press d for connection instructions");`
- Add a blank line after the hint.
2. **[`src/main.c`](src/main.c) — new function `render_connections()`**:
- Clears the screen and renders the top frame.
- Iterates `g_connection_info` but formats it with the spaced layout shown above. Since `g_connection_info` already stores formatted strings like `"Server: unix @nsigner_hairy_dog"` and `" Client: nsigner --socket-name ... client '<json>'"`, either:
- **Option A**: Reformat the stored strings into blocks by detecting `Server:` lines as transport boundaries and printing blank lines + indentation.
- **Option B**: Store connection info in a structured form (transport type, server address, client command(s)) and render the block layout from the structured data.
- Option B is cleaner but requires changing `connection_info_add()` and all its call sites. Option A is a smaller change. **Recommend Option A** for now — parse the existing flat list into blocks.
- **FIPS section**: The connection string is the npub-based FIPS URL (`http://<npub>.fips:<port>`), which FIPS resolves to the signer's TCP endpoint. The example uses `curl` since FIPS makes the endpoint HTTP-reachable for clients with FIPS installed. The raw TCP bind address (`tcp:[::]:11111`) is not shown — it's an implementation detail; the npub URL is what clients use.
- Print OTP pad status if bound.
- Print the status line at the bottom.
- Print "Press any key to return" footer.
3. **[`src/main.c`](src/main.c) — `g_main_menu_items`** (line 825):
- Add `{"^_d^: display connections", 'd'}` to the menu array.
4. **[`src/main.c`](src/main.c) — event loop** (line ~3068, the stdin keystroke handler):
- Add a case for `'d'`:
- Call `render_connections()`.
- Read one keystroke from stdin (blocking `read()`).
- Call `render_status()` to return to the normal display.
5. **[`README.md`](README.md) §3.2**:
- Update the example TUI layout to match the new default display (no Connections section, `d` in hotkeys).
- Add a note that pressing `d` shows the connection instructions.
6. **[`api.md`](api.md) §5 Transports**:
- No changes needed — the transport documentation is already separate from the TUI display.
## Files Changed
| File | Change |
|------|--------|
| [`src/main.c`](src/main.c) | Remove Connections from `render_status()`, add `render_connections()`, add `d` hotkey + menu item + event loop case |
| [`README.md`](README.md) | Update §3.2 example display to match new layout |
| [`api.md`](api.md) | No changes |
## Verification
- `make dev` builds clean.
- Start nsigner with all transports selected. Default display shows no Connections section, just the "Press d for connection instructions" hint.
- Press `d` — connection instructions appear with clear spacing between transports.
- Press any key — returns to normal status display.
- All other hotkeys (`l`, `r`, `a`, `q`) still work.

View File

@@ -759,8 +759,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 53
#define NSIGNER_VERSION "v0.0.53"
#define NSIGNER_VERSION_PATCH 55
#define NSIGNER_VERSION "v0.0.55"
/* NSIGNER_HEADERLESS_DECLS_END */
@@ -797,12 +797,24 @@ int transport_recv_framed(int fd, char **out_payload, size_t max_size);
#define NSIGNER_QREXEC_SERVICE_NAME "qubes.NsignerRpc"
#define ACTIVITY_LOG_CAP 16
#define CONNECTION_INFO_CAP 8
#define CONNECTION_INFO_STR_MAX 256
typedef struct {
char lines[ACTIVITY_LOG_CAP][256];
int count;
} activity_log_t;
/* Structured connection info for the on-demand connection display.
* Each entry represents one transport block: a title (e.g. "Unix socket"),
* a connection string (the address/endpoint), an optional example command,
* and an optional extra line (e.g. qrexec bridge info under Unix). */
typedef struct {
char title[64];
char connection_string[CONNECTION_INFO_STR_MAX];
char example[CONNECTION_INFO_STR_MAX]; /* empty if none */
char extra[CONNECTION_INFO_STR_MAX]; /* empty if none */
} connection_info_entry_t;
static volatile sig_atomic_t g_running = 1;
static activity_log_t g_activity_log;
static int g_auto_approve = 0;
@@ -819,13 +831,16 @@ 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 connection_info_entry_t g_connection_info[CONNECTION_INFO_CAP];
static int g_connection_info_count = 0;
/* OTP pad status line (shown at bottom of connection display) */
static char g_otp_pad_status[CONNECTION_INFO_STR_MAX];
static const TuiMenuItem g_main_menu_items[] = {
{"^_l^: lock/reunlock", 'l'},
{"^_r^: refresh", 'r'},
{"^_a^: toggle auto-approve", 'a'},
{"^_d^: display connections", 'd'},
{"^_q^:/x quit", 'q'}
};
@@ -845,28 +860,49 @@ static void render_status(const role_table_t *role_table,
int derived_count,
const char *socket_name);
static void render_connections(const role_table_t *role_table,
const mnemonic_state_t *mnemonic,
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));
g_otp_pad_status[0] = '\0';
}
static void connection_info_add(const char *fmt, ...) {
va_list ap;
/* Add a transport block to the connection info.
* title: transport name (e.g. "Unix socket", "FIPS", "HTTP")
* connection_str: the address/endpoint clients use to reach the signer
* example: example client command (empty string "" if none)
* extra: optional extra line under the block (e.g. qrexec info; "" if none) */
static void connection_info_add_transport(const char *title,
const char *connection_str,
const char *example,
const char *extra) {
connection_info_entry_t *e;
if (fmt == NULL || g_connection_info_count >= CONNECTION_INFO_CAP) {
if (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);
e = &g_connection_info[g_connection_info_count];
memset(e, 0, sizeof(*e));
if (title) { strncpy(e->title, title, sizeof(e->title) - 1); }
if (connection_str) { strncpy(e->connection_string, connection_str, sizeof(e->connection_string) - 1); }
if (example) { strncpy(e->example, example, sizeof(e->example) - 1); }
if (extra) { strncpy(e->extra, extra, sizeof(e->extra) - 1); }
g_connection_info_count++;
}
/* Set the OTP pad status line (shown at bottom of connection display). */
static void connection_info_set_otp(const char *fmt, ...) {
va_list ap;
if (fmt == NULL) { g_otp_pad_status[0] = '\0'; return; }
va_start(ap, fmt);
(void)vsnprintf(g_otp_pad_status, sizeof(g_otp_pad_status), fmt, ap);
va_end(ap);
}
static void handle_signal(int sig) {
(void)sig;
g_running = 0;
@@ -1440,7 +1476,11 @@ static void role_table_get_cell(int row, int col, char *out, size_t out_size, vo
(void)snprintf(out, out_size, "%s", role_curve_to_str(r->curve));
break;
case 3:
(void)snprintf(out, out_size, "%s", (r->selector_type == SELECTOR_NOSTR_INDEX) ? "nostr_index" : "role_path");
if (r->selector_type == SELECTOR_NOSTR_INDEX) {
(void)snprintf(out, out_size, "m/44'/1237'/%d'/0/0", r->nostr_index);
} else {
(void)snprintf(out, out_size, "%s", r->role_path);
}
break;
default:
out[0] = '\0';
@@ -1448,6 +1488,97 @@ static void role_table_get_cell(int row, int col, char *out, size_t out_size, vo
}
}
/* On-demand connection instructions display (press 'd').
* Shows each transport as a titled block with the connection string
* and an example client command, with blank lines between transports. */
static void render_connections(const role_table_t *role_table,
const mnemonic_state_t *mnemonic,
int derived_count,
const char *socket_name) {
TuiFrame frame = { "n_signer", NSIGNER_VERSION, "> Connection Instructions" };
TuiSize size = tui_terminal_size();
char status_buf[256];
int i;
tui_clear_continuous(size.height);
tui_render_top_frame(&frame, size.width);
tui_print("");
for (i = 0; i < g_connection_info_count; i++) {
connection_info_entry_t *e = &g_connection_info[i];
/* Transport title */
tui_print("^*%s^:", e->title);
tui_print("");
/* Connection string (indented) */
tui_print(" %s", e->connection_string);
tui_print("");
/* Example command (if present) */
if (e->example[0] != '\0') {
tui_print(" Example:");
/* The example may contain embedded newlines for multi-line commands */
{
const char *p = e->example;
const char *nl;
while (*p != '\0') {
nl = strchr(p, '\n');
if (nl != NULL) {
tui_print(" %.*s", (int)(nl - p), p);
p = nl + 1;
} else {
tui_print(" %s", p);
break;
}
}
}
tui_print("");
}
/* Extra info (if present, e.g. qrexec bridge) */
if (e->extra[0] != '\0') {
const char *p = e->extra;
const char *nl;
while (*p != '\0') {
nl = strchr(p, '\n');
if (nl != NULL) {
tui_print(" %.*s", (int)(nl - p), p);
p = nl + 1;
} else {
tui_print(" %s", p);
break;
}
}
tui_print("");
}
tui_print("");
}
/* OTP pad status */
if (g_otp_pad_status[0] != '\0') {
tui_print("%s", g_otp_pad_status);
tui_print("");
}
/* Status line */
(void)snprintf(status_buf,
sizeof(status_buf),
"session=%s (%d words) signer=%s derived=%d auto-approve=%s",
mnemonic_is_loaded(mnemonic) ? "unlocked" : "locked",
(mnemonic != NULL) ? mnemonic->word_count : 0,
(socket_name != NULL) ? socket_name : "(none)",
derived_count,
g_auto_approve ? "ON" : "OFF");
tui_print("%s", status_buf);
tui_print("");
tui_print("Press any key to return");
tui_anchor_prompt(0, 0);
fflush(stdout);
}
static void render_status(const role_table_t *role_table,
const mnemonic_state_t *mnemonic,
int derived_count,
@@ -1461,16 +1592,6 @@ static void render_status(const role_table_t *role_table,
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_print("^*Roles^:");
if (role_table == NULL || role_table->count == 0) {
tui_print("(none)");
@@ -1479,7 +1600,7 @@ static void render_status(const role_table_t *role_table,
{"Role", 20, 0},
{"Purpose", 12, 0},
{"Curve", 12, 0},
{"Selector", 12, 0}
{"Derivation path", 24, 0}
};
role_table_view_data_t view;
TuiTable table;
@@ -2886,76 +3007,122 @@ int main(int argc, char *argv[]) {
if (multi_listen) {
/* Multi-listener mode: show all active transports */
if (transport_mask & (TRANSPORT_UNIX | TRANSPORT_QREXEC_BRIDGE)) {
connection_info_add("Server: unix @%s%s", socket_name,
bridge_source_trusted ? " (bridge-source-trusted)" : "");
connection_info_add(" Client: nsigner --socket-name %s client '<json>'", socket_name);
char unix_conn[128];
char unix_example[256];
char unix_extra[256] = "";
snprintf(unix_conn, sizeof(unix_conn), "@%s", socket_name);
snprintf(unix_example, sizeof(unix_example),
"nsigner --socket-name %s client '<json>'", socket_name);
if (transport_mask & TRANSPORT_QREXEC_BRIDGE) {
connection_info_add(" Qrexec: qrexec-client-vm <target_qube> %s",
NSIGNER_QREXEC_SERVICE_NAME);
snprintf(unix_extra, sizeof(unix_extra),
"Qrexec (bridge-source-trusted):\n"
" qrexec-client-vm <target_qube> %s",
NSIGNER_QREXEC_SERVICE_NAME);
printf("qrexec service: %s\n", NSIGNER_QREXEC_SERVICE_NAME);
}
connection_info_add_transport("Unix socket", unix_conn, unix_example, unix_extra);
printf("System is ready and waiting for connections on @%s.\n", socket_name);
}
if (transport_mask & TRANSPORT_TCP) {
const char *tcp_addr = servers[tcp_server_idx].socket_name;
connection_info_add("Server: FIPS/TCP %s", tcp_addr + 4); /* skip "tcp:" */
connection_info_add(" Client: nsigner --listen %s client '<json>'", tcp_addr);
char fips_conn[256] = "";
char fips_example[256] = "";
/* If we have a FIPS identity, show the npub-based URL */
if (have_fips_identity &&
extract_listen_port(tcp_addr, listen_port, sizeof(listen_port)) == 0) {
snprintf(fips_conn, sizeof(fips_conn),
"http://%s.fips:%s", fips_npub, listen_port);
snprintf(fips_example, sizeof(fips_example),
"curl -X POST http://%s.fips:%s/ \\\n"
" -H 'Content-Type: application/json' \\\n"
" -d '<json>'", fips_npub, listen_port);
printf("fips address: http://%s.fips:%s\n", fips_npub, listen_port);
} else {
snprintf(fips_conn, sizeof(fips_conn), "%s", tcp_addr);
snprintf(fips_example, sizeof(fips_example),
"nsigner --listen %s client '<json>'", tcp_addr);
}
connection_info_add_transport("FIPS", fips_conn, fips_example, "");
printf("System is ready and waiting for connections on %s.\n", tcp_addr);
}
if (transport_mask & TRANSPORT_HTTP) {
const char *http_addr = servers[http_server_idx].socket_name;
connection_info_add("Server: HTTP %s", http_addr + 5); /* skip "http:" */
connection_info_add(" Client: curl -X POST http://%s/ -H 'Content-Type: application/json' -d '<json>'",
http_addr + 5);
char http_conn[256];
char http_example[256];
snprintf(http_conn, sizeof(http_conn), "http://%s", http_addr + 5);
snprintf(http_example, sizeof(http_example),
"curl -X POST http://%s/ \\\n"
" -H 'Content-Type: application/json' \\\n"
" -d '<json>'", http_addr + 5);
connection_info_add_transport("HTTP", http_conn, http_example, "");
printf("System is ready and waiting for connections on http://%s.\n", http_addr + 5);
}
} else if (listen_mode == NSIGNER_LISTEN_UNIX) {
connection_info_add("Server: unix @%s", socket_name);
connection_info_add(" Client: nsigner --socket-name %s client '<json>'", socket_name);
char unix_conn[128];
char unix_example[256];
char unix_extra[256] = "";
snprintf(unix_conn, sizeof(unix_conn), "@%s", socket_name);
snprintf(unix_example, sizeof(unix_example),
"nsigner --socket-name %s client '<json>'", socket_name);
if (bridge_source_trusted) {
connection_info_add(" Qrexec: qrexec-client-vm <target_qube> %s",
NSIGNER_QREXEC_SERVICE_NAME);
snprintf(unix_extra, sizeof(unix_extra),
"Qrexec (bridge-source-trusted):\n"
" qrexec-client-vm <target_qube> %s",
NSIGNER_QREXEC_SERVICE_NAME);
printf("qrexec service: %s\n", NSIGNER_QREXEC_SERVICE_NAME);
}
connection_info_add_transport("Unix socket", unix_conn, unix_example, unix_extra);
printf("System is ready and waiting for connections on @%s.\n", socket_name);
} else if (listen_mode == NSIGNER_LISTEN_TCP) {
const char *actual_addr = server.socket_name;
connection_info_add("Server: %s", actual_addr);
connection_info_add(" Client: nsigner --listen %s client '<json>'", actual_addr);
char fips_conn[256] = "";
char fips_example[256] = "";
if (have_fips_identity &&
extract_listen_port(actual_addr, listen_port, sizeof(listen_port)) == 0) {
snprintf(fips_conn, sizeof(fips_conn),
"http://%s.fips:%s", fips_npub, listen_port);
snprintf(fips_example, sizeof(fips_example),
"curl -X POST http://%s.fips:%s/ \\\n"
" -H 'Content-Type: application/json' \\\n"
" -d '<json>'", fips_npub, listen_port);
printf("fips address: http://%s.fips:%s\n", fips_npub, listen_port);
} else {
snprintf(fips_conn, sizeof(fips_conn), "%s", actual_addr);
snprintf(fips_example, sizeof(fips_example),
"nsigner --listen %s client '<json>'", actual_addr);
}
connection_info_add_transport("FIPS", fips_conn, fips_example, "");
printf("System is ready and waiting for connections on %s.\n", actual_addr);
} else if (listen_mode == NSIGNER_LISTEN_HTTP) {
const char *actual_addr = server.socket_name;
connection_info_add("Server: %s", actual_addr);
connection_info_add(" Client: curl -X POST http://%s/ -H 'Content-Type: application/json' -d '<json>'",
actual_addr + 5); /* skip "http:" prefix */
char http_conn[256];
char http_example[256];
snprintf(http_conn, sizeof(http_conn), "http://%s", actual_addr + 5);
snprintf(http_example, sizeof(http_example),
"curl -X POST http://%s/ \\\n"
" -H 'Content-Type: application/json' \\\n"
" -d '<json>'", actual_addr + 5);
connection_info_add_transport("HTTP", http_conn, http_example, "");
printf("System is ready and waiting for connections on %s.\n", actual_addr);
} else if (listen_mode == NSIGNER_LISTEN_QREXEC) {
connection_info_add("Server: qrexec (one request per invocation)");
connection_info_add_transport("Qrexec", "qrexec (one request per invocation)", "", "");
printf("System is ready and waiting for a qrexec request.\n");
} else {
connection_info_add("Server: stdio (one request per invocation)");
connection_info_add_transport("Stdio", "stdio (one request per invocation)", "", "");
printf("System is ready and waiting for a stdio request.\n");
}
/* Show OTP pad status if bound. */
if (otp_pad_is_bound()) {
connection_info_add("OTP pad: %s (offset %llu / %llu bytes)",
otp_pad_chksum(),
(unsigned long long)otp_pad_current_offset(),
(unsigned long long)otp_pad_size());
connection_info_set_otp("OTP pad: %s (offset %llu / %llu bytes)",
otp_pad_chksum(),
(unsigned long long)otp_pad_current_offset(),
(unsigned long long)otp_pad_size());
}
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);
@@ -3077,6 +3244,16 @@ int main(int argc, char *argv[]) {
g_auto_approve = g_auto_approve ? 0 : 1;
server_set_prompt_always_allow(g_auto_approve);
render_status(&role_table, &mnemonic, derived_count, socket_name);
} else if (lower == 'd') {
/* Display connection instructions on demand.
* Show the connections screen, wait for any key, then
* return to the normal status display. */
render_connections(&role_table, &mnemonic, derived_count, socket_name);
{
char dch = '\0';
(void)read(STDIN_FILENO, &dch, 1);
}
render_status(&role_table, &mnemonic, derived_count, socket_name);
} else if (lower == 'l') {
mnemonic_source_t relock_source;