Compare commits

...

3 Commits

6 changed files with 424 additions and 57 deletions

View File

@@ -239,7 +239,7 @@ Discovery:
- `nsigner list` enumerates currently bound `nsigner_*` abstract sockets by reading `/proc/net/unix`.
- `nsigner --listen stdio` runs one framed JSON-RPC request/response over stdin/stdout.
- `nsigner --listen qrexec` is the same stdio framing mode, but caller identity can be derived from `QREXEC_REMOTE_DOMAIN` (displayed as `qubes:<source-vm>`).
- `nsigner --listen tcp:127.0.0.1:PORT` (or `tcp:[::1]:PORT`) enables loopback-only TCP listening for non-AF_UNIX clients.
- `nsigner --listen tcp:IPv4:PORT` or `tcp:[IPv6]:PORT` enables TCP listening for non-AF_UNIX clients (for example `tcp:127.0.0.1:8080`, `tcp:[::]:8080`, or `tcp:[fd00::1234]:8080`).
### 7.2 ESP32 MCU: USB-CDC serial
@@ -303,10 +303,10 @@ Generic stdio transport mode (single framed request over stdin/stdout):
nsigner --listen stdio
```
TCP loopback transport mode (no TUI; serves requests until terminated):
TCP transport mode (no TUI; serves requests until terminated):
```bash
nsigner --listen tcp:127.0.0.1:8080
nsigner --listen tcp:[::]:8080
```
### 9.2 Send a request (client mode)

View File

@@ -2,17 +2,17 @@
## 1. Scope
This runbook covers a practical Tier-1 deployment of `nsigner` over a loopback TCP listener, with connectivity provided by FIPS as the network substrate.
This runbook covers a practical Tier-1 deployment of `nsigner` over a TCP listener, with connectivity provided by FIPS as the network substrate.
Tier-1 objective:
- Keep `nsigner` transport simple (`--listen tcp:127.0.0.1:PORT` or `--listen tcp:[::1]:PORT`).
- Keep `nsigner` transport simple (`--listen tcp:IPv4:PORT` or `--listen tcp:[IPv6]:PORT`).
- Use FIPS to carry traffic between peers.
- Do not add FIPS runtime dependencies into `nsigner`.
Out of scope in this document:
- Remote non-loopback TCP exposure (`--allow-remote`, TLS) planned for later phase.
- Mandatory transport-level TLS/authentication hardening (still planned for a later phase).
- Automatic caller->npub enrichment from FIPS session metadata.
---
@@ -22,17 +22,17 @@ Out of scope in this document:
Two cooperating layers:
1. **Signer process layer** (`nsigner`)
- Listens on loopback TCP only.
- Listens on operator-selected TCP endpoint (IPv4 or IPv6).
- Uses existing 4-byte big-endian framed JSON-RPC protocol.
- Keeps existing policy/prompt behavior.
2. **Network substrate layer** (FIPS)
- Establishes peer connectivity between nodes/qubes.
- Carries application traffic to a local loopback endpoint on each side.
- Carries application traffic to the configured signer TCP endpoint.
Conceptually:
`client app -> local FIPS endpoint -> FIPS mesh -> remote FIPS endpoint -> 127.0.0.1:PORT -> nsigner`
`client app -> local FIPS endpoint -> FIPS mesh -> remote FIPS endpoint -> signer TCP endpoint -> nsigner`
---
@@ -58,21 +58,21 @@ Operational assumptions:
## 4. Start signer in Tier-1 TCP mode
Run `nsigner` in loopback TCP listen mode:
Run `nsigner` in TCP listen mode:
```bash
./build/nsigner --listen tcp:127.0.0.1:8080
./build/nsigner --listen tcp:[::]:8080
```
Or IPv6 loopback:
Or bind to a specific FIPS ULA address:
```bash
./build/nsigner --listen tcp:[::1]:8080
./build/nsigner --listen tcp:[fd00::1234]:8080
```
Behavior notes:
- Non-loopback values are rejected by design.
- Bind target is operator-controlled; pick the narrowest reachable address that satisfies your topology.
- No TUI hotkey loop is required in TCP mode; process serves requests until terminated.
- Caller identity is shown as a TCP endpoint descriptor in activity/prompt context.
@@ -82,14 +82,14 @@ Behavior notes:
Because FIPS deployment topologies vary, use this generic pattern:
1. Bind `nsigner` on loopback in signer environment.
2. Configure FIPS service/forwarding so remote authenticated peer traffic is delivered to that loopback endpoint.
1. Bind `nsigner` on a deliberate signer endpoint (`[::]:PORT` for broad reach, or specific `fd..` for tighter scope).
2. Configure FIPS service/forwarding so remote authenticated peer traffic is delivered to that signer endpoint.
3. On caller side, direct client traffic to the local FIPS ingress endpoint for that remote service.
Validation checklist:
- FIPS session is established between caller and signer nodes.
- Transport path from caller -> signer loopback endpoint succeeds.
- Transport path from caller -> configured signer endpoint succeeds.
- `nsigner` receives framed request and returns framed response.
---
@@ -134,7 +134,7 @@ Trigger a request path that requires prompt/denial and confirm client handles po
## 7. Security guardrails
- Keep listener loopback-only in Tier-1.
- Prefer binding to a specific FIPS IPv6 ULA (`fd..`) rather than wildcard (`[::]`) when possible.
- Do not expose signer port directly on LAN/WAN.
- Keep FIPS peer allowlist tight; avoid broad trust domains.
- Treat FIPS connectivity as transport, not authorization bypass.
@@ -147,20 +147,12 @@ Trigger a request path that requires prompt/denial and confirm client handles po
### 8.1 `invalid tcp listen target`
Cause:
- `--listen` argument does not match `tcp:HOST:PORT` or `tcp:[::1]:PORT`.
- `--listen` argument does not match `tcp:HOST:PORT` or `tcp:[IPv6]:PORT`.
Fix:
- Use explicit loopback host and valid numeric port.
- Use valid numeric port and valid IPv4/IPv6 literal host.
### 8.2 `non-loopback TCP bind denied`
Cause:
- Attempt to bind non-loopback target in Tier-1 mode.
Fix:
- Switch to `127.x.x.x` or `::1` target.
### 8.3 Framing parse failures (`parse_error`)
### 8.2 Framing parse failures (`parse_error`)
Cause:
- Client sent line-delimited/raw JSON instead of framed JSON.
@@ -168,7 +160,7 @@ Cause:
Fix:
- Send 4-byte big-endian length prefix followed by exact UTF-8 JSON payload bytes.
### 8.4 FIPS path up, signer path down
### 8.3 FIPS path up, signer path down
Cause:
- FIPS session exists but forwarding/service mapping to signer loopback endpoint is missing.

195
install_qube_fips_nsigner.sh Executable file
View File

@@ -0,0 +1,195 @@
#!/usr/bin/env bash
set -euo pipefail
# User-only installer for Qubes AppVM persistence model.
# Nothing is written to /usr, /etc, or other root-owned paths.
#
# Installs into $HOME:
# - nsigner -> ~/.local/bin/nsigner
# - startup helper -> ~/start_nsigner.sh
#
# Usage:
# bash install_qube_fips_nsigner.sh
# bash install_qube_fips_nsigner.sh --help
#
# Optional env vars:
# NSIGNER_VERSION=v0.0.7
# NSIGNER_GITEA_TOKEN=<token> # if n_signer release assets are private
# NSIGNER_BINARY_URL=<direct url to nsigner_static_x86_64>
NSIGNER_VERSION="${NSIGNER_VERSION:-v0.0.7}"
NSIGNER_RELEASE_PAGE="https://git.laantungir.net/laantungir/n_signer/releases/tag/${NSIGNER_VERSION}"
NSIGNER_API_TAG_URL="https://git.laantungir.net/api/v1/repos/laantungir/n_signer/releases/tags/${NSIGNER_VERSION}"
PREFIX_BIN="${HOME}/.local/bin"
log() { printf "\033[1;34m[INFO]\033[0m %s\n" "$*"; }
warn() { printf "\033[1;33m[WARN]\033[0m %s\n" "$*"; }
err() { printf "\033[1;31m[ERR ]\033[0m %s\n" "$*"; }
show_help() {
cat <<EOF
Usage: bash install_qube_fips_nsigner.sh [options]
User-only install (Qubes AppVM friendly):
- n_signer ${NSIGNER_VERSION}
- signer startup helper script
Options:
-h, --help Show this help and exit
Optional env vars:
NSIGNER_VERSION=v0.0.7
NSIGNER_GITEA_TOKEN=<token> # required if n_signer release assets are private
NSIGNER_BINARY_URL=<direct url to nsigner_static_x86_64>
Install paths:
~/.local/bin/nsigner
~/start_nsigner.sh
EOF
}
require_cmd() {
command -v "$1" >/dev/null 2>&1 || {
err "Missing command: $1"
exit 1
}
}
install_runtime_deps() {
if command -v apt-get >/dev/null 2>&1; then
log "Installing runtime dependencies via apt"
sudo apt-get update
sudo apt-get install -y ca-certificates curl jq
elif command -v dnf >/dev/null 2>&1; then
log "Installing runtime dependencies via dnf"
sudo dnf install -y ca-certificates curl jq
else
err "Unsupported distro: need apt-get or dnf to install runtime dependencies"
exit 1
fi
}
prepare_dirs() {
mkdir -p "${PREFIX_BIN}"
}
download_nsigner_asset_url() {
local headers=()
if [[ -n "${NSIGNER_GITEA_TOKEN:-}" ]]; then
headers=(-H "Authorization: token ${NSIGNER_GITEA_TOKEN}")
fi
curl -fsSL "${headers[@]}" "${NSIGNER_API_TAG_URL}" \
| jq -r '.assets[]?.browser_download_url // empty' \
| grep -E 'nsigner_static_x86_64$' \
| head -n1 || true
}
install_nsigner() {
log "Installing n_signer ${NSIGNER_VERSION}"
log "Release page: ${NSIGNER_RELEASE_PAGE}"
local asset_url="${NSIGNER_BINARY_URL:-}"
if [[ -z "${asset_url}" ]]; then
asset_url="$(download_nsigner_asset_url)"
fi
if [[ -z "${asset_url}" ]]; then
err "Could not find downloadable n_signer x86_64 release binary for ${NSIGNER_VERSION}."
err "Provide NSIGNER_BINARY_URL or NSIGNER_GITEA_TOKEN so the release asset can be resolved."
exit 1
fi
log "Using n_signer binary URL: ${asset_url}"
if [[ -n "${NSIGNER_GITEA_TOKEN:-}" ]]; then
curl -fL -H "Authorization: token ${NSIGNER_GITEA_TOKEN}" -o "${PREFIX_BIN}/nsigner" "${asset_url}"
else
curl -fL -o "${PREFIX_BIN}/nsigner" "${asset_url}"
fi
chmod 0755 "${PREFIX_BIN}/nsigner"
log "Installed ${PREFIX_BIN}/nsigner from release binary"
}
write_signer_start_script() {
local script_path="${HOME}/start_nsigner.sh"
cat >"${script_path}" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
export PATH="$HOME/.local/bin:$PATH"
LISTEN_TARGET="${NSIGNER_LISTEN_TARGET:-tcp:[::]:8080}"
echo "=== n_signer startup ==="
echo "listen target: ${LISTEN_TARGET}"
# Optional: print current FIPS identity info if fipsctl is available.
if command -v fipsctl >/dev/null 2>&1; then
if fipsctl show status >/dev/null 2>&1; then
STATUS_JSON="$(fipsctl show status)"
elif sudo -n fipsctl show status >/dev/null 2>&1; then
STATUS_JSON="$(sudo -n fipsctl show status)"
else
STATUS_JSON=""
fi
if [[ -n "${STATUS_JSON}" ]]; then
FIPS_IPV6="$(printf '%s\n' "${STATUS_JSON}" | sed -n 's/.*"ipv6_addr": "\([^"]*\)".*/\1/p')"
FIPS_NPUB="$(printf '%s\n' "${STATUS_JSON}" | sed -n 's/.*"npub": "\([^"]*\)".*/\1/p')"
LISTEN_PORT="$(printf '%s\n' "${LISTEN_TARGET}" | sed -n 's/.*:\([0-9][0-9]*\)$/\1/p')"
[[ -n "${FIPS_IPV6}" ]] && echo "fips ipv6: ${FIPS_IPV6}"
[[ -n "${FIPS_NPUB}" ]] && echo "fips npub: ${FIPS_NPUB}"
if [[ -n "${FIPS_NPUB}" && -n "${LISTEN_PORT}" ]]; then
echo "fips address: http://${FIPS_NPUB}.fips:${LISTEN_PORT}"
fi
else
echo "fips status: unavailable (run as user in fips group or with sudo)"
fi
fi
echo
echo "Starting signer..."
echo "On first remote request, approve in prompt with [y] or [a]."
exec "$HOME/.local/bin/nsigner" --listen "${LISTEN_TARGET}"
EOF
chmod 0755 "${script_path}"
log "Wrote ${script_path}"
}
post_checks() {
export PATH="${PREFIX_BIN}:${PATH}"
log "Running post-install checks"
require_cmd nsigner
nsigner --version || true
log "User binaries installed in: ${PREFIX_BIN}"
log "If needed, add to shell PATH: export PATH=\"${PREFIX_BIN}:\$PATH\""
}
main() {
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
show_help
exit 0
fi
if [[ $# -gt 0 ]]; then
err "Unknown option: $1"
show_help
exit 1
fi
install_runtime_deps
prepare_dirs
install_nsigner
write_signer_start_script
post_checks
log "Completed user-only install of n_signer"
log "Start signer with: ~/start_nsigner.sh"
}
main "$@"

View File

@@ -214,22 +214,22 @@ Use Qubes' native inter-qube primitive instead of inventing one.
- Tests: a unit test that injects fake qrexec env vars and a stdio framing harness; an integration script that documents end-to-end install in a Qubes VM (manual, not in CI).
- Docs: add a "Qubes deployment" section to [`README.md`](../README.md) and to [`CLIENT_IMPLEMENTATION.md`](../CLIENT_IMPLEMENTATION.md).
### 7.2 Phase T2 — TCP loopback transport
### 7.2 Phase T2 — TCP transport
Smallest IP-based step; on-ramp for non-Linux clients and for FIPS later.
- New CLI: `nsigner --listen tcp:127.0.0.1:PORT`.
- Default-deny non-loopback binds (reject `0.0.0.0` / non-`127.x` / non-`::1` unless `--allow-remote`, see T3).
- Caller identity for loopback: `tcp_local { addr }`. Approval prompt still mandatory.
- New CLI: `nsigner --listen tcp:HOST:PORT` (IPv4 literal) or `nsigner --listen tcp:[IPv6]:PORT`.
- Current behavior: accepts operator-selected local/remote bind addresses (including `[::]` and `fd..`), pending later transport hardening.
- Caller identity for TCP: endpoint address/port in caller descriptor. Approval prompt still mandatory.
- `nsigner list` extended to enumerate active TCP listeners (from internal registry; not from `/proc/net/tcp`).
- Same framing as AF_UNIX path; no protocol changes.
- Tests: integration coverage that spawns a child signer with `--listen tcp:127.0.0.1:0`, captures the bound port, runs the same NIP-04/NIP-44/sign_event matrix as AF_UNIX.
- Tests: integration coverage that spawns a child signer with `--listen tcp:127.0.0.1:0` (and one IPv6 case), captures the bound port, runs the same NIP-04/NIP-44/sign_event matrix as AF_UNIX.
- Docs: extend [`documents/CLIENT_IMPLEMENTATION.md`](../documents/CLIENT_IMPLEMENTATION.md) section 2 with `tcp:` discovery rules and section 3 confirming framing parity.
Implementation checklist (Tier-1 delivery):
- [x] Parse `--listen tcp:HOST:PORT` in [`src/main.c`](../src/main.c).
- [x] Reject invalid/non-loopback listen targets in [`src/server.c`](../src/server.c).
- [x] Parse and validate literal IPv4/IPv6 listen targets in [`src/server.c`](../src/server.c).
- [x] Bind/listen non-blocking TCP sockets and run server loop without TUI dependence.
- [x] Keep existing framed JSON-RPC protocol unchanged via shared [`src/transport_frame.c`](../src/transport_frame.c).
- [ ] Add integration test coverage for `tcp:127.0.0.1:PORT` request flow.
@@ -250,7 +250,7 @@ Only after T2 is solid.
FIPS is a *substrate* for an existing TCP listener, not a new transport in nsigner code.
- Deployment topology: nsigner binds TCP loopback inside the FIPS network namespace (or on a host where `fips0` is up); peers reach it via `fd00::/8` IPv6 derived from the signer's npub.
- Deployment topology: nsigner binds a chosen TCP endpoint inside the FIPS network namespace (or on a host where `fips0` is up); peers reach it via `fd00::/8` IPv6 derived from the signer's npub.
- Optional `caller_kind=fips` enrichment: a small sidecar query (`fipsctl show sessions` style) maps the connecting IPv6 address to a peer npub and feeds it into `caller_identity_t.fips { peer_npub }`. If unavailable, fall back to `tcp_remote` identity.
- nsigner does not embed FIPS, does not depend on libfips, and does not require Rust.
- New optional flag: `--peer-id-source fips:/var/run/fips/fips.sock` (path/method TBD per FIPS API).
@@ -259,7 +259,7 @@ FIPS is a *substrate* for an existing TCP listener, not a new transport in nsign
Execution tasks for initial FIPS trial:
- [x] Deliver T2 TCP loopback listener as FIPS substrate prerequisite.
- [x] Deliver T2 TCP listener as FIPS substrate prerequisite.
- [x] Document signer/caller qube deployment flow in [`documents/FIPS_DEPLOYMENT.md`](../documents/FIPS_DEPLOYMENT.md).
- [ ] Add two-node operator validation script (manual) using `fipsctl` + framed JSON-RPC client.
- [ ] Evaluate optional caller identity enrichment from FIPS session metadata.

View File

@@ -451,8 +451,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 6
#define NSIGNER_VERSION "v0.0.6"
#define NSIGNER_VERSION_PATCH 9
#define NSIGNER_VERSION "v0.0.9"
/* NSIGNER_HEADERLESS_DECLS_END */
@@ -746,6 +746,28 @@ static void activity_log_cb(const char *message, void *user_data) {
g_activity_log.count++;
}
static void tcp_activity_stdout_cb(const char *message, void *user_data) {
time_t now;
struct tm tm_now;
char ts[32];
(void)user_data;
if (message == NULL) {
return;
}
now = time(NULL);
if (localtime_r(&now, &tm_now) != NULL) {
(void)strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M:%S", &tm_now);
} else {
strncpy(ts, "0000-00-00 00:00:00", sizeof(ts) - 1);
ts[sizeof(ts) - 1] = '\0';
}
printf("[%s] %s\n", ts, message);
fflush(stdout);
}
static void render_status(const role_table_t *role_table,
const mnemonic_state_t *mnemonic,
int derived_count,
@@ -1141,7 +1163,7 @@ int main(int argc, char *argv[]) {
break;
}
if (prc > 0 && (pfds[0].revents & POLLIN)) {
if (server_handle_one(&server, NULL, NULL) < 0) {
if (server_handle_one(&server, tcp_activity_stdout_cb, NULL) < 0) {
break;
}
}

View File

@@ -469,6 +469,157 @@ int transport_recv_framed(int fd, char **out_payload, size_t max_size);
static int g_prompt_always_allow = 0;
static int g_noninteractive_prompt_default = -1;
static int caller_id_extract_ipv6(const char *caller_id, char *out_ipv6, size_t out_sz) {
const char *start;
const char *end;
size_t len;
if (caller_id == NULL || out_ipv6 == NULL || out_sz == 0) {
return -1;
}
if (strncmp(caller_id, "tcp:[", 5) != 0) {
return -1;
}
start = caller_id + 5;
end = strchr(start, ']');
if (end == NULL || end[1] != ':') {
return -1;
}
len = (size_t)(end - start);
if (len == 0 || len >= out_sz) {
return -1;
}
memcpy(out_ipv6, start, len);
out_ipv6[len] = '\0';
return 0;
}
static int read_cmd_output(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_fips_peer_for_ipv6(const char *ipv6,
char *out_npub,
size_t out_npub_sz,
char *out_name,
size_t out_name_sz) {
char *json = NULL;
cJSON *root = NULL;
cJSON *peers = NULL;
int i;
if (ipv6 == NULL || out_npub == NULL || out_npub_sz == 0 || out_name == NULL || out_name_sz == 0) {
return -1;
}
out_npub[0] = '\0';
out_name[0] = '\0';
if (read_cmd_output("fipsctl show peers 2>/dev/null", &json) != 0) {
return -1;
}
root = cJSON_Parse(json);
free(json);
if (root == NULL) {
return -1;
}
peers = cJSON_GetObjectItemCaseSensitive(root, "peers");
if (!cJSON_IsArray(peers)) {
cJSON_Delete(root);
return -1;
}
for (i = 0; i < cJSON_GetArraySize(peers); ++i) {
cJSON *peer = cJSON_GetArrayItem(peers, i);
cJSON *peer_ip;
cJSON *peer_npub;
cJSON *peer_name;
if (!cJSON_IsObject(peer)) {
continue;
}
peer_ip = cJSON_GetObjectItemCaseSensitive(peer, "ipv6_addr");
if (!cJSON_IsString(peer_ip) || peer_ip->valuestring == NULL) {
continue;
}
if (strcmp(peer_ip->valuestring, ipv6) != 0) {
continue;
}
peer_npub = cJSON_GetObjectItemCaseSensitive(peer, "npub");
peer_name = cJSON_GetObjectItemCaseSensitive(peer, "display_name");
if (cJSON_IsString(peer_npub) && peer_npub->valuestring != NULL) {
strncpy(out_npub, peer_npub->valuestring, out_npub_sz - 1);
out_npub[out_npub_sz - 1] = '\0';
}
if (cJSON_IsString(peer_name) && peer_name->valuestring != NULL) {
strncpy(out_name, peer_name->valuestring, out_name_sz - 1);
out_name[out_name_sz - 1] = '\0';
}
cJSON_Delete(root);
return (out_npub[0] != '\0') ? 0 : -1;
}
cJSON_Delete(root);
return -1;
}
void server_set_prompt_always_allow(int enabled) {
g_prompt_always_allow = enabled ? 1 : 0;
}
@@ -501,6 +652,20 @@ static int prompt_for_policy_decision(const caller_identity_t *caller,
printf("\nApproval required\n");
printf("caller: %s\n", (caller != NULL) ? caller->caller_id : "unknown");
if (caller != NULL && caller->kind == NSIGNER_LISTEN_TCP) {
char ipv6[INET6_ADDRSTRLEN];
char npub[128];
char display_name[128];
if (caller_id_extract_ipv6(caller->caller_id, ipv6, sizeof(ipv6)) == 0 &&
lookup_fips_peer_for_ipv6(ipv6, npub, sizeof(npub), display_name, sizeof(display_name)) == 0) {
if (display_name[0] != '\0') {
printf("fips peer: %s (%s)\n", npub, display_name);
} else {
printf("fips peer: %s\n", npub);
}
}
}
printf("method: %s\n", (method != NULL) ? method : "unknown");
printf("role: %s\n", (role_name != NULL) ? role_name : "unknown");
printf("purpose: %s\n", (purpose != NULL) ? purpose : "unknown");
@@ -601,17 +766,17 @@ static int parse_tcp_target(const char *target,
return -1;
}
if (strcmp(out_host, "::1") == 0) {
*out_family = AF_INET6;
} else {
{
struct in6_addr addr6;
struct in_addr addr4;
if (inet_pton(AF_INET, out_host, &addr4) != 1) {
if (inet_pton(AF_INET6, out_host, &addr6) == 1) {
*out_family = AF_INET6;
} else if (inet_pton(AF_INET, out_host, &addr4) == 1) {
*out_family = AF_INET;
} else {
return -1;
}
if ((ntohl(addr4.s_addr) & 0xff000000U) != 0x7f000000U) {
return -2;
}
*out_family = AF_INET;
}
*out_port = (uint16_t)port_long;
@@ -746,17 +911,10 @@ int server_start(server_ctx_t *ctx) {
int one = 1;
int prc = parse_tcp_target(ctx->socket_name, &family, host, sizeof(host), &port);
if (prc == -2) {
(void)snprintf(ctx->last_error,
sizeof(ctx->last_error),
"non-loopback TCP bind denied: %s (use 127.x.x.x or ::1)",
ctx->socket_name);
return -1;
}
if (prc != 0) {
(void)snprintf(ctx->last_error,
sizeof(ctx->last_error),
"invalid tcp listen target: %s (expected tcp:127.0.0.1:PORT or tcp:[::1]:PORT)",
"invalid tcp listen target: %s (expected tcp:IPv4:PORT or tcp:[IPv6]:PORT)",
ctx->socket_name);
return -1;
}