#ifndef _DEFAULT_SOURCE #define _DEFAULT_SOURCE #endif #include "cst_parliament_cli.h" #include "cst_agent.h" #include "cst_discover.h" #include "cst_net.h" #include "cst_parliament.h" #include "cst_peer.h" #include "cst_realm.h" #include "os_compat.h" #include #include #include #include #ifdef _WIN32 #include #else #include #endif static void sleep_ms(int ms) { #ifdef _WIN32 Sleep((DWORD)ms); #else struct timespec ts = {ms / 1000, (ms % 1000) * 1000000L}; nanosleep(&ts, NULL); #endif } /* Prefer CST_REALM_PASSWORD; otherwise prompt on a TTY. */ static int resolve_password(char *buf, size_t n, const char **out) { const char *env = getenv("CST_REALM_PASSWORD"); if (env && env[0]) { *out = env; return 0; } if (cst_prompt_secret("Realm password (input hidden): ", buf, n) != 0) { fprintf(stderr, "password required (set CST_REALM_PASSWORD or type it)\n"); return -1; } *out = buf; return 0; } int cst_parliament_cli_realm_new(void) { char dir[OS_PATH_MAX], fp[33]; if (cst_realm_dir(dir, sizeof(dir)) != 0) { fprintf(stderr, "cannot resolve realm directory\n"); return 1; } cst_realm_keys_t k; if (cst_realm_generate(&k) != 0 || cst_realm_save(dir, &k) != 0) { fprintf(stderr, "cannot create realm keys in %s\n", dir); return 1; } cst_realm_fingerprint(k.pk, fp); printf("realm_created dir=%s fingerprint=%s\n", dir, fp); return 0; } int cst_parliament_cli_realm_password_init(const char *password) { char pwbuf[256]; const char *pw = password; if ((!pw || !pw[0]) && resolve_password(pwbuf, sizeof(pwbuf), &pw) != 0) return 1; char dir[OS_PATH_MAX], fp[33]; if (cst_realm_dir(dir, sizeof(dir)) != 0) { fprintf(stderr, "cannot resolve realm directory\n"); return 1; } if (cst_realm_init_password(dir, pw) != 0) { fprintf(stderr, "password realm init failed\n"); return 1; } cst_realm_keys_t k; if (cst_realm_unlock_password(dir, pw, &k) != 0) return 1; cst_realm_fingerprint(k.pk, fp); printf("realm_password_mode dir=%s fingerprint=%s\n", dir, fp); if (pw == pwbuf) memset(pwbuf, 0, sizeof(pwbuf)); return 0; } int cst_parliament_cli_login(const char *password, const char *instance_id, const char *device_label, int peer_port) { return cst_parliament_cli_login_and_serve(password, instance_id, device_label, peer_port > 0 ? peer_port : 9100); } int cst_parliament_cli_login_and_serve(const char *password, const char *instance_id, const char *device_label, int peer_port) { char err[256], pwbuf[256]; const char *pw = password; if ((!pw || !pw[0]) && resolve_password(pwbuf, sizeof(pwbuf), &pw) != 0) return 1; char *hand = cst_parliament_login(pw, instance_id, device_label, err, sizeof(err)); if (!hand) { fprintf(stderr, "%s\n", err[0] ? err : "login failed"); if (pw == pwbuf) memset(pwbuf, 0, sizeof(pwbuf)); return 1; } fputs(hand, stdout); free(hand); if (cst_parliament_daemon_start(pw, instance_id, peer_port > 0 ? peer_port : 9100, err, sizeof(err)) != 0) { fprintf(stderr, "warning: service: %s (chair login still valid)\n", err[0] ? err : "start failed"); printf("service=failed\n"); if (pw == pwbuf) memset(pwbuf, 0, sizeof(pwbuf)); return 0; } if (cst_parliament_daemon_running()) printf("service=running\n"); if (pw == pwbuf) memset(pwbuf, 0, sizeof(pwbuf)); return 0; } int cst_parliament_cli_service(const char *password, const char *instance_id, int peer_port, int foreground) { char err[256], pwbuf[256]; const char *pw = password; if (!instance_id || !instance_id[0]) { fprintf(stderr, "instance id required\n"); return 1; } if ((!pw || !pw[0]) && resolve_password(pwbuf, sizeof(pwbuf), &pw) != 0) return 1; int rc = 0; if (foreground) { if (cst_parliament_daemon_run(pw, instance_id, peer_port > 0 ? peer_port : 9100, err, sizeof(err)) != 0) { fprintf(stderr, "%s\n", err[0] ? err : "service failed"); rc = 1; } } else if (cst_parliament_daemon_start(pw, instance_id, peer_port > 0 ? peer_port : 9100, err, sizeof(err)) != 0) { fprintf(stderr, "%s\n", err[0] ? err : "service start failed"); rc = 1; } else { printf("service=running\n"); } if (pw == pwbuf) memset(pwbuf, 0, sizeof(pwbuf)); return rc; } int cst_parliament_cli_service_stop(void) { if (cst_parliament_daemon_stop() != 0) { fprintf(stderr, "service stop failed\n"); return 1; } printf("service=stopped\n"); return 0; } int cst_parliament_cli_new_project(const char *project_id) { if (cst_parliament_new_project(project_id) != 0) { fprintf(stderr, "new project failed\n"); return 1; } printf("parliament_new_project id=%s\n", project_id && project_id[0] ? project_id : "default"); return 0; } int cst_parliament_cli_realm_enroll(const char *instance_id, const char *label) { if (!instance_id || !instance_id[0]) { fprintf(stderr, "instance id required\n"); return 1; } char dir[OS_PATH_MAX]; cst_realm_keys_t k; if (cst_realm_dir(dir, sizeof(dir)) != 0 || cst_realm_load(dir, &k) != 0) { fprintf(stderr, "load realm keys failed (run --realm-new first)\n"); return 1; } cst_realm_cert_t cert; uint8_t seed[32], sk[64]; if (cst_realm_enroll(&k, instance_id, label, 0, &cert, seed, sk) != 0 || cst_realm_instance_save(instance_id, &cert, seed) != 0) { fprintf(stderr, "enroll/save failed\n"); return 1; } char fp[33]; cst_realm_fingerprint(k.pk, fp); printf("enrolled id=%s label=%s realm=%s\n", instance_id, label ? label : "", fp); return 0; } static int load_realm_for_cli(cst_realm_keys_t *realm) { char dir[OS_PATH_MAX]; if (cst_realm_dir(dir, sizeof(dir)) != 0) return -1; if (cst_realm_is_password_mode(dir)) { const char *pw = getenv("CST_REALM_PASSWORD"); if (!pw || !pw[0]) return -1; return cst_realm_unlock_password(dir, pw, realm); } return cst_realm_load(dir, realm); } int cst_parliament_cli_peer_listen(int port, const char *instance_id) { if (!instance_id || !instance_id[0]) { fprintf(stderr, "instance id required\n"); return 1; } char dir[OS_PATH_MAX], err[256]; cst_realm_keys_t realm; cst_realm_cert_t cert; uint8_t sk[64]; if (cst_realm_dir(dir, sizeof(dir)) != 0 || load_realm_for_cli(&realm) != 0 || cst_realm_instance_load(instance_id, &cert, sk) != 0) { fprintf(stderr, "cannot load realm/instance %s\n", instance_id); return 1; } if (cst_realm_revoked(dir, instance_id)) { fprintf(stderr, "instance revoked\n"); return 1; } os_net_init(); os_socket_t ls = os_net_listen("0.0.0.0", port > 0 ? port : 0, err, sizeof(err)); if (ls == OS_SOCKET_BAD) { fprintf(stderr, "listen failed: %s\n", err); return 1; } int bound = os_net_port(ls); printf("peer_listen port=%d id=%s\n", bound, instance_id); fflush(stdout); cst_peer_session_t sess; if (cst_peer_accept((intptr_t)ls, &cert, sk, realm.pk, 60000, &sess, err, sizeof(err)) != 0) { fprintf(stderr, "accept/handshake failed: %s\n", err); os_net_close(ls); return 1; } printf("peer_ok remote=%s host=%s sealed=%d\n", sess.remote.instance_id, sess.host, sess.sealed); fflush(stdout); { cst_peer_sync_bundle_t local = {0}; cst_parliament_meta_t m; memset(&m, 0, sizeof(m)); (void)cst_parliament_load(&m); unsigned long before = m.revision; local.revision = m.revision; snprintf(local.parliament_id, sizeof(local.parliament_id), "%s", m.parliament_id); snprintf(local.project_id, sizeof(local.project_id), "%s", m.project_id); snprintf(local.chair_instance, sizeof(local.chair_instance), "%s", m.chair_instance); local.context = cst_parliament_context_get(); if (cst_peer_sync(&sess, &local, 15000, err, sizeof(err)) != 0) fprintf(stderr, "peer_sync failed: %s\n", err); else { if (local.revision > before) (void)cst_parliament_apply_sync(local.revision, local.parliament_id, local.project_id, local.chair_instance, local.context); printf("peer_sync rev=%lu sealed=1\n", local.revision); } free(local.context); } cst_peer_close(&sess); os_net_close(ls); return 0; } int cst_parliament_cli_peer_dial(const char *host, int port, const char *instance_id) { if (!host || port <= 0 || !instance_id) { fprintf(stderr, "host, port and instance id required\n"); return 1; } char dir[OS_PATH_MAX], err[256]; cst_realm_keys_t realm; cst_realm_cert_t cert; uint8_t sk[64]; if (cst_realm_dir(dir, sizeof(dir)) != 0 || load_realm_for_cli(&realm) != 0 || cst_realm_instance_load(instance_id, &cert, sk) != 0) { fprintf(stderr, "cannot load realm/instance %s\n", instance_id); return 1; } cst_peer_session_t sess; if (cst_peer_dial(host, port, &cert, sk, realm.pk, 30000, &sess, err, sizeof(err)) != 0) { fprintf(stderr, "dial/handshake failed: %s\n", err); return 1; } printf("peer_ok remote=%s sealed=%d\n", sess.remote.instance_id, sess.sealed); fflush(stdout); { cst_peer_sync_bundle_t local = {0}; cst_parliament_meta_t m; memset(&m, 0, sizeof(m)); (void)cst_parliament_load(&m); unsigned long before = m.revision; local.revision = m.revision; snprintf(local.parliament_id, sizeof(local.parliament_id), "%s", m.parliament_id); snprintf(local.project_id, sizeof(local.project_id), "%s", m.project_id); snprintf(local.chair_instance, sizeof(local.chair_instance), "%s", m.chair_instance); local.context = cst_parliament_context_get(); if (cst_peer_sync(&sess, &local, 15000, err, sizeof(err)) != 0) fprintf(stderr, "peer_sync failed: %s\n", err); else { if (local.revision > before) (void)cst_parliament_apply_sync(local.revision, local.parliament_id, local.project_id, local.chair_instance, local.context); printf("peer_sync rev=%lu sealed=1\n", local.revision); } free(local.context); } cst_peer_close(&sess); return 0; } int cst_parliament_cli_discover(const char *instance_id, int peer_port, int wait_ms) { if (!instance_id || peer_port <= 0) { fprintf(stderr, "instance id and peer port required\n"); return 1; } char dir[OS_PATH_MAX], fp[33]; cst_realm_keys_t realm; if (cst_realm_dir(dir, sizeof(dir)) != 0 || load_realm_for_cli(&realm) != 0 || cst_realm_fingerprint(realm.pk, fp) != 0) { fprintf(stderr, "cannot load realm\n"); return 1; } cst_discover_t *d = cst_discover_start(fp, instance_id, instance_id, peer_port); if (!d) { fprintf(stderr, "discover start failed\n"); return 1; } int left = wait_ms > 0 ? wait_ms : 3000; while (left > 0) { cst_peer_info_t peers[16]; int n = cst_discover_peers(d, peers, 16); for (int i = 0; i < n; i++) { printf("peer iid=%s host=%s port=%d name=%s\n", peers[i].instance_id, peers[i].host, peers[i].port, peers[i].name); } if (n > 0) break; sleep_ms(200); left -= 200; } cst_discover_stop(d); return 0; }