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>
248 lines
7.9 KiB
C
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;
|
|
}
|