feat: parliament daemon, login gate, peer context sync (0.9.13)
Login claims the chair, writes a session ticket, and starts a background service for discovery plus revision-based context sync. Password Realm opens require --login (or CST_REALM_PASSWORD); --service/--service-stop manage the resident agent. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,321 @@
|
||||
#ifndef _DEFAULT_SOURCE
|
||||
#define _DEFAULT_SOURCE
|
||||
#endif
|
||||
#include "cst_parliament.h"
|
||||
#include "cst_discover.h"
|
||||
#include "cst_net.h"
|
||||
#include "cst_peer.h"
|
||||
#include "cst_realm.h"
|
||||
#include "cst_util.h"
|
||||
#include "monocypher.h"
|
||||
#include "os_compat.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
static volatile int g_stop;
|
||||
|
||||
#ifndef _WIN32
|
||||
static void on_sig(int sig) {
|
||||
(void)sig;
|
||||
g_stop = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
int cst_parliament_daemon_pidfile(char *out, size_t n) {
|
||||
char dir[OS_PATH_MAX];
|
||||
if (cst_realm_dir(dir, sizeof(dir)) != 0)
|
||||
return -1;
|
||||
return os_path_join(out, n, dir, "parliament.pid");
|
||||
}
|
||||
|
||||
static int read_pid(void) {
|
||||
char path[OS_PATH_MAX];
|
||||
if (cst_parliament_daemon_pidfile(path, sizeof(path)) != 0)
|
||||
return -1;
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f)
|
||||
return -1;
|
||||
int pid = -1;
|
||||
if (fscanf(f, "%d", &pid) != 1)
|
||||
pid = -1;
|
||||
fclose(f);
|
||||
return pid;
|
||||
}
|
||||
|
||||
static int write_pid(int pid) {
|
||||
char path[OS_PATH_MAX], tmp[OS_PATH_MAX + 8];
|
||||
if (cst_parliament_daemon_pidfile(path, sizeof(path)) != 0)
|
||||
return -1;
|
||||
if (snprintf(tmp, sizeof(tmp), "%s.tmp", path) >= (int)sizeof(tmp))
|
||||
return -1;
|
||||
FILE *f = fopen(tmp, "wb");
|
||||
if (!f)
|
||||
return -1;
|
||||
int ok = fprintf(f, "%d\n", pid) > 0;
|
||||
if (fclose(f) != 0)
|
||||
ok = 0;
|
||||
if (!ok) {
|
||||
remove(tmp);
|
||||
return -1;
|
||||
}
|
||||
return os_rename_replace(tmp, path);
|
||||
}
|
||||
|
||||
static void clear_pid(void) {
|
||||
char path[OS_PATH_MAX];
|
||||
if (cst_parliament_daemon_pidfile(path, sizeof(path)) == 0)
|
||||
remove(path);
|
||||
}
|
||||
|
||||
int cst_parliament_daemon_running(void) {
|
||||
int pid = read_pid();
|
||||
if (pid <= 0)
|
||||
return 0;
|
||||
#ifdef _WIN32
|
||||
HANDLE h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, (DWORD)pid);
|
||||
if (!h)
|
||||
return 0;
|
||||
CloseHandle(h);
|
||||
return 1;
|
||||
#else
|
||||
return kill(pid, 0) == 0;
|
||||
#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
|
||||
}
|
||||
|
||||
static int load_local_bundle(cst_peer_sync_bundle_t *b) {
|
||||
memset(b, 0, sizeof(*b));
|
||||
cst_parliament_meta_t m;
|
||||
if (cst_parliament_load(&m) != 0)
|
||||
return -1;
|
||||
b->revision = m.revision;
|
||||
snprintf(b->parliament_id, sizeof(b->parliament_id), "%s", m.parliament_id);
|
||||
snprintf(b->project_id, sizeof(b->project_id), "%s", m.project_id);
|
||||
snprintf(b->chair_instance, sizeof(b->chair_instance), "%s",
|
||||
m.chair_instance);
|
||||
b->context = cst_parliament_context_get();
|
||||
return b->context ? 0 : -1;
|
||||
}
|
||||
|
||||
static int apply_if_newer(cst_peer_sync_bundle_t *b, unsigned long before) {
|
||||
if (b->revision <= before)
|
||||
return 0;
|
||||
return cst_parliament_apply_sync(b->revision, b->parliament_id, b->project_id,
|
||||
b->chair_instance, b->context);
|
||||
}
|
||||
|
||||
static int sync_on_session(cst_peer_session_t *sess) {
|
||||
cst_peer_sync_bundle_t local;
|
||||
if (load_local_bundle(&local) != 0)
|
||||
return -1;
|
||||
unsigned long before = local.revision;
|
||||
char err[128];
|
||||
int rc = cst_peer_sync(sess, &local, 15000, err, sizeof(err));
|
||||
if (rc == 0)
|
||||
apply_if_newer(&local, before);
|
||||
free(local.context);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int unlock_realm(const char *password, cst_realm_keys_t *keys) {
|
||||
char dir[OS_PATH_MAX];
|
||||
if (cst_realm_dir(dir, sizeof(dir)) != 0)
|
||||
return -1;
|
||||
if (cst_realm_is_password_mode(dir))
|
||||
return cst_realm_unlock_password(dir, password, keys);
|
||||
return cst_realm_load(dir, keys);
|
||||
}
|
||||
|
||||
int cst_parliament_daemon_run(const char *password, const char *instance_id,
|
||||
int peer_port, char *err, size_t err_sz) {
|
||||
if (err && err_sz)
|
||||
err[0] = 0;
|
||||
if (!instance_id || !instance_id[0]) {
|
||||
if (err && err_sz)
|
||||
snprintf(err, err_sz, "instance id required");
|
||||
return -1;
|
||||
}
|
||||
cst_realm_keys_t realm;
|
||||
if (unlock_realm(password, &realm) != 0) {
|
||||
if (err && err_sz)
|
||||
snprintf(err, err_sz, "cannot unlock realm");
|
||||
return -1;
|
||||
}
|
||||
cst_realm_cert_t cert;
|
||||
uint8_t sk[64];
|
||||
if (cst_realm_instance_load(instance_id, &cert, sk) != 0) {
|
||||
if (err && err_sz)
|
||||
snprintf(err, err_sz, "instance not enrolled; login first");
|
||||
crypto_wipe(realm.seed, sizeof(realm.seed));
|
||||
crypto_wipe(realm.sk, sizeof(realm.sk));
|
||||
return -1;
|
||||
}
|
||||
char fp[33];
|
||||
cst_realm_fingerprint(realm.pk, fp);
|
||||
os_net_init();
|
||||
char nerr[128];
|
||||
os_socket_t ls =
|
||||
os_net_listen("0.0.0.0", peer_port > 0 ? peer_port : 0, nerr, sizeof(nerr));
|
||||
if (ls == OS_SOCKET_BAD) {
|
||||
if (err && err_sz)
|
||||
snprintf(err, err_sz, "listen failed: %s", nerr);
|
||||
crypto_wipe(sk, sizeof(sk));
|
||||
crypto_wipe(realm.seed, sizeof(realm.seed));
|
||||
crypto_wipe(realm.sk, sizeof(realm.sk));
|
||||
return -1;
|
||||
}
|
||||
int bound = os_net_port(ls);
|
||||
cst_discover_t *disc =
|
||||
cst_discover_start(fp, instance_id, instance_id, bound);
|
||||
write_pid(
|
||||
#ifdef _WIN32
|
||||
(int)GetCurrentProcessId()
|
||||
#else
|
||||
(int)getpid()
|
||||
#endif
|
||||
);
|
||||
fprintf(stderr, "parliament_service pid=%d port=%d id=%s realm=%s\n",
|
||||
#ifdef _WIN32
|
||||
(int)GetCurrentProcessId(),
|
||||
#else
|
||||
(int)getpid(),
|
||||
#endif
|
||||
bound, instance_id, fp);
|
||||
fflush(stderr);
|
||||
|
||||
#ifndef _WIN32
|
||||
signal(SIGTERM, on_sig);
|
||||
signal(SIGINT, on_sig);
|
||||
signal(SIGHUP, on_sig);
|
||||
#endif
|
||||
g_stop = 0;
|
||||
int tick = 0;
|
||||
while (!g_stop) {
|
||||
cst_peer_session_t sess;
|
||||
char herr[128];
|
||||
if (cst_peer_accept((intptr_t)ls, &cert, sk, realm.pk, 500, &sess, herr,
|
||||
sizeof(herr)) == 0) {
|
||||
sync_on_session(&sess);
|
||||
cst_peer_close(&sess);
|
||||
}
|
||||
if (++tick % 10 == 0 && disc) {
|
||||
cst_peer_info_t peers[16];
|
||||
int n = cst_discover_peers(disc, peers, 16);
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (peers[i].port <= 0)
|
||||
continue;
|
||||
if (cst_peer_dial(peers[i].host, peers[i].port, &cert, sk, realm.pk,
|
||||
3000, &sess, herr, sizeof(herr)) == 0) {
|
||||
sync_on_session(&sess);
|
||||
cst_peer_close(&sess);
|
||||
}
|
||||
}
|
||||
}
|
||||
sleep_ms(200);
|
||||
}
|
||||
if (disc)
|
||||
cst_discover_stop(disc);
|
||||
os_net_close(ls);
|
||||
clear_pid();
|
||||
crypto_wipe(sk, sizeof(sk));
|
||||
crypto_wipe(realm.seed, sizeof(realm.seed));
|
||||
crypto_wipe(realm.sk, sizeof(realm.sk));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cst_parliament_daemon_stop(void) {
|
||||
int pid = read_pid();
|
||||
if (pid <= 0)
|
||||
return 0;
|
||||
#ifdef _WIN32
|
||||
HANDLE h = OpenProcess(PROCESS_TERMINATE, FALSE, (DWORD)pid);
|
||||
if (h) {
|
||||
TerminateProcess(h, 0);
|
||||
CloseHandle(h);
|
||||
}
|
||||
#else
|
||||
if (kill(pid, SIGTERM) != 0 && errno != ESRCH)
|
||||
return -1;
|
||||
for (int i = 0; i < 25; i++) {
|
||||
if (kill(pid, 0) != 0)
|
||||
break;
|
||||
sleep_ms(100);
|
||||
}
|
||||
if (kill(pid, 0) == 0)
|
||||
kill(pid, SIGKILL);
|
||||
#endif
|
||||
clear_pid();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cst_parliament_daemon_start(const char *password, const char *instance_id,
|
||||
int peer_port, char *err, size_t err_sz) {
|
||||
if (err && err_sz)
|
||||
err[0] = 0;
|
||||
if (cst_parliament_daemon_running())
|
||||
return 0;
|
||||
#ifdef _WIN32
|
||||
/* Windows: run in-process foreground loop under a detached console is
|
||||
* deferred; callers should use --service in a service manager. */
|
||||
if (err && err_sz)
|
||||
snprintf(err, err_sz, "background daemon requires --service on Windows");
|
||||
return -1;
|
||||
#else
|
||||
if (!password || !password[0]) {
|
||||
if (err && err_sz)
|
||||
snprintf(err, err_sz, "password required to start service");
|
||||
return -1;
|
||||
}
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
if (err && err_sz)
|
||||
snprintf(err, err_sz, "fork failed");
|
||||
return -1;
|
||||
}
|
||||
if (pid > 0) {
|
||||
/* Parent: wait briefly for pidfile. */
|
||||
for (int i = 0; i < 50; i++) {
|
||||
if (cst_parliament_daemon_running())
|
||||
return 0;
|
||||
sleep_ms(50);
|
||||
}
|
||||
if (err && err_sz)
|
||||
snprintf(err, err_sz, "service did not start");
|
||||
return -1;
|
||||
}
|
||||
/* Child: detach. */
|
||||
setsid();
|
||||
int fd = open("/dev/null", O_RDWR);
|
||||
if (fd >= 0) {
|
||||
dup2(fd, 0);
|
||||
/* Keep stderr for service banner until closed by operator logs. */
|
||||
if (fd > 2)
|
||||
close(fd);
|
||||
}
|
||||
char derr[256];
|
||||
(void)cst_parliament_daemon_run(password, instance_id, peer_port, derr,
|
||||
sizeof(derr));
|
||||
_exit(0);
|
||||
#endif
|
||||
}
|
||||
Reference in New Issue
Block a user