1 // SONIC ROBO BLAST 2
2 //-----------------------------------------------------------------------------
3 // Copyright (C) 2020 by James R.
4 //
5 // This program is free software distributed under the
6 // terms of the GNU General Public License, version 2.
7 // See the 'LICENSE' file for more details.
8 //-----------------------------------------------------------------------------
9 // \brief HTTP based master server
10
11 /*
12 Documentation available here.
13
14 <http://mb.srb2.org/MS/tools/api/v1/>
15 */
16
17 #ifdef HAVE_CURL
18 #include <curl/curl.h>
19 #endif
20
21 #include "doomdef.h"
22 #include "d_clisrv.h"
23 #include "command.h"
24 #include "m_argv.h"
25 #include "m_menu.h"
26 #include "mserv.h"
27 #include "i_tcp.h"/* for current_port */
28 #include "i_threads.h"
29
30 /* reasonable default I guess?? */
31 #define DEFAULT_BUFFER_SIZE (4096)
32
33 /* I just stop myself from making macros anymore. */
34 #define Blame( ... ) \
35 CONS_Printf("\x85" __VA_ARGS__)
36
37 static void MasterServer_Debug_OnChange (void);
38
39 consvar_t cv_masterserver_timeout = CVAR_INIT
40 (
41 "masterserver_timeout", "5", CV_SAVE, CV_Unsigned,
42 NULL
43 );
44
45 consvar_t cv_masterserver_debug = CVAR_INIT
46 (
47 "masterserver_debug", "Off", CV_SAVE|CV_CALL, CV_OnOff,
48 MasterServer_Debug_OnChange
49 );
50
51 consvar_t cv_masterserver_token = CVAR_INIT
52 (
53 "masterserver_token", "", CV_SAVE, NULL,
54 NULL
55 );
56
57 #ifdef MASTERSERVER
58
59 static int hms_started;
60
61 static char *hms_api;
62 #ifdef HAVE_THREADS
63 static I_mutex hms_api_mutex;
64 #endif
65
66 static char *hms_server_token;
67
68 struct HMS_buffer
69 {
70 CURL *curl;
71 char *buffer;
72 int needle;
73 int end;
74 };
75
76 static void
Contact_error(void)77 Contact_error (void)
78 {
79 CONS_Alert(CONS_ERROR,
80 "There was a problem contacting the master server...\n"
81 );
82 }
83
84 static size_t
HMS_on_read(char * s,size_t _1,size_t n,void * userdata)85 HMS_on_read (char *s, size_t _1, size_t n, void *userdata)
86 {
87 struct HMS_buffer *buffer;
88 size_t blocks;
89
90 (void)_1;
91
92 buffer = userdata;
93
94 if (n >= (size_t)( buffer->end - buffer->needle ))
95 {
96 /* resize to next multiple of buffer size */
97 blocks = ( n / DEFAULT_BUFFER_SIZE + 1 );
98 buffer->end += ( blocks * DEFAULT_BUFFER_SIZE );
99
100 buffer->buffer = realloc(buffer->buffer, buffer->end);
101 }
102
103 memcpy(&buffer->buffer[buffer->needle], s, n);
104 buffer->needle += n;
105
106 return n;
107 }
108
109 static struct HMS_buffer *
HMS_connect(const char * format,...)110 HMS_connect (const char *format, ...)
111 {
112 va_list ap;
113 CURL *curl;
114 char *url;
115 char *quack_token;
116 size_t seek;
117 size_t token_length;
118 struct HMS_buffer *buffer;
119
120 if (! hms_started)
121 {
122 if (curl_global_init(CURL_GLOBAL_ALL) != 0)
123 {
124 Contact_error();
125 Blame("From curl_global_init.\n");
126 return NULL;
127 }
128 else
129 {
130 atexit(curl_global_cleanup);
131 hms_started = 1;
132 }
133 }
134
135 curl = curl_easy_init();
136
137 if (! curl)
138 {
139 Contact_error();
140 Blame("From curl_easy_init.\n");
141 return NULL;
142 }
143
144 if (cv_masterserver_token.string[0])
145 {
146 quack_token = curl_easy_escape(curl, cv_masterserver_token.string, 0);
147 token_length = ( sizeof "?token="-1 )+ strlen(quack_token);
148 }
149 else
150 {
151 quack_token = NULL;
152 token_length = 0;
153 }
154
155 #ifdef HAVE_THREADS
156 I_lock_mutex(&hms_api_mutex);
157 #endif
158
159 seek = strlen(hms_api) + 1;/* + '/' */
160
161 va_start (ap, format);
162 url = malloc(seek + vsnprintf(0, 0, format, ap) + token_length + 1);
163 va_end (ap);
164
165 sprintf(url, "%s/", hms_api);
166
167 #ifdef HAVE_THREADS
168 I_unlock_mutex(hms_api_mutex);
169 #endif
170
171 va_start (ap, format);
172 seek += vsprintf(&url[seek], format, ap);
173 va_end (ap);
174
175 if (quack_token)
176 sprintf(&url[seek], "?token=%s", quack_token);
177
178 CONS_Printf("HMS: connecting '%s'...\n", url);
179
180 buffer = malloc(sizeof *buffer);
181 buffer->curl = curl;
182 buffer->end = DEFAULT_BUFFER_SIZE;
183 buffer->buffer = malloc(buffer->end);
184 buffer->needle = 0;
185
186 if (cv_masterserver_debug.value)
187 {
188 curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
189 curl_easy_setopt(curl, CURLOPT_STDERR, logstream);
190 }
191
192 if (M_CheckParm("-bindaddr") && M_IsNextParm())
193 {
194 curl_easy_setopt(curl, CURLOPT_INTERFACE, M_GetNextParm());
195 }
196
197 curl_easy_setopt(curl, CURLOPT_URL, url);
198 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
199 curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
200
201 curl_easy_setopt(curl, CURLOPT_TIMEOUT, cv_masterserver_timeout.value);
202 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, HMS_on_read);
203 curl_easy_setopt(curl, CURLOPT_WRITEDATA, buffer);
204
205 curl_free(quack_token);
206 free(url);
207
208 return buffer;
209 }
210
211 static int
HMS_do(struct HMS_buffer * buffer)212 HMS_do (struct HMS_buffer *buffer)
213 {
214 CURLcode cc;
215 long status;
216
217 char *p;
218
219 cc = curl_easy_perform(buffer->curl);
220
221 if (cc != CURLE_OK)
222 {
223 Contact_error();
224 Blame(
225 "From curl_easy_perform: %s\n",
226 curl_easy_strerror(cc)
227 );
228 return 0;
229 }
230
231 buffer->buffer[buffer->needle] = '\0';
232
233 curl_easy_getinfo(buffer->curl, CURLINFO_RESPONSE_CODE, &status);
234
235 if (status != 200)
236 {
237 p = strchr(buffer->buffer, '\n');
238
239 if (p)
240 *p = '\0';
241
242 Contact_error();
243 Blame(
244 "Master server error %ld: %s%s\n",
245 status,
246 buffer->buffer,
247 ( (p) ? "" : " (malformed)" )
248 );
249
250 return 0;
251 }
252 else
253 return 1;
254 }
255
256 static void
HMS_end(struct HMS_buffer * buffer)257 HMS_end (struct HMS_buffer *buffer)
258 {
259 curl_easy_cleanup(buffer->curl);
260 free(buffer->buffer);
261 free(buffer);
262 }
263
264 int
HMS_fetch_rooms(int joining,int query_id)265 HMS_fetch_rooms (int joining, int query_id)
266 {
267 struct HMS_buffer *hms;
268 int ok;
269
270 int doing_shit;
271
272 char *id;
273 char *title;
274 char *room_motd;
275
276 int id_no;
277
278 char *p;
279 char *end;
280
281 int i;
282
283 (void)query_id;
284
285 hms = HMS_connect("rooms");
286
287 if (! hms)
288 return 0;
289
290 if (HMS_do(hms))
291 {
292 doing_shit = 1;
293
294 p = hms->buffer;
295
296 for (i = 0; i < NUM_LIST_ROOMS && ( end = strstr(p, "\n\n\n") );)
297 {
298 *end = '\0';
299
300 id = strtok(p, "\n");
301 title = strtok(0, "\n");
302 room_motd = strtok(0, "");
303
304 if (id && title && room_motd)
305 {
306 id_no = atoi(id);
307
308 /*
309 Don't show the 'All' room if hosting. And it's a hack like this
310 because I'm way too lazy to add another feature to the MS.
311 */
312 if (joining || id_no != 0)
313 {
314 #ifdef HAVE_THREADS
315 I_lock_mutex(&ms_QueryId_mutex);
316 {
317 if (query_id != ms_QueryId)
318 doing_shit = 0;
319 }
320 I_unlock_mutex(ms_QueryId_mutex);
321
322 if (! doing_shit)
323 break;
324 #endif
325
326 room_list[i].header.buffer[0] = 1;
327
328 room_list[i].id = id_no;
329 strlcpy(room_list[i].name, title, sizeof room_list[i].name);
330 strlcpy(room_list[i].motd, room_motd, sizeof room_list[i].motd);
331
332 i++;
333 }
334
335 p = ( end + 3 );/* skip the three linefeeds */
336 }
337 else
338 break;
339 }
340
341 if (doing_shit)
342 room_list[i].header.buffer[0] = 0;
343
344 ok = 1;
345
346 if (doing_shit)
347 {
348 #ifdef HAVE_THREADS
349 I_lock_mutex(&m_menu_mutex);
350 #endif
351 {
352 for (i = 0; room_list[i].header.buffer[0]; i++)
353 {
354 if(*room_list[i].name != '\0')
355 {
356 MP_RoomMenu[i+1].text = room_list[i].name;
357 roomIds[i] = room_list[i].id;
358 MP_RoomMenu[i+1].status = IT_STRING|IT_CALL;
359 }
360 }
361 }
362 #ifdef HAVE_THREADS
363 I_unlock_mutex(m_menu_mutex);
364 #endif
365 }
366 }
367 else
368 ok = 0;
369
370 HMS_end(hms);
371
372 return ok;
373 }
374
375 int
HMS_register(void)376 HMS_register (void)
377 {
378 struct HMS_buffer *hms;
379 int ok;
380
381 char post[256];
382
383 char *title;
384
385 hms = HMS_connect("rooms/%d/register", ms_RoomId);
386
387 if (! hms)
388 return 0;
389
390 title = curl_easy_escape(hms->curl, cv_servername.string, 0);
391
392 snprintf(post, sizeof post,
393 "port=%d&"
394 "title=%s&"
395 "version=%s",
396
397 current_port,
398
399 title,
400
401 SRB2VERSION
402 );
403
404 curl_free(title);
405
406 curl_easy_setopt(hms->curl, CURLOPT_POSTFIELDS, post);
407
408 ok = HMS_do(hms);
409
410 if (ok)
411 {
412 hms_server_token = strdup(strtok(hms->buffer, "\n"));
413 }
414
415 HMS_end(hms);
416
417 return ok;
418 }
419
420 int
HMS_unlist(void)421 HMS_unlist (void)
422 {
423 struct HMS_buffer *hms;
424 int ok;
425
426 hms = HMS_connect("servers/%s/unlist", hms_server_token);
427
428 if (! hms)
429 return 0;
430
431 curl_easy_setopt(hms->curl, CURLOPT_CUSTOMREQUEST, "POST");
432
433 ok = HMS_do(hms);
434 HMS_end(hms);
435
436 free(hms_server_token);
437
438 return ok;
439 }
440
441 int
HMS_update(void)442 HMS_update (void)
443 {
444 struct HMS_buffer *hms;
445 int ok;
446
447 char post[256];
448
449 char *title;
450
451 hms = HMS_connect("servers/%s/update", hms_server_token);
452
453 if (! hms)
454 return 0;
455
456 title = curl_easy_escape(hms->curl, cv_servername.string, 0);
457
458 snprintf(post, sizeof post,
459 "title=%s",
460 title
461 );
462
463 curl_free(title);
464
465 curl_easy_setopt(hms->curl, CURLOPT_POSTFIELDS, post);
466
467 ok = HMS_do(hms);
468 HMS_end(hms);
469
470 return ok;
471 }
472
473 void
HMS_list_servers(void)474 HMS_list_servers (void)
475 {
476 struct HMS_buffer *hms;
477
478 char *list;
479 char *p;
480
481 hms = HMS_connect("servers");
482
483 if (! hms)
484 return;
485
486 if (HMS_do(hms))
487 {
488 list = curl_easy_unescape(hms->curl, hms->buffer, 0, NULL);
489
490 p = strtok(list, "\n");
491
492 while (p != NULL)
493 {
494 CONS_Printf("\x80%s\n", p);
495 p = strtok(NULL, "\n");
496 }
497
498 curl_free(list);
499 }
500
501 HMS_end(hms);
502 }
503
504 msg_server_t *
HMS_fetch_servers(msg_server_t * list,int room_number,int query_id)505 HMS_fetch_servers (msg_server_t *list, int room_number, int query_id)
506 {
507 struct HMS_buffer *hms;
508
509 int doing_shit;
510
511 char local_version[9];
512
513 char *room;
514
515 char *address;
516 char *port;
517 char *title;
518 char *version;
519
520 char *end;
521 char *section_end;
522 char *p;
523
524 int i;
525
526 (void)query_id;
527
528 if (room_number > 0)
529 {
530 hms = HMS_connect("rooms/%d/servers", room_number);
531 }
532 else
533 hms = HMS_connect("servers");
534
535 if (! hms)
536 return NULL;
537
538 if (HMS_do(hms))
539 {
540 doing_shit = 1;
541
542 snprintf(local_version, sizeof local_version,
543 "%s",
544 SRB2VERSION
545 );
546
547 p = hms->buffer;
548 i = 0;
549
550 do
551 {
552 section_end = strstr(p, "\n\n");
553
554 room = strtok(p, "\n");
555
556 p = strtok(0, "");
557
558 if (! p)
559 break;
560
561 while (i < MAXSERVERLIST && ( end = strchr(p, '\n') ))
562 {
563 *end = '\0';
564
565 address = strtok(p, " ");
566 port = strtok(0, " ");
567 title = strtok(0, " ");
568 version = strtok(0, "");
569
570 if (address && port && title && version)
571 {
572 #ifdef HAVE_THREADS
573 I_lock_mutex(&ms_QueryId_mutex);
574 {
575 if (query_id != ms_QueryId)
576 doing_shit = 0;
577 }
578 I_unlock_mutex(ms_QueryId_mutex);
579
580 if (! doing_shit)
581 break;
582 #endif
583
584 if (strcmp(version, local_version) == 0)
585 {
586 strlcpy(list[i].ip, address, sizeof list[i].ip);
587 strlcpy(list[i].port, port, sizeof list[i].port);
588 strlcpy(list[i].name, title, sizeof list[i].name);
589 strlcpy(list[i].version, version, sizeof list[i].version);
590
591 list[i].room = atoi(room);
592
593 list[i].header.buffer[0] = 1;
594
595 i++;
596 }
597
598 if (end == section_end)/* end of list for this room */
599 break;
600 else
601 p = ( end + 1 );/* skip server delimiter */
602 }
603 else
604 {
605 section_end = 0;/* malformed so quit the parsing */
606 break;
607 }
608 }
609
610 if (! doing_shit)
611 break;
612
613 p = ( section_end + 2 );
614 }
615 while (section_end) ;
616
617 if (doing_shit)
618 list[i].header.buffer[0] = 0;
619 }
620 else
621 list = NULL;
622
623 HMS_end(hms);
624
625 return list;
626 }
627
628 int
HMS_compare_mod_version(char * buffer,size_t buffer_size)629 HMS_compare_mod_version (char *buffer, size_t buffer_size)
630 {
631 struct HMS_buffer *hms;
632 int ok;
633
634 char *version;
635 char *version_name;
636
637 hms = HMS_connect("versions/%d", MODID);
638
639 if (! hms)
640 return 0;
641
642 ok = 0;
643
644 if (HMS_do(hms))
645 {
646 version = strtok(hms->buffer, " ");
647 version_name = strtok(0, "\n");
648
649 if (version && version_name)
650 {
651 if (atoi(version) != MODVERSION)
652 {
653 strlcpy(buffer, version_name, buffer_size);
654 ok = 1;
655 }
656 else
657 ok = -1;
658 }
659 }
660
661 HMS_end(hms);
662
663 return ok;
664 }
665
666 void
HMS_set_api(char * api)667 HMS_set_api (char *api)
668 {
669 #ifdef HAVE_THREADS
670 I_lock_mutex(&hms_api_mutex);
671 #endif
672 {
673 free(hms_api);
674 hms_api = api;
675 }
676 #ifdef HAVE_THREADS
677 I_unlock_mutex(hms_api_mutex);
678 #endif
679 }
680
681 #endif/*MASTERSERVER*/
682
683 static void
MasterServer_Debug_OnChange(void)684 MasterServer_Debug_OnChange (void)
685 {
686 #ifdef MASTERSERVER
687 /* TODO: change to 'latest-log.txt' for log files revision. */
688 if (cv_masterserver_debug.value)
689 CONS_Printf("Master server debug messages will appear in log.txt\n");
690 #endif
691 }
692