ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
discovery-service/main.c
Go to the documentation of this file.
1
10#include <stdio.h>
11#include <stdlib.h>
12#include <string.h>
13#include <stdbool.h>
14#include <signal.h>
15#include <errno.h>
16
22#include <ascii-chat/common.h>
23#include <ascii-chat/version.h>
24#include <ascii-chat/log/log.h>
30
31/* ============================================================================
32 * Static State
33 * ============================================================================ */
34
35static acds_server_t g_server;
36
37/* ============================================================================
38 * server_like Callbacks
39 * ============================================================================ */
40
41static void acds_interrupt_fn(int sig) {
42 (void)sig;
43
45 if (tcp) {
46 atomic_store_bool(&tcp->running, false);
47 // Close listen sockets to interrupt select()
51 }
55 }
56 }
57}
58
59static asciichat_error_t acds_init_fn(void *user_data) {
60 (void)user_data;
61 asciichat_error_t result;
62
63 const options_t *opts = options_get();
64
65 log_info("ascii-chat Discovery Service (acds) starting...");
67
68 // Initialize session string generator (libsodium)
69 result = acds_string_init();
70 if (result != ASCIICHAT_OK) {
71 log_error("Failed to initialize session string generator");
72 return result;
73 }
74
75 // Load or generate identity keys
76 uint8_t public_key[32];
77 uint8_t secret_key[64];
78
79 const char *acds_key_path = GET_OPTION(encrypt_key);
80 char default_key_path[PLATFORM_MAX_PATH_LENGTH] = {0};
81 if (!acds_key_path || acds_key_path[0] == '\0') {
82 const char *config_dir = get_config_dir();
83 if (config_dir) {
84 safe_snprintf(default_key_path, sizeof(default_key_path), "%sdiscovery_identity", config_dir);
85 acds_key_path = default_key_path;
86 }
87 }
88 log_info("Loading identity key from %s", acds_key_path);
89 result = acds_identity_load(acds_key_path, public_key, secret_key);
90
91 if (result != ASCIICHAT_OK) {
92 log_info("Identity key not found, generating new key...");
93
94 result = acds_identity_generate(public_key, secret_key);
95 if (result != ASCIICHAT_OK) {
96 log_error("Failed to generate identity key");
97 return result;
98 }
99
100 result = acds_identity_save(acds_key_path, public_key, secret_key);
101 if (result != ASCIICHAT_OK) {
102 log_error("Failed to save identity key to %s", acds_key_path);
103 return result;
104 }
105
106 log_info("Saved new identity key to %s", acds_key_path);
107 }
108
109 // Display server fingerprint
110 char fingerprint[65];
111 acds_identity_fingerprint(public_key, fingerprint);
112 log_info("Discovery server identity: SHA256:%s", fingerprint);
113 char msg[256];
114 safe_snprintf(msg, sizeof(msg), "Server fingerprint: SHA256:%s", fingerprint);
115 log_console(LOG_INFO, msg);
116
117 // Create config from options for server initialization
118 acds_config_t config = {0};
119 int port_num = GET_OPTION(port);
120 if (port_num < 1 || port_num > 65535) {
121 log_error("Invalid port: %d (must be 1-65535)", port_num);
122 return ERROR_INVALID_PARAM;
123 }
124 config.port = port_num;
125
126 const char *address = opts && opts->address[0] != '\0' ? opts->address : "127.0.0.1";
127 const char *address6 = opts && opts->address6[0] != '\0' ? opts->address6 : "::1";
128 const char *log_file = opts && opts->log_file[0] != '\0' ? opts->log_file : "acds.log";
129 SAFE_STRNCPY(config.address, address, sizeof(config.address));
130 SAFE_STRNCPY(config.address6, address6, sizeof(config.address6));
131 SAFE_STRNCPY(config.database_path, GET_OPTION(discovery_database_path), sizeof(config.database_path));
132 SAFE_STRNCPY(config.key_path, acds_key_path, sizeof(config.key_path));
133 SAFE_STRNCPY(config.log_file, log_file, sizeof(config.log_file));
134 config.log_level = GET_OPTION(log_level);
135 config.require_server_identity = GET_OPTION(require_server_identity) != 0;
136 config.require_client_identity = GET_OPTION(require_client_identity) != 0;
137
138 if (config.require_server_identity) {
139 log_info("Security: Requiring signed identity from servers creating sessions");
140 }
141 if (config.require_client_identity) {
142 log_info("Security: Requiring signed identity from clients joining sessions");
143 }
144
145 // Parse STUN servers from comma-separated list
146 config.stun_count = 0;
147 memset(config.stun_servers, 0, sizeof(config.stun_servers));
148 const char *stun_servers_str = GET_OPTION(stun_servers);
149 if (stun_servers_str && stun_servers_str[0] != '\0') {
150 char stun_copy[OPTIONS_BUFF_SIZE];
151 SAFE_STRNCPY(stun_copy, stun_servers_str, sizeof(stun_copy));
152
153 char *saveptr = NULL;
154 char *token = platform_strtok_r(stun_copy, ",", &saveptr);
155 while (token && config.stun_count < 4) {
156 while (*token == ' ' || *token == '\t')
157 token++;
158 size_t len = strlen(token);
159 while (len > 0 && (token[len - 1] == ' ' || token[len - 1] == '\t')) {
160 token[--len] = '\0';
161 }
162
163 if (len > 0 && len < sizeof(config.stun_servers[0].host)) {
164 config.stun_servers[config.stun_count].host_len = (uint8_t)len;
165 SAFE_STRNCPY(config.stun_servers[config.stun_count].host, token,
166 sizeof(config.stun_servers[config.stun_count].host));
167 log_info("Added STUN server: %s", token);
168 config.stun_count++;
169 } else if (len > 0) {
170 log_warn("STUN server URL too long (max 63 chars): %s", token);
171 }
172
173 token = platform_strtok_r(NULL, ",", &saveptr);
174 }
175 }
176
177 // Parse TURN servers from comma-separated list
178 config.turn_count = 0;
179 memset(config.turn_servers, 0, sizeof(config.turn_servers));
180 const char *turn_servers_str = GET_OPTION(turn_servers);
181 const char *turn_username_str = GET_OPTION(turn_username);
182 const char *turn_credential_str = GET_OPTION(turn_credential);
183
184 if (turn_servers_str && turn_servers_str[0] != '\0') {
185 char turn_copy[OPTIONS_BUFF_SIZE];
186 SAFE_STRNCPY(turn_copy, turn_servers_str, sizeof(turn_copy));
187
188 char *saveptr = NULL;
189 char *token = platform_strtok_r(turn_copy, ",", &saveptr);
190 while (token && config.turn_count < 4) {
191 while (*token == ' ' || *token == '\t')
192 token++;
193 size_t len = strlen(token);
194 while (len > 0 && (token[len - 1] == ' ' || token[len - 1] == '\t')) {
195 token[--len] = '\0';
196 }
197
198 if (len > 0 && len < sizeof(config.turn_servers[0].url)) {
199 config.turn_servers[config.turn_count].url_len = (uint8_t)len;
200 SAFE_STRNCPY(config.turn_servers[config.turn_count].url, token,
201 sizeof(config.turn_servers[config.turn_count].url));
202
203 if (turn_username_str && turn_username_str[0] != '\0') {
204 size_t username_len = strlen(turn_username_str);
205 if (username_len < sizeof(config.turn_servers[0].username)) {
206 config.turn_servers[config.turn_count].username_len = (uint8_t)username_len;
207 SAFE_STRNCPY(config.turn_servers[config.turn_count].username, turn_username_str,
208 sizeof(config.turn_servers[config.turn_count].username));
209 }
210 }
211
212 if (turn_credential_str && turn_credential_str[0] != '\0') {
213 size_t credential_len = strlen(turn_credential_str);
214 if (credential_len < sizeof(config.turn_servers[0].credential)) {
215 config.turn_servers[config.turn_count].credential_len = (uint8_t)credential_len;
216 SAFE_STRNCPY(config.turn_servers[config.turn_count].credential, turn_credential_str,
217 sizeof(config.turn_servers[config.turn_count].credential));
218 }
219 }
220
221 log_info("Added TURN server: %s (username: %s)", token,
222 turn_username_str && turn_username_str[0] ? turn_username_str : "<none>");
223 config.turn_count++;
224 } else if (len > 0) {
225 log_warn("TURN server URL too long (max 63 chars): %s", token);
226 }
227
228 token = platform_strtok_r(NULL, ",", &saveptr);
229 }
230 }
231
232 // Copy TURN secret for dynamic credential generation
233 const char *turn_secret_str = GET_OPTION(turn_secret);
234 if (turn_secret_str && turn_secret_str[0] != '\0') {
235 SAFE_STRNCPY(config.turn_secret, turn_secret_str, sizeof(config.turn_secret));
236 log_info("TURN dynamic credential generation enabled");
237 } else {
238 config.turn_secret[0] = '\0';
239 }
240
241 // Check for shutdown request during startup
243 log_info("Shutdown signal received during initialization, aborting startup");
244 return ASCIICHAT_OK;
245 }
246
247 // Copy identity keys to server struct (needed by handlers for handshake)
248 memcpy(g_server.identity_public, public_key, 32);
249 memcpy(g_server.identity_secret, secret_key, 64);
250
251 // Initialize server (DB, rate limiter, worker pool — TCP server comes from server_like)
252 result = acds_server_init(&g_server, &config);
253 if (result != ASCIICHAT_OK) {
254 log_error("Server initialization failed");
255 return result;
256 }
257
258 // Check again after server init in case SIGTERM arrived during init
260 log_info("Shutdown signal received after server initialization");
261 return ASCIICHAT_OK;
262 }
263
264 log_info("Discovery service initialized");
265 return ASCIICHAT_OK;
266}
267
268static void acds_cleanup_fn(void *user_data) {
269 (void)user_data;
270 log_info("Shutting down discovery server...");
271 acds_server_shutdown(&g_server);
272 log_info("Discovery server stopped");
273}
274
275/* ============================================================================
276 * Entry Point
277 * ============================================================================ */
278
279int acds_main(void) {
281 // Required callbacks
282 .init_fn = acds_init_fn,
283 .init_user_data = NULL,
284 .interrupt_fn = acds_interrupt_fn,
285
286 // Cleanup
287 .cleanup_fn = acds_cleanup_fn,
288 .cleanup_user_data = NULL,
289
290 // No status screen for ACDS (for now)
291 .status_fn = NULL,
292 .status_user_data = NULL,
293
294 // TCP handler
295 .tcp_handler = acds_client_handler,
296 .tcp_user_data = &g_server,
297
298 // WebSocket
299 .websocket =
300 {
301 .enabled = true,
303 .user_data = &g_server,
304 },
305
306 // mDNS
307 .mdns =
308 {
309 .enabled = true,
310 .service_name = "ascii-chat-Discovery-Service",
311 .service_type = "_ascii-chat-discovery-service._tcp",
312 },
313
314 // UPnP
315 .upnp =
316 {
317 .enabled = true,
318 .description = "ascii-chat ACDS",
319 },
320
321 // System
322 .raise_fd_limit = true,
323 .fd_limit_target = 65536,
324 };
325
326 return session_server_like_run(&config);
327}
Platform abstraction layer umbrella header providing unified cross-platform API.
void atomic_store_bool(atomic_t *a, bool value)
Atomically store a boolean value.
Definition atomic.c:177
⚙️ Common definitions, error codes, macros, and types shared throughout the application
int acds_main(void)
ACDS (discovery-service mode) entry point.
asciichat_error_t acds_string_init(void)
Initialize session string system with cached word validation.
Cross-platform error handling utilities.
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
unsigned char uint8_t
Definition common.h:56
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define log_file(...)
File-only logging - writes to log file only, no stderr output.
Definition log/log.h:646
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
const options_t * options_get(void)
Get current options (lock-free read)
Definition rcu.c:496
#define OPTIONS_BUFF_SIZE
Buffer size for option string values.
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:148
#define INVALID_SOCKET_VALUE
Invalid socket value (POSIX: -1)
Definition socket.h:278
int socket_close(socket_t sock)
Close a socket.
char * platform_strtok_r(char *str, const char *delim, char **saveptr)
Thread-safe string tokenization (strtok_r replacement)
char * get_config_dir(void)
Get configuration directory path with XDG_CONFIG_HOME support.
Definition path.c:527
asciichat_error_t acds_identity_load(const char *path, uint8_t public_key[32], uint8_t secret_key[64])
Load identity from file.
Definition identity.c:31
asciichat_error_t acds_identity_save(const char *path, const uint8_t public_key[32], const uint8_t secret_key[64])
Save identity to file.
Definition identity.c:60
void acds_identity_fingerprint(const uint8_t public_key[32], char fingerprint[65])
Compute SHA256 fingerprint of public key.
Definition identity.c:103
asciichat_error_t acds_identity_generate(uint8_t public_key[32], uint8_t secret_key[64])
Generate new Ed25519 keypair.
Definition identity.c:17
⚙️ Unified options parsing system for ascii-chat with builder pattern and lock-free access
Platform initialization and static synchronization helpers.
📝 Logging API with multiple log levels and terminal output control
#define log_console(level, message)
Definition log/log.h:1003
📂 Path Manipulation Utilities
bool session_server_like_shutdown_requested(void)
tcp_server_t * session_server_like_get_tcp_server(void)
asciichat_error_t session_server_like_run(const session_server_like_config_t *config)
Shared initialization, networking, and teardown for server-like modes.
Cross-platform crash signal and exception handling.
void * acds_websocket_client_handler(void *arg)
WebSocket client handler (thread entry point)
void * acds_client_handler(void *arg)
Per-client connection handler (thread entry point)
void acds_server_shutdown(acds_server_t *server)
Shutdown discovery server.
asciichat_error_t acds_server_init(acds_server_t *server, const acds_config_t *config)
Initialize discovery server.
ClangTool/LibTooling compatibility shim for stdbool.h.
Discovery server configuration.
char address[256]
IPv4 bind address (empty = all interfaces)
turn_server_t turn_servers[4]
TURN server configurations.
char database_path[512]
SQLite database path.
bool require_server_identity
Require servers to provide signed identity when creating sessions.
uint8_t turn_count
Number of configured TURN servers (0-4)
int port
TCP listen port (default 27225)
char turn_secret[256]
Shared secret for TURN credential generation (HMAC-SHA1)
uint8_t stun_count
Number of configured STUN servers (0-4)
char key_path[512]
Ed25519 identity key file path.
char log_file[512]
Log file path (empty = stderr)
char address6[256]
IPv6 bind address (empty = all interfaces)
bool require_client_identity
Require clients to provide signed identity when joining sessions.
stun_server_t stun_servers[4]
STUN server configurations.
log_level_t log_level
Logging verbosity level.
Discovery server state.
uint8_t identity_public[32]
Ed25519 public key.
uint8_t identity_secret[64]
Ed25519 secret key.
Consolidated options structure.
char address6[256]
IPv6 bind address (server only)
char log_file[256]
Log file path.
char address[256]
Server address (client) or bind address (server)
atomic_t running
Server running flag (set false to shutdown)
socket_t listen_socket
IPv4 listen socket.
socket_t listen_socket6
IPv6 listen socket.
#define PLATFORM_MAX_PATH_LENGTH
Definition system.c:69
#define LOG_INFO
Definition types.h:40
#define ASCII_CHAT_VERSION_FULL
Definition version.h:21
#define ASCII_CHAT_BUILD_TYPE
Definition version.h:14
#define ASCII_CHAT_BUILD_DATE
Definition version.h:16