v0.0.54 - TUI: show full derivation path in Roles table, move connection instructions to on-demand 'd' hotkey display with spaced transport blocks
This commit is contained in:
73
README.md
73
README.md
@@ -105,21 +105,18 @@ 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
|
||||
n_signer v0.0.53 > Main Menu
|
||||
|
||||
Connections:
|
||||
Server: unix @nsigner_hairy_dog
|
||||
Client: nsigner --socket-name nsigner_hairy_dog client '<json>'
|
||||
OTP pad: 333e9902db839d9d... (offset 288 / 1048576 bytes)
|
||||
Press d for connection instructions
|
||||
|
||||
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 +125,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
|
||||
|
||||
|
||||
151
plans/tui_display_connections_on_demand.md
Normal file
151
plans/tui_display_connections_on_demand.md
Normal 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.
|
||||
285
src/main.c
285
src/main.c
@@ -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 54
|
||||
#define NSIGNER_VERSION "v0.0.54"
|
||||
|
||||
|
||||
/* 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,15 +1592,7 @@ 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("Press ^_d^ for connection instructions");
|
||||
tui_print("");
|
||||
tui_print("^*Roles^:");
|
||||
if (role_table == NULL || role_table->count == 0) {
|
||||
@@ -1479,7 +1602,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 +3009,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",
|
||||
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",
|
||||
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)",
|
||||
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 +3246,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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user