55 {
57
58
59
60
61 char **acds_argv =
SAFE_MALLOC((
size_t)(argc + 2) *
sizeof(
char *),
char **);
62 if (!acds_argv) {
64 }
65 acds_argv[0] = argv[0];
66 acds_argv[1] = "acds";
67 for (int i = 1; i < argc; i++) {
68 acds_argv[i + 1] = argv[i];
69 }
70 acds_argv[argc + 1] = NULL;
71
73
76 return result;
77 }
78
79
81 if (opts && opts->
help) {
83 return 0;
84 }
85
87 printf("ascii-chat-acds version %s (%s, %s)\n", ASCII_CHAT_VERSION_FULL, ASCII_CHAT_BUILD_TYPE,
88 ASCII_CHAT_BUILD_DATE);
89 return 0;
90 }
91
92
93
96 return result;
97 }
98
99
100
101 rcu_register_thread();
102 log_debug(
"Main thread registered with RCU library");
103
104 log_info(
"ASCII-Chat Discovery Service (acds) starting...");
105 log_info(
"Version: %s (%s, %s)", ASCII_CHAT_VERSION_FULL, ASCII_CHAT_BUILD_TYPE, ASCII_CHAT_BUILD_DATE);
106
107
110 log_error(
"Failed to initialize session string generator");
111 return result;
112 }
113
114
117
120
122 log_info(
"Identity key not found, generating new key...");
123
126 log_error(
"Failed to generate identity key");
127 return result;
128 }
129
133 return result;
134 }
135
137 }
138
139
140 char fingerprint[65];
142 log_info(
"Discovery server identity: SHA256:%s", fingerprint);
143 printf("🔑 Server fingerprint: SHA256:%s\n", fingerprint);
144
145
148 const char *address = opts && opts->
address[0] !=
'\0' ? opts->
address :
"127.0.0.1";
149 const char *address6 = opts && opts->
address6[0] !=
'\0' ? opts->
address6 :
"::1";
159
160
162 log_info(
"Security: Requiring signed identity from servers creating sessions");
163 }
165 log_info(
"Security: Requiring signed identity from clients joining sessions");
166 }
167
168
171 const char *stun_servers_str =
GET_OPTION(stun_servers);
172 if (stun_servers_str && stun_servers_str[0] != '\0') {
174 SAFE_STRNCPY(stun_copy, stun_servers_str,
sizeof(stun_copy));
175
176 char *saveptr = NULL;
177 char *token = strtok_r(stun_copy, ",", &saveptr);
179
180 while (*token == ' ' || *token == '\t')
181 token++;
182 size_t len = strlen(token);
183 while (len > 0 && (token[len - 1] == ' ' || token[len - 1] == '\t')) {
184 token[--len] = '\0';
185 }
186
187 if (len > 0 && len <
sizeof(config.
stun_servers[0].host)) {
191 log_info(
"Added STUN server: %s", token);
193 } else if (len > 0) {
194 log_warn(
"STUN server URL too long (max 63 chars): %s", token);
195 }
196
197 token = strtok_r(NULL, ",", &saveptr);
198 }
199 }
200
201
204 const char *turn_servers_str =
GET_OPTION(turn_servers);
205 const char *turn_username_str =
GET_OPTION(turn_username);
206 const char *turn_credential_str =
GET_OPTION(turn_credential);
207
208 if (turn_servers_str && turn_servers_str[0] != '\0') {
210 SAFE_STRNCPY(turn_copy, turn_servers_str,
sizeof(turn_copy));
211
212 char *saveptr = NULL;
213 char *token = strtok_r(turn_copy, ",", &saveptr);
215
216 while (*token == ' ' || *token == '\t')
217 token++;
218 size_t len = strlen(token);
219 while (len > 0 && (token[len - 1] == ' ' || token[len - 1] == '\t')) {
220 token[--len] = '\0';
221 }
222
223 if (len > 0 && len <
sizeof(config.
turn_servers[0].url)) {
227
228
229 if (turn_username_str && turn_username_str[0] != '\0') {
230 size_t username_len = strlen(turn_username_str);
231 if (username_len <
sizeof(config.
turn_servers[0].username)) {
235 }
236 }
237
238
239 if (turn_credential_str && turn_credential_str[0] != '\0') {
240 size_t credential_len = strlen(turn_credential_str);
241 if (credential_len <
sizeof(config.
turn_servers[0].credential)) {
245 }
246 }
247
248 log_info(
"Added TURN server: %s (username: %s)", token,
249 turn_username_str && turn_username_str[0] ? turn_username_str : "<none>");
251 } else if (len > 0) {
252 log_warn(
"TURN server URL too long (max 63 chars): %s", token);
253 }
254
255 token = strtok_r(NULL, ",", &saveptr);
256 }
257 }
258
259
260 const char *turn_secret_str =
GET_OPTION(turn_secret);
261 if (turn_secret_str && turn_secret_str[0] != '\0') {
263 log_info(
"TURN dynamic credential generation enabled");
264 } else {
266 }
267
268
270 memset(&server, 0, sizeof(server));
271 g_server = &server;
272
275 log_error(
"Server initialization failed");
276 g_server = NULL;
277 return result;
278 }
279
280
281
282
283
284
285
286
287
288
289
292
294 char public_addr[22];
296 printf("🌐 Public endpoint: %s (direct TCP)\n", public_addr);
297 log_info(
"UPnP: Port mapping successful, public endpoint: %s", public_addr);
298 }
299 } else {
300 log_info(
"UPnP: Port mapping unavailable or failed - will use WebRTC fallback");
301 printf("📡 Clients behind strict NATs will use WebRTC fallback\n");
302 }
303 } else {
305 log_info(
"UPnP: Disabled via --no-upnp option");
306 } else {
307 log_info(
"UPnP: Disabled via environment variable or configuration");
308 }
309 printf("📡 WebRTC will be used for all clients\n");
310 }
311
312
313
314 log_debug(
"Initializing mDNS for ACDS LAN service discovery...");
316 if (!g_mdns_ctx) {
317 LOG_ERRNO_IF_SET(
"Failed to initialize mDNS (non-fatal, LAN discovery disabled)");
318 log_warn(
"mDNS disabled for ACDS - LAN discovery of discovery service will not be available");
319 } else {
320
321
322 char hostname[256] = {0};
323 gethostname(hostname, sizeof(hostname) - 1);
324
326 .
name =
"ASCII-Chat-Discovery-Service",
327 .type = "_ascii-chat-discovery-service._tcp",
328 .host = hostname,
330 .txt_records = NULL,
331 .txt_count = 0,
332 };
333
337 log_warn(
"mDNS advertising failed for ACDS - LAN discovery disabled");
339 g_mdns_ctx = NULL;
340 } else {
341 printf("🌐 mDNS: ACDS advertised as '_ascii-chat-discovery-service._tcp.local' on LAN\n");
342 log_info(
"mDNS: ACDS advertised as '_ascii-chat-discovery-service._tcp.local' (port=%d)", config.
port);
343 }
344 }
345
346
347 signal(SIGINT, signal_handler);
348 signal(SIGTERM, signal_handler);
349
350
351 log_info(
"Discovery server listening on port %d", config.
port);
352 printf(
"🌐 Listening on port %d\n", config.
port);
354 printf("Press Ctrl+C to stop\n\n");
355
359 }
360
361
362 log_info(
"Shutting down discovery server...");
364 g_server = NULL;
365
366
367 if (g_upnp_ctx) {
370 }
371
372
373 if (g_mdns_ctx) {
375 g_mdns_ctx = NULL;
377 }
378
379
380
381 log_debug(
"Unregistering main thread from RCU library");
382 rcu_unregister_thread();
383
384 log_info(
"Discovery server stopped");
385 return result;
386}
#define LOG_ERRNO_IF_SET(message)
Check if any error occurred and log it if so.
#define SAFE_STRNCPY(dst, src, size)
asciichat_error_t asciichat_shared_init(const char *default_log_filename, bool is_client)
Initialize common subsystems shared by client and server.
#define SAFE_MALLOC(size, cast)
asciichat_error_t
Error and exit codes - unified status values (0-255)
#define log_warn(...)
Log a WARN message.
#define log_error(...)
Log an ERROR message.
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
#define log_file(...)
File-only logging - writes to log file only, no stderr output.
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
char opt_acds_key_path[OPTIONS_BUFF_SIZE]
Ed25519 identity key path (ACDS mode only)
int opt_acds_port
TCP listen port (ACDS mode only)
const options_t * options_get(void)
Get current options (lock-free read)
#define OPTIONS_BUFF_SIZE
Buffer size for option string values.
asciichat_error_t options_init(int argc, char **argv)
Initialize options by parsing command-line arguments.
void usage(FILE *desc, asciichat_mode_t mode)
Print usage information for client, server, or mirror mode.
char opt_acds_database_path[OPTIONS_BUFF_SIZE]
SQLite database path (ACDS mode only)
@ MODE_ACDS
Discovery service mode - session management and WebRTC signaling.
asciichat_error_t acds_identity_load(const char *path, uint8_t public_key[32], uint8_t secret_key[64])
Load identity from file.
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.
void acds_identity_fingerprint(const uint8_t public_key[32], char fingerprint[65])
Compute SHA256 fingerprint of public key.
asciichat_error_t acds_identity_generate(uint8_t public_key[32], uint8_t secret_key[64])
Generate new Ed25519 keypair.
void asciichat_mdns_shutdown(asciichat_mdns_t *mdns)
Shutdown mDNS context and cleanup.
asciichat_mdns_t * asciichat_mdns_init(void)
Initialize mDNS context.
asciichat_error_t asciichat_mdns_advertise(asciichat_mdns_t *mdns, const asciichat_mdns_service_t *service)
Advertise a service on the local network.
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.
asciichat_error_t acds_server_run(acds_server_t *server)
Run discovery server main loop.
asciichat_error_t acds_string_init(void)
Initialize random number generator for string generation.
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.
Service information for advertisement.
Consolidated options structure.
bool version
Show version information.
char address6[256]
IPv6 bind address (server only)
bool help
Show help message.
char log_file[256]
Log file path.
char address[256]
Server address (client) or bind address (server)
void nat_upnp_close(nat_upnp_context_t **ctx)
Close port mapping and clean up.
asciichat_error_t nat_upnp_open(uint16_t internal_port, const char *description, nat_upnp_context_t **ctx)
Discover and open port via UPnP.
asciichat_error_t nat_upnp_get_address(const nat_upnp_context_t *ctx, char *addr, size_t addr_len)
Get the public address (IP:port) for advertising to clients.