Files
dat-thang/libcess/tools/cesspool-bootstrap.c
T
divorce b735900f2e 0.0.002
ddat interop with dat js
cable interop with cabal js
ddat's frontend = dat-thang
cable fe = cable-thang
dillo support based on cel's dillo-dat
bootstrap node
xplatform support

Signed-off-by: divo <418@cryspace.space>
2026-10-19 00:17:02 -04:00

248 lines
7.9 KiB
C

#define _POSIX_C_SOURCE 200809L
#include "cess_compat.h"
#include "cesspool.h"
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include <sodium.h>
#ifndef EKEYREJECTED
#define EKEYREJECTED EACCES
#endif
#ifdef _WIN32
#include <sys/stat.h>
#endif
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#endif
#define BOOTSTRAP_PATH_BYTES 1400u
static volatile sig_atomic_t bootstrap_running = 1;
static void stop_bootstrap(int signal_number)
{
(void)signal_number;
bootstrap_running = 0;
}
static void usage(FILE *out)
{
fputs("cesspool-bootstrap - dedicated Cesspool public rendezvous node\n"
"usage: cesspool-bootstrap [--bind IPv4] [--port UDP-PORT]\n"
" [--advertise HOST:PORT] [--state-dir DIR]\n"
" cesspool-bootstrap --help\n"
"Runs in the foreground for a service manager. It handles peer discovery;\n"
"chat and archive payloads remain peer-to-peer.\n", out);
}
static int parse_port(const char *text, uint16_t *port)
{
char *end;
unsigned long value;
if (!text || !*text || !port) { errno = EINVAL; return -1; }
value = strtoul(text, &end, 10);
if (*end || value == 0 || value > 65535) { errno = EINVAL; return -1; }
*port = (uint16_t)value;
return 0;
}
static int make_one_state_directory(const char *path, int secure_existing)
{
struct stat info;
int created = 0;
if (!path || !*path) { errno = EINVAL; return -1; }
if (mkdir(path, 0700) == 0) created = 1;
else if (errno != EEXIST) return -1;
if (stat(path, &info) != 0) return -1;
if (!S_ISDIR(info.st_mode)) { errno = ENOTDIR; return -1; }
if ((created || secure_existing) && chmod(path, S_IRWXU) != 0) return -1;
return 0;
}
static int make_state_directory(const char *path)
{
char copy[BOOTSTRAP_PATH_BYTES];
size_t length, start = 0, i;
if (!path || !*path) { errno = EINVAL; return -1; }
length = strlen(path);
if (length >= sizeof(copy)) { errno = ENAMETOOLONG; return -1; }
memcpy(copy, path, length + 1);
#ifdef _WIN32
if (length >= 3 && copy[1] == ':') start = 3;
#else
if (copy[0] == '/') start = 1;
#endif
for (i = start; i < length; i++) {
if (copy[i] != '/' && copy[i] != '\\') continue;
if (i == 0 || (i == 2 && copy[1] == ':')) continue;
copy[i] = '\0';
if (make_one_state_directory(copy, 0) != 0) return -1;
copy[i] = path[i];
}
return make_one_state_directory(copy, 1);
}
static int default_state_directory(char *out, size_t capacity)
{
const char *base;
int n;
if (!out || capacity == 0) { errno = EINVAL; return -1; }
#ifdef _WIN32
base = getenv("LOCALAPPDATA");
if (!base || !*base) { errno = ENOENT; return -1; }
n = snprintf(out, capacity, "%s/dat-gpt/cesspool-bootstrap-state", base);
#else
base = getenv("XDG_STATE_HOME");
if (base && *base) n = snprintf(out, capacity, "%s/dat-gpt/cesspool-bootstrap", base);
else {
base = getenv("HOME");
if (!base || !*base) { errno = ENOENT; return -1; }
n = snprintf(out, capacity, "%s/.local/state/dat-gpt/cesspool-bootstrap", base);
}
#endif
if (n < 0 || (size_t)n >= capacity) { errno = ENAMETOOLONG; return -1; }
return 0;
}
static int load_or_create_identity(const char *state_dir, cesspool_keypair_t *identity)
{
char key_path[BOOTSTRAP_PATH_BYTES];
uint8_t derived[32];
int n;
if (!state_dir || !identity) { errno = EINVAL; return -1; }
n = snprintf(key_path, sizeof(key_path), "%s/%s", state_dir, "identity.key");
if (n < 0 || (size_t)n >= sizeof(key_path)) { errno = ENAMETOOLONG; return -1; }
if (cesspool_identity_init()) return -1;
if (cesspool_identity_load(key_path, identity) == 0) {
if (crypto_sign_ed25519_sk_to_pk(derived, identity->seckey) != 0 ||
sodium_memcmp(derived, identity->pubkey, sizeof(derived)) != 0) {
sodium_memzero(identity, sizeof(*identity));
errno = EKEYREJECTED;
return -1;
}
if (chmod(key_path, S_IRUSR | S_IWUSR) != 0) return -1;
return 0;
}
if (errno != ENOENT || cesspool_identity_generate(identity) ||
cesspool_identity_save(key_path, identity) != 0 ||
chmod(key_path, S_IRUSR | S_IWUSR) != 0) {
int saved = errno;
sodium_memzero(identity, sizeof(*identity));
errno = saved;
return -1;
}
return 0;
}
static uint64_t monotonic_milliseconds(void)
{
struct timespec now;
if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) return 0;
return (uint64_t)now.tv_sec * 1000u + (uint64_t)now.tv_nsec / 1000000u;
}
int main(int argc, char **argv)
{
cesspool_t pool;
cesspool_keypair_t identity;
const char *bind_host = "0.0.0.0";
const char *state_dir = NULL;
const char *advertise = NULL;
uint16_t port = CESSNET_PORT;
uint64_t next_status = 0;
char default_state[BOOTSTRAP_PATH_BYTES];
int i, result = 1;
memset(&pool, 0, sizeof(pool));
pool.net.sock = CESS_INVALID_SOCKET;
memset(&identity, 0, sizeof(identity));
for (i = 1; i < argc; i++) {
if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")) {
usage(stdout);
return 0;
}
if ((!strcmp(argv[i], "--bind") || !strcmp(argv[i], "--port") ||
!strcmp(argv[i], "--advertise") || !strcmp(argv[i], "--state-dir")) && i + 1 >= argc) {
fprintf(stderr, "cesspool-bootstrap: %s needs a value\n", argv[i]);
return 2;
}
if (!strcmp(argv[i], "--bind")) bind_host = argv[++i];
else if (!strcmp(argv[i], "--port")) {
if (parse_port(argv[++i], &port)) { perror("cesspool-bootstrap --port"); return 2; }
} else if (!strcmp(argv[i], "--advertise")) advertise = argv[++i];
else if (!strcmp(argv[i], "--state-dir")) state_dir = argv[++i];
else {
fprintf(stderr, "cesspool-bootstrap: unknown option %s\n", argv[i]);
usage(stderr);
return 2;
}
}
if (!state_dir) {
if (default_state_directory(default_state, sizeof(default_state)) != 0) {
perror("cesspool-bootstrap default state directory");
return 1;
}
state_dir = default_state;
}
if (!bind_host[0] || strchr(bind_host, ':')) {
fputs("cesspool-bootstrap: --bind requires an IPv4 host or 0.0.0.0\n", stderr);
return 2;
}
(void)umask(077);
if (make_state_directory(state_dir) || load_or_create_identity(state_dir, &identity) ||
cesspool_init(&pool, identity.pubkey) ||
cesspool_listen(&pool, bind_host, port) ||
cesspool_set_bootstrap_server(&pool, 1)) {
perror("cesspool-bootstrap startup");
goto done;
}
signal(SIGINT, stop_bootstrap);
signal(SIGTERM, stop_bootstrap);
printf("Cesspool public bootstrap ready\n"
" bind: %s:%u/udp\n"
" state: persistent private identity loaded\n"
" peer discovery only; peer payloads are not relayed\n",
bind_host, pool.port);
if (advertise && *advertise)
printf(" advertised endpoint: %s/udp\n"
" client setup: dat bootstrap set %s\n"
" client setup: cable bootstrap set %s\n"
" GUI setup: Settings > Cesspool Node > %s\n",
advertise, advertise, advertise, advertise);
else printf(" advertised endpoint: not set; configure a public host:port in clients\n");
printf(" Windows firewall (run elevated): netsh advfirewall firewall add rule name=\"Cesspool UDP %u\" dir=in action=allow protocol=UDP localport=%u\n",
pool.port, pool.port);
puts(" route the same UDP port through the edge router or cloud firewall");
fflush(stdout);
while (bootstrap_running) {
int polled = cesspool_poll(&pool, 1000);
uint64_t now = monotonic_milliseconds();
if (polled < 0 && errno != EINTR) {
perror("cesspool-bootstrap poll");
goto done;
}
if (cesspool_tick(&pool, now) != 0) {
perror("cesspool-bootstrap maintenance");
goto done;
}
if (now >= next_status) {
fprintf(stderr, "cesspool-bootstrap: peers=%lu topics=%lu\n",
(unsigned long)cesspool_announced_peer_count(&pool),
(unsigned long)cesspool_announced_topic_count(&pool));
next_status = now + 60000u;
}
}
result = 0;
done:
if (pool.net.sock != CESS_INVALID_SOCKET) (void)cesspool_destroy(&pool);
sodium_memzero(&identity, sizeof(identity));
return result;
}