srv.c (38624B)
1 /* srv.c - servidor de partidas: sockets, partidas persistentes, bots. El juego 2 * en si lo pone un GameModule (ver srv.h). */ 3 #ifdef _WIN32 4 #define _CRT_RAND_S /* rand_s */ 5 #endif 6 #include <dirent.h> 7 #include <errno.h> 8 #include <fcntl.h> 9 #include <signal.h> 10 #include <stdarg.h> 11 #include <stdio.h> 12 #include <stdlib.h> 13 #include <string.h> 14 #ifndef _WIN32 15 #include <strings.h> 16 #endif 17 #include <sys/stat.h> 18 #include <sys/time.h> 19 #include <time.h> 20 #include <unistd.h> 21 #include "plat.h" 22 #ifndef _WIN32 23 #include <arpa/inet.h> 24 #include <netinet/in.h> 25 #endif 26 #include "srv.h" 27 #ifndef SRV_NO_WS 28 #include "ws.h" /* clientes WebSocket (la version web): mismo protocolo, envuelto en frames */ 29 #endif 30 31 typedef struct { 32 Server *srv; 33 char code[8]; 34 int np; 35 char kind[SRV_MAX_SEATS]; /* H libre, P humano, B bot, N neutral, - no existe */ 36 char token[SRV_MAX_SEATS][33]; 37 char name[SRV_MAX_SEATS][20]; 38 uint64_t seed; 39 char opts[SRV_LINE]; 40 char **hist; /* movidas resueltas, sin redactar */ 41 int nhist, caphist; 42 FILE *log; 43 char **chat; /* "<de> <para|-1> <texto>" */ 44 int nchat, capchat; 45 FILE *chatf; 46 bool loading; 47 char again[8]; /* la partida "otra como esta" ya creada (AGAIN), para no crear dos */ 48 void *st; /* estado del juego (GameModule.state_size) */ 49 } SGame; 50 51 /* Limites para que un cliente no pueda llenar la memoria o el disco del VPS. */ 52 #define SRV_MAX_GAMES 5000 53 #define SRV_MAX_MOVES 20000 /* movidas por partida */ 54 #define SRV_MAX_CHAT 3000 /* lineas de chat por partida */ 55 #define SRV_CHAT_REPLAY SRV_MAX_CHAT /* al entrar se manda todo el chat que podes ver */ 56 #define SRV_CHAT_LEN 200 57 #define SRV_CREATES_CONN 10 /* partidas nuevas por conexion */ 58 #define SRV_CREATES_MIN 60 /* partidas nuevas por minuto (en total) */ 59 #define SRV_HELLO_MS 10000 /* tiempo para saludar */ 60 #define SRV_IDLE_MS 600000 /* sin trafico: se corta (el cliente manda PING cada 30 s) */ 61 62 typedef struct { 63 int fd; 64 char in[4096]; 65 int inlen; 66 bool hello; 67 char name[20]; 68 char key[33]; /* AUTH: clave del asiento (hash de nombre + contrasena) */ 69 int game, seat; 70 int creates; 71 long long since, last, last_say; 72 #ifndef SRV_NO_WS 73 uint8_t ws; /* 0 TCP, 1 esperando el handshake, 2 WebSocket */ 74 uint8_t wsin[4096]; /* frames crudos; el payload pasa a in */ 75 int wslen; 76 #endif 77 } Client; 78 79 struct Server { 80 const GameModule *gm; 81 int lfd, port; 82 Client cl[SRV_MAX_CLIENTS]; 83 SGame **games; 84 int ngames, cap; 85 char dir[512]; 86 char admin[33]; /* secreto de <dir>/admin.key para el comando ADMIN */ 87 bool quiet; 88 long long create_window; 89 int creates_in_window; 90 }; 91 92 long long srv_now_ms(void) 93 { 94 #ifdef _WIN32 95 return (long long)GetTickCount64(); 96 #else 97 struct timespec ts; 98 clock_gettime(CLOCK_MONOTONIC, &ts); 99 return (long long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; 100 #endif 101 } 102 103 static void logf_(Server *s, const char *fmt, ...) 104 { 105 if (s->quiet) return; 106 char t[32]; 107 time_t now = time(0); 108 strftime(t, sizeof t, "%Y-%m-%d %H:%M:%S", localtime(&now)); 109 fprintf(stderr, "[%s] ", t); 110 va_list ap; 111 va_start(ap, fmt); 112 vfprintf(stderr, fmt, ap); 113 va_end(ap); 114 fputc('\n', stderr); 115 } 116 117 static void random_bytes(void *buf, size_t n) 118 { 119 #ifdef _WIN32 120 for (size_t i = 0; i < n; i++) { 121 unsigned v; 122 if (rand_s(&v)) v = (unsigned)rand(); 123 ((uint8_t *)buf)[i] = (uint8_t)v; 124 } 125 #else 126 int fd = open("/dev/urandom", O_RDONLY); 127 if (fd >= 0) { 128 ssize_t r = read(fd, buf, n); 129 close(fd); 130 if (r == (ssize_t)n) return; 131 } 132 struct timeval tv; 133 gettimeofday(&tv, 0); 134 srand((unsigned)(tv.tv_usec ^ tv.tv_sec ^ getpid())); 135 for (size_t i = 0; i < n; i++) ((uint8_t *)buf)[i] = (uint8_t)rand(); 136 #endif 137 } 138 139 static void new_token(char out[33]) 140 { 141 uint8_t b[16]; 142 random_bytes(b, sizeof b); 143 for (int i = 0; i < 16; i++) snprintf(out + 2 * i, 3, "%02x", b[i]); 144 } 145 146 /* ------------------------------------------------------------------ envio */ 147 148 static void drop(Server *s, Client *c); 149 150 static void send_all(Server *s, Client *c, const void *data, int n) 151 { 152 const char *p = data; 153 int off = 0; 154 while (off < n) { 155 ssize_t w = send(c->fd, p + off, n - off, MSG_NOSIGNAL); 156 if (w <= 0) { drop(s, c); return; } 157 off += (int)w; 158 } 159 } 160 161 static void sendf(Server *s, Client *c, const char *fmt, ...) 162 { 163 if (c->fd < 0) return; 164 char buf[SRV_LINE + 64]; 165 va_list ap; 166 va_start(ap, fmt); 167 int n = vsnprintf(buf, sizeof buf - 1, fmt, ap); 168 va_end(ap); 169 if (n < 0) return; 170 if (n > (int)sizeof buf - 2) n = (int)sizeof buf - 2; 171 buf[n++] = '\n'; 172 #ifndef SRV_NO_WS 173 if (c->ws == 2) { 174 uint8_t fr[sizeof buf + 10]; 175 send_all(s, c, fr, ws_wrap(WS_BINARY, buf, n, fr, sizeof fr)); 176 return; 177 } 178 #endif 179 send_all(s, c, buf, n); 180 } 181 182 static bool seat_online(Server *s, SGame *g, int seat) 183 { 184 for (int i = 0; i < SRV_MAX_CLIENTS; i++) 185 if (s->cl[i].fd >= 0 && s->cl[i].game >= 0 && s->games[s->cl[i].game] == g && s->cl[i].seat == seat) return true; 186 return false; 187 } 188 189 static void send_seat(Server *s, Client *c, SGame *g, int i) 190 { 191 int on = g->kind[i] == 'B' || g->kind[i] == 'N' || seat_online(s, g, i); 192 sendf(s, c, "SEAT %d %c %d %s", i, g->kind[i], on, g->name[i][0] ? g->name[i] : "-"); 193 } 194 195 static void broadcast_seat(Server *s, SGame *g, int i) 196 { 197 for (int k = 0; k < SRV_MAX_CLIENTS; k++) { 198 Client *c = &s->cl[k]; 199 if (c->fd >= 0 && c->game >= 0 && s->games[c->game] == g) send_seat(s, c, g, i); 200 } 201 } 202 203 static void drop(Server *s, Client *c) 204 { 205 if (c->fd < 0) return; 206 sock_close(c->fd); 207 c->fd = -1; 208 int gi = c->game; 209 c->game = -1; 210 c->inlen = 0; 211 if (gi >= 0) broadcast_seat(s, s->games[gi], c->seat); 212 } 213 214 /* ------------------------------------------------------------ persistencia */ 215 216 static void game_dir(Server *s, SGame *g, char *out, size_t n) { snprintf(out, n, "%s/%s", s->dir, g->code); } 217 218 static void save_meta(Server *s, SGame *g) 219 { 220 if (!s->dir[0]) return; 221 char dir[600], tmp[700], path[700]; 222 game_dir(s, g, dir, sizeof dir); 223 mkdir(dir, 0700); 224 snprintf(tmp, sizeof tmp, "%s/meta.tmp", dir); 225 snprintf(path, sizeof path, "%s/meta", dir); 226 FILE *f = fopen(tmp, "w"); 227 if (!f) return; 228 fchmod(fileno(f), 0600); 229 fprintf(f, "%s\nSEED %llu\nOPTS %s\n", s->gm->magic, (unsigned long long)g->seed, g->opts); 230 for (int i = 0; i < g->np; i++) 231 fprintf(f, "SEAT %d %c %s %s\n", i, g->kind[i], g->token[i][0] ? g->token[i] : "-", g->name[i][0] ? g->name[i] : "-"); 232 fclose(f); 233 rename(tmp, path); 234 } 235 236 static void open_log(Server *s, SGame *g) 237 { 238 if (!s->dir[0]) return; 239 char dir[600], path[700]; 240 game_dir(s, g, dir, sizeof dir); 241 mkdir(dir, 0700); 242 snprintf(path, sizeof path, "%s/log", dir); 243 g->log = fopen(path, "a"); 244 snprintf(path, sizeof path, "%s/chat", dir); 245 g->chatf = fopen(path, "a"); 246 } 247 248 /* ------------------------------------------------------------ chat */ 249 250 static bool chat_add(SGame *g, const char *line) 251 { 252 if (g->nchat >= SRV_MAX_CHAT) return false; 253 if (g->nchat == g->capchat) { 254 int nc = g->capchat ? g->capchat * 2 : 64; 255 char **c = realloc(g->chat, sizeof(char *) * (size_t)nc); 256 if (!c) return false; 257 g->chat = c; 258 g->capchat = nc; 259 } 260 char *copy = strdup(line); 261 if (!copy) return false; 262 g->chat[g->nchat++] = copy; 263 return true; 264 } 265 266 /* el privado (para >= 0) lo ven solo quien lo manda y quien lo recibe */ 267 static bool chat_visible(const char *line, int seat) 268 { 269 int from, to; 270 if (sscanf(line, "%d %d", &from, &to) != 2) return false; 271 return to < 0 || to == seat || from == seat; 272 } 273 274 static void load_chat(Server *s, SGame *g) 275 { 276 char path[700], line[SRV_LINE]; 277 snprintf(path, sizeof path, "%s/%s/chat", s->dir, g->code); 278 FILE *f = fopen(path, "r"); 279 if (!f) return; 280 while (fgets(line, sizeof line, f)) { 281 line[strcspn(line, "\r\n")] = 0; 282 if (line[0]) chat_add(g, line); 283 } 284 fclose(f); 285 } 286 287 /* ------------------------------------------------------------ exportar */ 288 289 static void put_line(void *ctx, const char *line) { fputs(line, ctx); fputc('\n', ctx); } 290 291 /* partida.txt: lo que exporte el juego, mas el chat (todo, tambien los privados: es el 292 * archivo del servidor) */ 293 static void write_export(Server *s, SGame *g) 294 { 295 if (!s->dir[0] || !s->gm->export) return; 296 char dir[600], tmp[700], path[700]; 297 game_dir(s, g, dir, sizeof dir); 298 snprintf(tmp, sizeof tmp, "%s/partida.tmp", dir); 299 snprintf(path, sizeof path, "%s/partida.txt", dir); 300 FILE *f = fopen(tmp, "w"); 301 if (!f) return; 302 s->gm->export(g->st, g->code, (const char *const *)g->hist, g->nhist, g->name, g->kind, put_line, f); 303 if (g->nchat) { 304 fprintf(f, "\n## Chat\n"); 305 for (int i = 0; i < g->nchat; i++) { 306 int from, to, used = 0; 307 if (sscanf(g->chat[i], "%d %d %n", &from, &to, &used) < 2) continue; 308 const char *fn = from >= 0 && from < g->np && g->name[from][0] ? g->name[from] : "?"; 309 if (to >= 0 && to < g->np) fprintf(f, "%s -> %s: %s\n", fn, g->name[to][0] ? g->name[to] : "?", g->chat[i] + used); 310 else fprintf(f, "%s: %s\n", fn, g->chat[i] + used); 311 } 312 } 313 fclose(f); 314 rename(tmp, path); 315 } 316 317 /* ------------------------------------------------------------ partidas */ 318 319 static void on_move(void *ctx, const char *move, bool is_auto) 320 { 321 SGame *g = ctx; 322 Server *s = g->srv; 323 if (g->nhist == g->caphist) { 324 int nc = g->caphist ? g->caphist * 2 : 256; 325 char **h = realloc(g->hist, sizeof(char *) * (size_t)nc); 326 if (!h) return; 327 g->hist = h; 328 g->caphist = nc; 329 } 330 char *copy = strdup(move); 331 if (!copy) return; 332 g->hist[g->nhist++] = copy; 333 if (g->loading) return; 334 if (g->log && !is_auto) { fprintf(g->log, "%s\n", move); fflush(g->log); } 335 write_export(s, g); 336 char line[SRV_LINE]; 337 for (int k = 0; k < SRV_MAX_CLIENTS; k++) { 338 Client *c = &s->cl[k]; 339 if (c->fd < 0 || c->game < 0 || s->games[c->game] != g) continue; 340 s->gm->redact(move, c->seat, line, sizeof line); 341 sendf(s, c, "EV %s", line); 342 } 343 } 344 345 static SGame *add_game(Server *s) 346 { 347 if (s->ngames == s->cap) { 348 int nc = s->cap ? s->cap * 2 : 16; 349 SGame **n = realloc(s->games, sizeof(SGame *) * (size_t)nc); 350 if (!n) return 0; 351 s->games = n; 352 s->cap = nc; 353 } 354 SGame *g = calloc(1, sizeof *g); 355 if (!g) return 0; 356 g->st = calloc(1, s->gm->state_size); 357 if (!g->st) { free(g); return 0; } 358 g->srv = s; 359 s->games[s->ngames++] = g; 360 return g; 361 } 362 363 static void free_game(SGame *g) 364 { 365 if (g->log) fclose(g->log); 366 if (g->chatf) fclose(g->chatf); 367 for (int i = 0; i < g->nchat; i++) free(g->chat[i]); 368 free(g->chat); 369 for (int i = 0; i < g->nhist; i++) free(g->hist[i]); 370 free(g->hist); 371 free(g->st); 372 free(g); 373 } 374 375 static int find_game(Server *s, const char *code) 376 { 377 for (int i = 0; i < s->ngames; i++) 378 if (!strcmp(s->games[i]->code, code)) return i; 379 return -1; 380 } 381 382 static bool start_game(Server *s, SGame *g) 383 { 384 g->loading = true; 385 if (!s->gm->start(g->st, g->opts, g->seed, g->kind, g->name, on_move, g)) return false; 386 /* el juego puede sumar asientos propios (ej. los neutrales de Catan para 2) */ 387 for (int i = 0; i < SRV_MAX_SEATS; i++) if (g->kind[i] != '-') g->np = i + 1; 388 return true; 389 } 390 391 static void load_game(Server *s, const char *code) 392 { 393 char path[700], line[SRV_LINE + 64]; 394 snprintf(path, sizeof path, "%s/%s/meta", s->dir, code); 395 FILE *f = fopen(path, "r"); 396 if (!f) return; 397 SGame tmp; 398 memset(&tmp, 0, sizeof tmp); 399 bool magic = false, opts = false; 400 for (int i = 0; i < SRV_MAX_SEATS; i++) tmp.kind[i] = '-'; 401 while (fgets(line, sizeof line, f)) { 402 line[strcspn(line, "\r\n")] = 0; 403 unsigned long long seed; 404 int i; 405 char k, tok[40], name[64]; 406 if (!strcmp(line, s->gm->magic)) magic = true; 407 else if (sscanf(line, "SEED %llu", &seed) == 1) tmp.seed = seed; 408 else if (!strncmp(line, "OPTS ", 5)) { snprintf(tmp.opts, sizeof tmp.opts, "%.*s", (int)sizeof tmp.opts - 1, line + 5); opts = true; } 409 else if (sscanf(line, "SEAT %d %c %39s %63[^\n]", &i, &k, tok, name) == 4 && i >= 0 && i < SRV_MAX_SEATS) { 410 tmp.kind[i] = k; 411 snprintf(tmp.token[i], 33, "%.32s", strcmp(tok, "-") ? tok : ""); 412 snprintf(tmp.name[i], 20, "%.19s", strcmp(name, "-") ? name : ""); 413 if (i + 1 > tmp.np) tmp.np = i + 1; 414 } 415 } 416 fclose(f); 417 if (!magic || !opts) return; 418 SGame *g = add_game(s); 419 if (!g) return; 420 snprintf(g->code, sizeof g->code, "%s", code); 421 g->seed = tmp.seed; 422 g->np = tmp.np; 423 memcpy(g->opts, tmp.opts, sizeof g->opts); 424 memcpy(g->kind, tmp.kind, sizeof g->kind); 425 memcpy(g->token, tmp.token, sizeof g->token); 426 memcpy(g->name, tmp.name, sizeof g->name); 427 if (!start_game(s, g)) { 428 logf_(s, "partida %s: opciones invalidas", code); 429 s->ngames--; 430 free_game(g); 431 return; 432 } 433 snprintf(path, sizeof path, "%s/%s/log", s->dir, code); 434 f = fopen(path, "r"); 435 int n = 0; 436 if (f) { 437 while (fgets(line, sizeof line, f)) { 438 line[strcspn(line, "\r\n")] = 0; 439 if (!line[0]) continue; 440 if (s->gm->replay(g->st, line) != 0) { logf_(s, "partida %s: movida %d no reproduce: %s", code, n, line); break; } 441 n++; 442 } 443 fclose(f); 444 } 445 g->loading = false; 446 load_chat(s, g); 447 open_log(s, g); 448 write_export(s, g); 449 } 450 451 static void load_all(Server *s) 452 { 453 DIR *d = opendir(s->dir); 454 if (!d) return; 455 struct dirent *e; 456 while ((e = readdir(d))) 457 if (e->d_name[0] != '.' && strlen(e->d_name) < 8) load_game(s, e->d_name); 458 closedir(d); 459 logf_(s, "%d partidas cargadas de %s", s->ngames, s->dir); 460 } 461 462 /* ------------------------------------------------------------ comandos */ 463 464 static void attach(Server *s, Client *c, int gi, int seat) 465 { 466 SGame *g = s->games[gi]; 467 c->game = gi; 468 c->seat = seat; 469 char line[SRV_LINE]; 470 sendf(s, c, "GAME %s %d %s", g->code, seat, g->token[seat]); 471 s->gm->header(g->st, line, sizeof line); 472 sendf(s, c, "HDR %s", line); 473 for (int i = 0; i < g->np; i++) send_seat(s, c, g, i); 474 for (int i = 0; i < g->nhist && c->fd >= 0; i++) { 475 s->gm->redact(g->hist[i], seat, line, sizeof line); 476 sendf(s, c, "EV %s", line); 477 } 478 int shown = 0, first = g->nchat; 479 while (first > 0 && shown < SRV_CHAT_REPLAY) if (chat_visible(g->chat[--first], seat)) shown++; 480 for (int i = first; i < g->nchat && c->fd >= 0; i++) 481 if (chat_visible(g->chat[i], seat)) sendf(s, c, "CHAT %s", g->chat[i]); 482 sendf(s, c, "SYNC"); 483 broadcast_seat(s, g, seat); 484 } 485 486 static void detach(Server *s, Client *c) 487 { 488 int gi = c->game; 489 c->game = -1; 490 if (gi >= 0) broadcast_seat(s, s->games[gi], c->seat); 491 } 492 493 static void clean_name(const char *in, char *out, int n) 494 { 495 int k = 0; 496 while (*in == ' ') in++; 497 for (; *in && k < n - 1; in++) { 498 unsigned char ch = (unsigned char)*in; 499 if (ch < 0x20 || ch == '|' || ch == 0x7F) continue; 500 out[k++] = (char)ch; 501 } 502 while (k > 0 && ((unsigned char)out[k - 1] & 0xC0) == 0xC0) k--; /* no cortar utf-8 */ 503 while (k > 0 && out[k - 1] == ' ') k--; 504 out[k] = 0; 505 if (!k) snprintf(out, (size_t)n, "?"); 506 } 507 508 /* Arma la partida (opciones y tipo de asiento ya validados) y sienta al creador en el 0. 509 * Con like (AGAIN): la gente de esa partida conserva su asiento y su llave (les aparece en 510 * "mis partidas"), y el que pidio queda en el asiento myseat. */ 511 static void create_game(Server *s, Client *c, const char *opts, const char *seats, int np, const SGame *like, int myseat) 512 { 513 const GameModule *gm = s->gm; 514 long long now = srv_now_ms(); 515 if (now - s->create_window > 60000) { s->create_window = now; s->creates_in_window = 0; } 516 if (s->ngames >= SRV_MAX_GAMES || c->creates >= SRV_CREATES_CONN || s->creates_in_window >= SRV_CREATES_MIN) { 517 sendf(s, c, "ERR %d server busy, try later", gm->err_args); 518 return; 519 } 520 c->creates++; 521 s->creates_in_window++; 522 if (c->game >= 0) detach(s, c); 523 SGame *g = add_game(s); 524 if (like) like = s->games[find_game(s, like->code)]; /* add_game puede mover el arreglo */ 525 if (!g) { sendf(s, c, "ERR %d out of memory", gm->err_args); return; } 526 static const char AL[] = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; 527 do { 528 uint8_t b[6]; 529 random_bytes(b, sizeof b); 530 for (int i = 0; i < 6; i++) g->code[i] = AL[b[i] % 32]; 531 g->code[6] = 0; 532 } while (find_game(s, g->code) != s->ngames - 1); 533 random_bytes(&g->seed, sizeof g->seed); 534 snprintf(g->opts, sizeof g->opts, "%s", opts); 535 g->np = np; 536 for (int i = 0; i < SRV_MAX_SEATS; i++) g->kind[i] = '-'; 537 for (int i = 0; i < np; i++) { 538 g->kind[i] = seats[i]; 539 if (seats[i] == 'B') snprintf(g->name[i], sizeof g->name[i], "Bot %d", i + 1); 540 } 541 int seat = 0; 542 if (like) { 543 for (int i = 0; i < np; i++) 544 if (like->kind[i] == 'P') { 545 g->kind[i] = 'P'; 546 memcpy(g->token[i], like->token[i], 33); 547 snprintf(g->name[i], sizeof g->name[i], "%s", like->name[i]); 548 } 549 seat = myseat >= 0 && myseat < np && g->kind[myseat] == 'P' ? myseat : 0; 550 } 551 if (g->kind[seat] != 'P' || !like) { 552 g->kind[seat] = 'P'; 553 if (c->key[0]) memcpy(g->token[seat], c->key, 33); 554 else new_token(g->token[seat]); 555 snprintf(g->name[seat], sizeof g->name[seat], "%s", c->name); 556 } 557 if (!start_game(s, g)) { 558 s->ngames--; 559 free_game(g); 560 sendf(s, c, "ERR %d bad create", gm->err_args); 561 return; 562 } 563 g->loading = false; 564 save_meta(s, g); 565 open_log(s, g); /* tambien abre el chat */ 566 write_export(s, g); 567 char sum[128] = ""; 568 if (gm->summary) gm->summary(g->st, sum, sizeof sum); 569 logf_(s, "partida %s creada por %s (%s)%s", g->code, c->name, sum, like ? " [otra como la anterior]" : ""); 570 attach(s, c, s->ngames - 1, seat); 571 } 572 573 static void cmd_create(Server *s, Client *c, const char *args) 574 { 575 const GameModule *gm = s->gm; 576 char opts[SRV_LINE], seats[SRV_MAX_SEATS + 1] = ""; 577 int np = gm->create(args, opts, sizeof opts, seats); 578 if (np < 1 || np > SRV_MAX_SEATS || seats[0] != 'H') { sendf(s, c, "ERR %d bad create", gm->err_args); return; } 579 for (int i = 0; i < np; i++) 580 if (seats[i] != 'H' && seats[i] != 'B') { sendf(s, c, "ERR %d bad seats", gm->err_args); return; } 581 create_game(s, c, opts, seats, np, 0, 0); 582 } 583 584 /* AGAIN: otra partida como la que estoy jugando: mismas opciones, bots donde habia bots, 585 * la misma gente en los mismos asientos (con su llave: les aparece en "mis partidas"), 586 * los asientos que estaban libres siguen libres, y otro reparto. */ 587 static void cmd_again(Server *s, Client *c) 588 { 589 const GameModule *gm = s->gm; 590 if (c->game < 0) { sendf(s, c, "ERR %d not in a game", gm->err_args); return; } 591 SGame *old = s->games[c->game]; 592 /* si otro de la mesa ya pidio "otra", es esa: lo mando a su asiento en vez de crear una mas */ 593 if (old->again[0]) { 594 int gi = find_game(s, old->again); 595 if (gi >= 0 && !gm->is_over(s->games[gi]->st)) { 596 SGame *g = s->games[gi]; 597 for (int k = 0; k < g->np; k++) 598 if (g->kind[k] == 'P' && !strcmp(g->token[k], old->token[c->seat])) { detach(s, c); attach(s, c, gi, k); return; } 599 } 600 } 601 char opts[SRV_LINE], seats[SRV_MAX_SEATS + 1] = ""; 602 if (gm->again) gm->again(old->opts, opts, sizeof opts); 603 else snprintf(opts, sizeof opts, "%s", old->opts); 604 int np = old->np, myseat = c->seat; 605 for (int i = 0; i < np; i++) seats[i] = old->kind[i] == 'B' ? 'B' : old->kind[i] == 'N' ? 'N' : 'H'; 606 seats[np] = 0; 607 char oldcode[8]; 608 snprintf(oldcode, sizeof oldcode, "%s", old->code); 609 create_game(s, c, opts, seats, np, old, myseat); 610 int oi = find_game(s, oldcode); /* create_game puede mover el arreglo de partidas */ 611 if (oi >= 0 && c->game >= 0) snprintf(s->games[oi]->again, sizeof s->games[oi]->again, "%s", s->games[c->game]->code); 612 } 613 614 static void cmd_join(Server *s, Client *c, const char *code) 615 { 616 int gi = -1; 617 if (!strcmp(code, "-")) { 618 for (int i = s->ngames - 1; i >= 0 && gi < 0; i--) { 619 if (s->gm->is_over(s->games[i]->st)) continue; 620 for (int k = 0; k < s->games[i]->np; k++) if (s->games[i]->kind[k] == 'H') { gi = i; break; } 621 } 622 } else gi = find_game(s, code); 623 if (gi < 0) { sendf(s, c, "ERR %d no such game", s->gm->err_args); return; } 624 SGame *g = s->games[gi]; 625 /* con AUTH, el mismo nombre vuelve a su asiento si la clave coincide */ 626 if (c->key[0]) 627 for (int k = 0; k < g->np; k++) 628 if (g->kind[k] == 'P' && !strcasecmp(g->name[k], c->name)) { 629 if (strcmp(g->token[k], c->key)) { sendf(s, c, "ERR %d name taken or wrong password", s->gm->err_args); return; } 630 if (c->game >= 0) detach(s, c); 631 logf_(s, "%s volvio a %s (asiento %d)", c->name, g->code, k); 632 attach(s, c, gi, k); 633 return; 634 } 635 int seat = -1; 636 for (int k = 0; k < g->np; k++) if (g->kind[k] == 'H') { seat = k; break; } 637 if (seat < 0) { sendf(s, c, "ERR %d game is full", s->gm->err_args); return; } 638 if (c->game >= 0) detach(s, c); 639 g->kind[seat] = 'P'; 640 if (c->key[0]) memcpy(g->token[seat], c->key, 33); 641 else new_token(g->token[seat]); 642 snprintf(g->name[seat], sizeof g->name[seat], "%s", c->name); 643 save_meta(s, g); 644 write_export(s, g); /* el nombre nuevo */ 645 logf_(s, "%s se unio a %s (asiento %d)", c->name, g->code, seat); 646 attach(s, c, gi, seat); 647 broadcast_seat(s, g, seat); 648 } 649 650 static int find_seat(Server *s, const char *code, const char *token, int *seat) 651 { 652 int gi = find_game(s, code); 653 if (gi < 0 || strlen(token) != 32) return -1; 654 for (int k = 0; k < s->games[gi]->np; k++) 655 if (s->games[gi]->kind[k] == 'P' && !strcmp(s->games[gi]->token[k], token)) { *seat = k; return gi; } 656 return -1; 657 } 658 659 static void cmd_open(Server *s, Client *c, const char *args) 660 { 661 char code[16], token[40]; 662 int seat; 663 if (sscanf(args, "%15s %39s", code, token) != 2) { sendf(s, c, "ERR %d bad open", s->gm->err_args); return; } 664 int gi = find_seat(s, code, token, &seat); 665 if (gi < 0) { sendf(s, c, "ERR %d no such game or bad token", s->gm->err_args); return; } 666 if (c->game >= 0) detach(s, c); 667 attach(s, c, gi, seat); 668 } 669 670 static void send_stat(Server *s, Client *c, SGame *g, int seat) 671 { 672 char names[200] = "", st[128] = ""; 673 for (int i = 0; i < g->np; i++) { 674 if (i) strncat(names, "|", sizeof names - strlen(names) - 1); 675 strncat(names, g->name[i][0] ? g->name[i] : "-", sizeof names - strlen(names) - 1); 676 } 677 s->gm->status(g->st, seat, st, sizeof st); 678 sendf(s, c, "STAT %s %d %s %d %s", g->code, seat, st, g->np, names); 679 } 680 681 static void cmd_status(Server *s, Client *c, const char *args) 682 { 683 char code[16], token[40]; 684 int used; 685 while (sscanf(args, "%15s %39s%n", code, token, &used) == 2) { 686 args += used; 687 int seat; 688 int gi = find_seat(s, code, token, &seat); 689 if (gi >= 0) send_stat(s, c, s->games[gi], seat); 690 } 691 sendf(s, c, "STATEND"); 692 } 693 694 /* MINE: todas las partidas donde tu clave (AUTH) tiene asiento, desde cualquier compu */ 695 static void cmd_mine(Server *s, Client *c) 696 { 697 int sent = 0; 698 for (int i = s->ngames - 1; i >= 0 && c->key[0] && sent < 50; i--) { 699 SGame *g = s->games[i]; 700 for (int k = 0; k < g->np; k++) 701 if (g->kind[k] == 'P' && !strcmp(g->token[k], c->key)) { send_stat(s, c, g, k); sent++; break; } 702 } 703 sendf(s, c, "STATEND"); 704 } 705 706 static void cmd_move(Server *s, Client *c, const char *args) 707 { 708 if (c->game < 0) { sendf(s, c, "ERR %d not in a game", s->gm->err_turn); return; } 709 SGame *g = s->games[c->game]; 710 if (g->nhist >= SRV_MAX_MOVES) { sendf(s, c, "ERR %d game too long", s->gm->err_args); return; } 711 int e = s->gm->submit(g->st, c->seat, args); 712 if (e) sendf(s, c, "ERR %d rejected", e); 713 } 714 715 /* SAY <para|-1> <texto>: chat de la partida (para >= 0: privado a ese asiento) */ 716 static void cmd_say(Server *s, Client *c, const char *args) 717 { 718 if (c->game < 0) { sendf(s, c, "ERR %d not in a game", s->gm->err_turn); return; } 719 SGame *g = s->games[c->game]; 720 int to, used = 0; 721 if (sscanf(args, "%d %n", &to, &used) < 1 || to < -1 || to >= g->np || to == c->seat) { 722 sendf(s, c, "ERR %d bad say", s->gm->err_args); 723 return; 724 } 725 long long now = srv_now_ms(); 726 if (now - c->last_say < 300) return; /* nada de inundar */ 727 c->last_say = now; 728 char text[SRV_CHAT_LEN + 1]; 729 clean_name(args + used, text, sizeof text); 730 if (!strcmp(text, "?") && strcmp(args + used, "?")) return; /* vacio */ 731 char line[SRV_LINE]; 732 snprintf(line, sizeof line, "%d %d %s", c->seat, to, text); 733 if (!chat_add(g, line)) { sendf(s, c, "ERR %d chat full", s->gm->err_args); return; } 734 if (g->chatf) { fprintf(g->chatf, "%s\n", line); fflush(g->chatf); } 735 write_export(s, g); 736 for (int k = 0; k < SRV_MAX_CLIENTS; k++) { 737 Client *o = &s->cl[k]; 738 if (o->fd >= 0 && o->game >= 0 && s->games[o->game] == g && chat_visible(line, o->seat)) sendf(s, o, "CHAT %s", line); 739 } 740 } 741 742 static bool is_key(const char *k) 743 { 744 size_t n = strlen(k); 745 if (n != 32) return false; 746 for (size_t i = 0; i < n; i++) 747 if (!((k[i] >= '0' && k[i] <= '9') || (k[i] >= 'a' && k[i] <= 'f'))) return false; 748 return true; 749 } 750 751 /* ADMIN <secreto> RESET <codigo> <clave> <nombre>: cambia la clave del asiento humano 752 * con ese nombre (para quien se olvido la contrasena; el nombre va al final porque 753 * puede tener espacios); lo corre el administrador del VPS con <juego>-admin 754 * (comun/deploy/admin.sh). El secreto esta en <dir>/admin.key. */ 755 static void cmd_admin(Server *s, Client *c, const char *args) 756 { 757 int ea = s->gm->err_args; 758 char secret[40], what[16], code[16], name[20], key[40]; 759 int used = 0; 760 int n = sscanf(args, "%39s %15s %15s %39s %n", secret, what, code, key, &used); 761 if (n < 1 || !s->admin[0] || strcmp(secret, s->admin)) { sendf(s, c, "ERR %d admin", ea); drop(s, c); return; } 762 if (n != 4 || strcmp(what, "RESET") || !is_key(key) || !args[used]) { sendf(s, c, "ERR %d uso: ADMIN <secreto> RESET <codigo> <clave> <nombre>", ea); return; } 763 clean_name(args + used, name, sizeof name); 764 int gi = find_game(s, code); 765 if (gi < 0) { sendf(s, c, "ERR %d no such game", ea); return; } 766 SGame *g = s->games[gi]; 767 for (int k = 0; k < g->np; k++) { 768 if (g->kind[k] != 'P' || strcasecmp(g->name[k], name)) continue; 769 memcpy(g->token[k], key, 33); 770 save_meta(s, g); 771 for (int i = 0; i < SRV_MAX_CLIENTS; i++) { 772 Client *o = &s->cl[i]; 773 if (o != c && o->fd >= 0 && o->game == gi && o->seat == k) drop(s, o); 774 } 775 logf_(s, "%s: clave del asiento %d (%s) cambiada por el administrador", g->code, k, g->name[k]); 776 sendf(s, c, "OK %s %d %s", g->code, k, g->name[k]); 777 return; 778 } 779 sendf(s, c, "ERR %d no human seat with that name", ea); 780 } 781 782 static void handle(Server *s, Client *c, char *line) 783 { 784 char *sp = strchr(line, ' '); 785 const char *args = sp ? sp + 1 : ""; 786 if (sp) *sp = 0; 787 int ea = s->gm->err_args; 788 if (!c->hello) { 789 if (strcmp(line, "HELLO")) { sendf(s, c, "ERR %d say HELLO first", ea); drop(s, c); return; } 790 int ver = atoi(args); 791 const char *nm = strchr(args, ' '); 792 clean_name(nm ? nm + 1 : "?", c->name, sizeof c->name); 793 if (ver != PROTO_VERSION) { sendf(s, c, "ERR %d protocol version %d required", ea, PROTO_VERSION); drop(s, c); return; } 794 c->hello = true; 795 sendf(s, c, "WELCOME %d", PROTO_VERSION); 796 return; 797 } 798 if (!strcmp(line, "CREATE")) cmd_create(s, c, args); 799 else if (!strcmp(line, "AGAIN")) cmd_again(s, c); 800 else if (!strcmp(line, "JOIN")) cmd_join(s, c, args); 801 else if (!strcmp(line, "OPEN")) cmd_open(s, c, args); 802 else if (!strcmp(line, "STATUS")) cmd_status(s, c, args); 803 else if (!strcmp(line, "MINE")) cmd_mine(s, c); 804 else if (!strcmp(line, "SAY")) cmd_say(s, c, args); 805 else if (!strcmp(line, "MOVE")) cmd_move(s, c, args); 806 else if (!strcmp(line, "AUTH")) { 807 if (!is_key(args)) { sendf(s, c, "ERR %d bad key", ea); return; } 808 memcpy(c->key, args, 33); 809 } 810 else if (!strcmp(line, "ADMIN")) cmd_admin(s, c, args); 811 else if (!strcmp(line, "LEAVE")) detach(s, c); 812 else if (!strcmp(line, "PING")) sendf(s, c, "PONG"); 813 else sendf(s, c, "ERR %d unknown command", ea); 814 } 815 816 /* ------------------------------------------------------------ red */ 817 818 Server *srv_open(const GameModule *gm, int port, const char *datadir, bool quiet, char *err, int errlen) 819 { 820 Server *s = calloc(1, sizeof *s); 821 if (!s) return 0; 822 s->gm = gm; 823 s->quiet = quiet; 824 for (int i = 0; i < SRV_MAX_CLIENTS; i++) { s->cl[i].fd = -1; s->cl[i].game = -1; } 825 sock_init(); 826 if (datadir && *datadir) { 827 snprintf(s->dir, sizeof s->dir, "%s", datadir); 828 mkdir(s->dir, 0700); 829 /* secreto del comando ADMIN: se crea la primera vez, solo lo lee el usuario del servidor */ 830 char path[600]; 831 snprintf(path, sizeof path, "%s/admin.key", s->dir); 832 FILE *f = fopen(path, "r"); 833 if (f) { 834 if (fscanf(f, "%32s", s->admin) != 1 || !is_key(s->admin)) s->admin[0] = 0; 835 fclose(f); 836 } 837 if (!s->admin[0]) { 838 int fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0600); /* nace ya con 0600 */ 839 if (fd >= 0 && (f = fdopen(fd, "w"))) { 840 new_token(s->admin); 841 fprintf(f, "%s\n", s->admin); 842 fclose(f); 843 } else if (fd >= 0) close(fd); 844 } 845 } 846 s->lfd = (int)socket(AF_INET6, SOCK_STREAM, 0); 847 bool v6 = s->lfd >= 0; 848 if (!v6) s->lfd = (int)socket(AF_INET, SOCK_STREAM, 0); 849 if (s->lfd < 0) { snprintf(err, (size_t)errlen, "socket: %s", sock_strerror(sock_err())); free(s); return 0; } 850 int one = 1, zero = 0; 851 #ifndef _WIN32 /* en Windows SO_REUSEADDR deja a otro robar el puerto */ 852 setsockopt(s->lfd, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof one); 853 #endif 854 (void)one; 855 int r; 856 if (v6) { 857 setsockopt(s->lfd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&zero, sizeof zero); 858 struct sockaddr_in6 a; 859 memset(&a, 0, sizeof a); 860 a.sin6_family = AF_INET6; 861 a.sin6_addr = in6addr_any; 862 a.sin6_port = htons((uint16_t)port); 863 r = bind(s->lfd, (struct sockaddr *)&a, sizeof a); 864 } else { 865 struct sockaddr_in a; 866 memset(&a, 0, sizeof a); 867 a.sin_family = AF_INET; 868 a.sin_addr.s_addr = htonl(INADDR_ANY); 869 a.sin_port = htons((uint16_t)port); 870 r = bind(s->lfd, (struct sockaddr *)&a, sizeof a); 871 } 872 if (r < 0 || listen(s->lfd, 16) < 0) { 873 snprintf(err, (size_t)errlen, "puerto %d: %s", port, sock_strerror(sock_err())); 874 sock_close(s->lfd); 875 free(s); 876 return 0; 877 } 878 s->port = port; 879 if (s->dir[0]) load_all(s); 880 logf_(s, "escuchando en el puerto %d", port); 881 return s; 882 } 883 884 int srv_port(Server *s) { return s->port; } 885 886 int srv_fds(Server *s, int *fds, int max) 887 { 888 int n = 0; 889 if (n < max) fds[n++] = s->lfd; 890 for (int i = 0; i < SRV_MAX_CLIENTS && n < max; i++) 891 if (s->cl[i].fd >= 0) fds[n++] = s->cl[i].fd; 892 return n; 893 } 894 895 int srv_timeout(Server *s) 896 { 897 long long now = srv_now_ms(), best = 60000; 898 for (int i = 0; i < s->ngames; i++) { 899 long long t = s->gm->next_wake(s->games[i]->st); 900 if (t < 0) continue; 901 long long d = t - now; 902 if (d < best) best = d < 0 ? 0 : d; 903 } 904 return (int)best; 905 } 906 907 static void accept_one(Server *s) 908 { 909 int fd = (int)accept(s->lfd, 0, 0); 910 if (fd < 0) return; 911 for (int i = 0; i < SRV_MAX_CLIENTS; i++) 912 if (s->cl[i].fd < 0) { 913 Client *c = &s->cl[i]; 914 memset(c, 0, sizeof *c); 915 c->fd = fd; 916 c->game = -1; 917 c->since = c->last = srv_now_ms(); 918 #ifdef _WIN32 919 DWORD tv = 3000; 920 #else 921 struct timeval tv = { 3, 0 }; 922 #endif 923 setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (void *)&tv, sizeof tv); 924 return; 925 } 926 sock_close(fd); 927 } 928 929 /* corta en lineas lo que haya en in y las despacha */ 930 static void feed_lines(Server *s, Client *c) 931 { 932 c->in[c->inlen] = 0; 933 char *start = c->in, *nl; 934 while (c->fd >= 0 && (nl = memchr(start, '\n', (size_t)(c->in + c->inlen - start)))) { 935 *nl = 0; 936 if (nl > start && nl[-1] == '\r') nl[-1] = 0; 937 if (nl - start < SRV_LINE) handle(s, c, start); 938 start = nl + 1; 939 } 940 if (c->fd < 0) return; 941 c->inlen -= (int)(start - c->in); 942 memmove(c->in, start, (size_t)c->inlen); 943 if (c->inlen >= SRV_LINE) drop(s, c); 944 } 945 946 #ifndef SRV_NO_WS 947 /* Procesa wsin: primero el handshake HTTP, despues frames cuyo payload va a in. */ 948 static void ws_input(Server *s, Client *c) 949 { 950 if (c->ws == 1) { 951 char resp[512]; 952 int used = 0, r = ws_handshake((const char *)c->wsin, c->wslen, &used, resp, sizeof resp); 953 if (r == 0) { if (c->wslen >= (int)sizeof c->wsin) drop(s, c); return; } 954 if (r < 0) { send_all(s, c, resp, (int)strlen(resp)); drop(s, c); return; } 955 send_all(s, c, resp, r); 956 if (c->fd < 0) return; 957 c->ws = 2; 958 c->wslen -= used; 959 memmove(c->wsin, c->wsin + used, (size_t)c->wslen); 960 } 961 int got = 0, ctl = 0, pinglen = 0; 962 uint8_t ping[WS_CTL_MAX]; 963 int used = ws_unwrap(c->wsin, c->wslen, c->in + c->inlen, (int)(sizeof c->in - 1 - (size_t)c->inlen), &got, &ctl, ping, &pinglen); 964 if (used < 0) { drop(s, c); return; } 965 if (used == 0 && c->wslen >= (int)sizeof c->wsin) { drop(s, c); return; } /* un frame mas grande que el buffer */ 966 c->inlen += got; 967 c->wslen -= used; 968 memmove(c->wsin, c->wsin + used, (size_t)c->wslen); 969 if (ctl & (1 << WS_PING)) { uint8_t fr[WS_CTL_MAX + 2]; send_all(s, c, fr, ws_wrap(WS_PONG, ping, pinglen, fr, sizeof fr)); } 970 if (c->fd >= 0) feed_lines(s, c); 971 if ((ctl & (1 << WS_CLOSE)) && c->fd >= 0) { uint8_t fr[2]; send_all(s, c, fr, ws_wrap(WS_CLOSE, 0, 0, fr, sizeof fr)); drop(s, c); } 972 } 973 #endif 974 975 static void read_client(Server *s, Client *c) 976 { 977 #ifndef SRV_NO_WS 978 if (c->ws) { 979 ssize_t r = recv(c->fd, c->wsin + c->wslen, (int)(sizeof c->wsin - (size_t)c->wslen), 0); 980 if (r <= 0) { drop(s, c); return; } 981 c->last = srv_now_ms(); 982 c->wslen += (int)r; 983 ws_input(s, c); 984 return; 985 } 986 #endif 987 ssize_t r = recv(c->fd, c->in + c->inlen, (int)(sizeof c->in - (size_t)c->inlen - 1), 0); 988 if (r <= 0) { drop(s, c); return; } 989 c->last = srv_now_ms(); 990 c->inlen += (int)r; 991 #ifndef SRV_NO_WS 992 /* un navegador: lo primero que manda es un pedido HTTP de upgrade, no HELLO */ 993 if (!c->hello && c->inlen < 4 && !memcmp(c->in, "GET ", (size_t)c->inlen)) return; /* puede ser "GE": esperar */ 994 if (!c->hello && c->inlen == (int)r && r >= 4 && !memcmp(c->in, "GET ", 4)) { 995 memcpy(c->wsin, c->in, (size_t)r); 996 c->wslen = (int)r; 997 c->inlen = 0; 998 c->ws = 1; 999 ws_input(s, c); 1000 return; 1001 } 1002 #endif 1003 feed_lines(s, c); 1004 } 1005 1006 void srv_poll(Server *s, int timeout_ms) 1007 { 1008 struct pollfd pf[SRV_MAX_CLIENTS + 1]; 1009 int idx[SRV_MAX_CLIENTS + 1], n = 0; 1010 pf[n].fd = s->lfd; pf[n].events = POLLIN; idx[n++] = -1; 1011 for (int i = 0; i < SRV_MAX_CLIENTS; i++) 1012 if (s->cl[i].fd >= 0) { pf[n].fd = s->cl[i].fd; pf[n].events = POLLIN; idx[n++] = i; } 1013 int bt = srv_timeout(s); 1014 if (timeout_ms < 0 || bt < timeout_ms) timeout_ms = bt; 1015 if (poll(pf, (nfds_t)n, timeout_ms) > 0) { 1016 for (int k = 0; k < n; k++) { 1017 if (!(pf[k].revents & (POLLIN | POLLHUP | POLLERR))) continue; 1018 if (idx[k] < 0) accept_one(s); 1019 else if (s->cl[idx[k]].fd == (int)pf[k].fd) read_client(s, &s->cl[idx[k]]); 1020 } 1021 } 1022 long long now = srv_now_ms(); 1023 for (int i = 0; i < SRV_MAX_CLIENTS; i++) { 1024 Client *c = &s->cl[i]; 1025 if (c->fd < 0) continue; 1026 if ((!c->hello && now - c->since > SRV_HELLO_MS) || now - c->last > SRV_IDLE_MS) drop(s, c); 1027 } 1028 for (int i = 0; i < s->ngames; i++) { 1029 long long t = s->gm->next_wake(s->games[i]->st); 1030 if (t >= 0 && now >= t) s->gm->wake(s->games[i]->st, now); 1031 } 1032 } 1033 1034 void srv_close(Server *s) 1035 { 1036 if (!s) return; 1037 for (int i = 0; i < SRV_MAX_CLIENTS; i++) if (s->cl[i].fd >= 0) sock_close(s->cl[i].fd); 1038 sock_close(s->lfd); 1039 for (int i = 0; i < s->ngames; i++) free_game(s->games[i]); 1040 free(s->games); 1041 free(s); 1042 } 1043 1044 /* ------------------------------------------------------------ daemon */ 1045 1046 static volatile sig_atomic_t stop; 1047 static void on_signal(int sig) { (void)sig; stop = 1; } 1048 1049 int srv_main(const GameModule *gm, const char *prog, int port, const char *data, int argc, char **argv) 1050 { 1051 bool quiet = false; 1052 for (int i = 1; i < argc; i++) { 1053 if (!strcmp(argv[i], "--port") && i + 1 < argc) port = atoi(argv[++i]); 1054 else if (!strcmp(argv[i], "--data") && i + 1 < argc) data = argv[++i]; 1055 else if (!strcmp(argv[i], "--quiet")) quiet = true; 1056 else { 1057 fprintf(stderr, "uso: %s [--port %d] [--data %s] [--quiet]\n", prog, port, data); 1058 return 1; 1059 } 1060 } 1061 #ifdef SIGPIPE 1062 signal(SIGPIPE, SIG_IGN); 1063 #endif 1064 signal(SIGINT, on_signal); 1065 signal(SIGTERM, on_signal); 1066 char err[256]; 1067 Server *s = srv_open(gm, port, data, quiet, err, sizeof err); 1068 if (!s) { fprintf(stderr, "%s: %s\n", prog, err); return 1; } 1069 while (!stop) srv_poll(s, 1000); 1070 srv_close(s); 1071 return 0; 1072 }