1 /*
2 * qstat
3 * by Steve Jankowski
4 *
5 * Doom3 / Quake4 protocol
6 * Copyright 2005 Ludwig Nussel
7 *
8 * Licensed under the Artistic License, see LICENSE.txt for license terms
9 */
10
11 #include <sys/types.h>
12 #ifndef _WIN32
13 #include <sys/socket.h>
14 #endif
15 #include <stdio.h>
16 #include <stdlib.h>
17
18 #include "qstat.h"
19 #include "debug.h"
20 #include "assert.h"
21
22 static const char doom3_master_query[] = "\xFF\xFFgetServers\x00\x00\x00\x00\x00\x00";
23 // version ^^^^^^^^^^^^^^^^
24 // null terminated mod string ^^^^
25 // filterbyte ^^^^
26
27 static const char quake4_master_query[] = "\xFF\xFFgetServers\x00\x00\x00\x00\x00\x00\x00\x00";
28 // version ^^^^^^^^^^^^^^^^
29 // null terminated mod string ^^^^
30 // null terminated player string ^^^^
31 // null terminated clan string ^^^^
32 // filterbyte ^^^^
33
34 static unsigned
put_param_string(struct qserver * server,const char * paramname,char * buf,unsigned buflen,unsigned off)35 put_param_string(struct qserver *server, const char *paramname, char *buf, unsigned buflen, unsigned off)
36 {
37 char *val = get_param_value(server, paramname, NULL);
38
39 if (val && (strlen(val) < buflen - off - 2)) {
40 strcpy(buf + off, val);
41 off += strlen(val) + 1;
42 } else {
43 buf[off++] = '\0';
44 }
45
46 return (off);
47 }
48
49
50 static char *
build_doom3_masterfilter(struct qserver * server,char * buf,unsigned * buflen,int q4)51 build_doom3_masterfilter(struct qserver *server, char *buf, unsigned *buflen, int q4)
52 {
53 int flen = 0;
54 char *pkt, *r, *sep = "";
55 unsigned char b = 0;
56 char *proto = server->query_arg;
57 unsigned ver;
58 unsigned off = 13;
59
60 if (!proto) {
61 if (q4) {
62 *buflen = sizeof(quake4_master_query);
63 return ((char *)quake4_master_query);
64 } else {
65 *buflen = sizeof(doom3_master_query);
66 return ((char *)doom3_master_query);
67 }
68 }
69
70 ver = (atoi(proto) & 0xFFFF) << 16;
71 proto = strchr(proto, '.');
72 if (proto && *++proto) {
73 ver |= (atoi(proto) & 0xFFFF);
74 }
75 if (q4) {
76 ver |= 1 << 31; // third party flag
77 }
78 memcpy(buf, doom3_master_query, sizeof(doom3_master_query));
79 put_long_little(ver, buf + off);
80 off += 4;
81
82 off = put_param_string(server, "game", buf, *buflen, off);
83
84 if (q4) {
85 off = put_param_string(server, "player", buf, *buflen, off);
86 off = put_param_string(server, "clan", buf, *buflen, off);
87 }
88
89 pkt = get_param_value(server, "status", NULL);
90 r = pkt;
91 while (pkt && sep) {
92 sep = strchr(r, ':');
93 if (sep) {
94 flen = sep - r;
95 } else {
96 flen = strlen(r);
97 }
98
99 if (strncmp(r, "password", flen) == 0) {
100 b |= 0x01;
101 } else if (strncmp(r, "nopassword", flen) == 0) {
102 b |= 0x02;
103 } else if (strncmp(r, "notfull", flen) == 0) {
104 b |= 0x04;
105 } else if (strncmp(r, "notfullnotempty", flen) == 0) {
106 b |= 0x08;
107 }
108 r = sep + 1;
109 }
110
111 pkt = get_param_value(server, "gametype", NULL);
112 if (pkt) {
113 if (strncmp(pkt, "dm", flen) == 0) {
114 b |= 0x10;
115 } else if (strncmp(pkt, "tourney", flen) == 0) {
116 b |= 0x20;
117 } else if (strncmp(pkt, "tdm", flen) == 0) {
118 b |= 0x30;
119 }
120 }
121
122 buf[off++] = (char)b;
123 *buflen = off;
124
125 return (buf);
126 }
127
128
129 query_status_t
send_doom3master_request_packet(struct qserver * server)130 send_doom3master_request_packet(struct qserver *server)
131 {
132 int rc = 0;
133 int packet_len = -1;
134 char *packet = NULL;
135 char query_buf[4096] = { 0 };
136
137 server->next_player_info = NO_PLAYER_INFO;
138
139 packet_len = sizeof(query_buf);
140 packet = build_doom3_masterfilter(server, query_buf, (unsigned *)&packet_len, 0);
141
142 rc = send(server->fd, packet, packet_len, 0);
143 if (rc == SOCKET_ERROR) {
144 return (send_error(server, rc));
145 }
146
147 if (server->retry1 == n_retries) {
148 gettimeofday(&server->packet_time1, NULL);
149 server->n_requests++;
150 } else {
151 server->n_retries++;
152 }
153 server->retry1--;
154 server->n_packets++;
155
156 return (INPROGRESS);
157 }
158
159
160 query_status_t
send_quake4master_request_packet(struct qserver * server)161 send_quake4master_request_packet(struct qserver *server)
162 {
163 int rc = 0;
164 int packet_len = -1;
165 char *packet = NULL;
166 char query_buf[4096] = { 0 };
167
168 server->next_player_info = NO_PLAYER_INFO;
169
170 packet_len = sizeof(query_buf);
171 packet = build_doom3_masterfilter(server, query_buf, (unsigned *)&packet_len, 1);
172
173 rc = send(server->fd, packet, packet_len, 0);
174 if (rc == SOCKET_ERROR) {
175 return (send_error(server, rc));
176 }
177
178 if (server->retry1 == n_retries) {
179 gettimeofday(&server->packet_time1, NULL);
180 server->n_requests++;
181 } else {
182 server->n_retries++;
183 }
184 server->retry1--;
185 server->n_packets++;
186
187 return (INPROGRESS);
188 }
189
190
191 static const char doom3_masterresponse[] = "\xFF\xFFservers";
192
193 query_status_t
deal_with_doom3master_packet(struct qserver * server,char * rawpkt,int pktlen)194 deal_with_doom3master_packet(struct qserver *server, char *rawpkt, int pktlen)
195 {
196 char *pkt, *dest;
197 int len;
198
199 server->ping_total += time_delta(&packet_recv_time, &server->packet_time1);
200
201 if ((pktlen < sizeof(doom3_masterresponse) + 6) || // at least one server
202 (pktlen - sizeof(doom3_masterresponse)) % 6 ||
203 (memcmp(doom3_masterresponse, rawpkt, sizeof(doom3_masterresponse)) != 0)) {
204 server->server_name = SERVERERROR;
205 server->master_pkt_len = 0;
206 malformed_packet(server, "too short or invalid response");
207 return (PKT_ERROR);
208 }
209
210 server->retry1 = 0; // received at least one packet so no need to retry
211
212 pkt = rawpkt + sizeof(doom3_masterresponse);
213 len = pktlen - sizeof(doom3_masterresponse);
214
215 server->master_pkt = (char *)realloc(server->master_pkt, server->master_pkt_len + len);
216
217 dest = server->master_pkt + server->master_pkt_len;
218
219 server->master_pkt_len += len;
220
221 while (len > 0) {
222 memcpy(dest, pkt, 4);
223 dest[4] = pkt[5];
224 dest[5] = pkt[4];
225 dest += 6;
226 pkt += 6;
227 len -= 6;
228 }
229 assert(len == 0);
230
231 server->n_servers = server->master_pkt_len / 6;
232
233 debug(2, "%d servers added", server->n_servers);
234
235 return (INPROGRESS);
236 }
237
238
239 static const char doom3_inforesponse[] = "\xFF\xFFinfoResponse";
240 static unsigned MAX_DOOM3_ASYNC_CLIENTS = 32;
241 static unsigned MAX_WOLF_ASYNC_CLIENTS = 16;
242
243 static query_status_t
_deal_with_doom3_packet(struct qserver * server,char * rawpkt,int pktlen,unsigned version)244 _deal_with_doom3_packet(struct qserver *server, char *rawpkt, int pktlen, unsigned version)
245 {
246 char *ptr = rawpkt;
247 char *end = rawpkt + pktlen;
248 int type = 0;
249 int size = 0;
250 int tail_size = 4;
251 int viewers = 0;
252 int tv = 0;
253 unsigned num_players = 0;
254 unsigned challenge = 0;
255 unsigned protocolver = 0;
256 char tmp[32];
257
258 server->n_servers++;
259 if (server->server_name == NULL) {
260 server->ping_total += time_delta(&packet_recv_time, &server->packet_time1);
261 } else {
262 gettimeofday(&server->packet_time1, NULL);
263 }
264
265 // Check if correct reply
266 if ((pktlen < sizeof(doom3_inforesponse) + 4 + 4 + 1) ||
267 (memcmp(doom3_inforesponse, ptr, sizeof(doom3_inforesponse)) != 0)) {
268 malformed_packet(server, "too short or invalid response");
269 return (PKT_ERROR);
270 }
271 ptr += sizeof(doom3_inforesponse);
272
273 if (5 == version) {
274 // TaskID
275 ptr += 4;
276 // osmask + ranked
277 tail_size++;
278 }
279
280 challenge = swap_long_from_little(ptr);
281 ptr += 4;
282
283 protocolver = swap_long_from_little(ptr);
284 ptr += 4;
285
286 snprintf(tmp, sizeof(tmp), "%u.%u", protocolver >> 16, protocolver & 0xFFFF);
287 debug(2, "challenge: 0x%08X, protocol: %s (0x%X)", challenge, tmp, protocolver);
288
289 server->protocol_version = protocolver;
290 add_rule(server, "protocol", tmp, NO_FLAGS);
291
292 if (5 == version) {
293 // Size Long
294 size = swap_long_from_little(ptr);
295 debug(2, "Size = %d", size);
296 ptr += 4;
297 }
298
299 // Commented out until we have a better way to specify the expected version
300 // This is due to prey demo requiring version 4 yet prey retail version 3
301
302 /*
303 * if( protocolver >> 16 != version )
304 * {
305 * malformed_packet(server, "protocol version %u, expected %u", protocolver >> 16, version );
306 * return PKT_ERROR;
307 * }
308 */
309
310 while (ptr < end) {
311 // server info:
312 // name value pairs null seperated
313 // empty name && value signifies the end of section
314 char *key, *val;
315 unsigned keylen, vallen;
316
317 key = ptr;
318 ptr = memchr(ptr, '\0', end - ptr);
319 if (!ptr) {
320 malformed_packet(server, "no rule key");
321 return (PKT_ERROR);
322 }
323 keylen = ptr - key;
324
325 val = ++ptr;
326 ptr = memchr(ptr, '\0', end - ptr);
327 if (!ptr) {
328 malformed_packet(server, "no rule value");
329 return (PKT_ERROR);
330 }
331 vallen = ptr - val;
332 ++ptr;
333
334 if ((keylen == 0) && (vallen == 0)) {
335 type = 1;
336 break; // end
337 }
338
339 debug(2, "key:%s=%s:", key, val);
340
341 // Lets see what we've got
342 if (0 == strcasecmp(key, "si_name")) {
343 server->server_name = strdup(val);
344 } else if (0 == strcasecmp(key, "fs_game")) {
345 server->game = strdup(val);
346 }
347 #if 0
348 else if (0 == strcasecmp(key, "si_version")) {
349 // format:
350 x
351 // DOOM 1.0.1262.win-x86 Jul 8 2004 16:46:37
352 server->protocol_version = atoi(val + 1);
353 }
354 #endif
355 else if (0 == strcasecmp(key, "si_map")) {
356 if ((5 == version) || (6 == version)) {
357 // ET:QW reports maps/<mapname>.entities
358 // so we strip that here if it exists
359 char *tmpp = strstr(val, ".entities");
360 if (NULL != tmpp) {
361 *tmpp = '\0';
362 }
363
364 if (0 == strncmp(val, "maps/", 5)) {
365 val += 5;
366 }
367 }
368 server->map_name = strdup(val);
369 } else if (0 == strcasecmp(key, "si_maxplayers")) {
370 server->max_players = atoi(val);
371 } else if (0 == strcasecmp(key, "ri_maxViewers")) {
372 char max[20];
373 sprintf(max, "%d", server->max_players);
374 add_rule(server, "si_maxplayers", max, NO_FLAGS);
375 server->max_players = atoi(val);
376 } else if (0 == strcasecmp(key, "ri_numViewers")) {
377 viewers = atoi(val);
378 tv = 1;
379 }
380
381 add_rule(server, key, val, NO_FLAGS);
382 }
383
384 if (type != 1) {
385 // no more info should be player headers here as we
386 // requested it
387 malformed_packet(server, "player info missing");
388 return (PKT_ERROR);
389 }
390
391 // now each player details
392 while (ptr < end - tail_size) {
393 struct player *player;
394 char *val;
395 unsigned char player_id = *ptr++;
396 short ping = 0;
397 unsigned rate = 0;
398
399 if (((6 == version) && (MAX_WOLF_ASYNC_CLIENTS == player_id)) || (MAX_DOOM3_ASYNC_CLIENTS == player_id)) {
400 break;
401 }
402 debug(2, "ID = %d\n", player_id);
403
404 // Note: id's are not steady
405 if (ptr + 7 > end) {// 2 ping + 4 rate + empty player name ('\0')
406 // run off the end and shouldnt have
407 malformed_packet(server, "player info too short");
408 return (PKT_ERROR);
409 }
410
411 /*
412 * if ( 6 == version )
413 * {
414 * // Playerid is broken in wolf its always 0
415 * player_id = num_players;
416 * }
417 */
418
419 player = add_player(server, player_id);
420 if (!player) {
421 malformed_packet(server, "duplicate player id");
422 return (PKT_ERROR);
423 }
424
425 // doesnt support score so set a sensible default
426 player->score = 0;
427 player->frags = 0;
428
429 // Ping
430 ping = swap_short_from_little(ptr);
431 player->ping = ping;
432 ptr += 2;
433
434 if ((5 != version) || (0xa0013 >= protocolver)) { // No Rate in ETQW since v1.4 ( protocol v10.19 )
435 // Rate
436 rate = swap_long_from_little(ptr);
437 {
438 char buf[16];
439 snprintf(buf, sizeof(buf), "%u", rate);
440 player_add_info(player, "rate", buf, NO_FLAGS);
441 }
442 ptr += 4;
443 }
444
445 if ((5 == version) && (((0xd0009 == protocolver) || (0xd000a == protocolver)) && (0 != num_players))) { // v13.9 or v13.10
446 // Fix the packet offset due to the single bit used for bot
447 // which realigns at the byte boundary for the player name
448 ptr++;
449 }
450
451 // Name
452 val = ptr;
453 ptr = memchr(ptr, '\0', end - ptr);
454 if (!ptr) {
455 malformed_packet(server, "player name not null terminated");
456 return (PKT_ERROR);
457 }
458 player->name = strdup(val);
459 ptr++;
460
461 switch (version) {
462 case 2: // Quake 4
463 val = ptr;
464 ptr = memchr(ptr, '\0', end - ptr);
465 if (!ptr) {
466 malformed_packet(server, "player clan not null terminated");
467 return (PKT_ERROR);
468 }
469 player->tribe_tag = strdup(val);
470 ptr++;
471 debug(2, "Player[%d] = %s, ping %hu, rate %u, id %hhu, clan %s",
472 num_players, player->name, ping, rate, player_id, player->tribe_tag);
473 break;
474
475 case 5: // ETQW
476 case 6: // Wolfenstien
477 if (0xa0011 <= protocolver) { // clan tag since 10.17
478 // clantag position
479 ptr++;
480
481 // clantag
482 val = ptr;
483 ptr = memchr(ptr, '\0', end - ptr);
484 if (!ptr) {
485 malformed_packet(server, "player clan not null terminated");
486 return (PKT_ERROR);
487 }
488 player->tribe_tag = strdup(val);
489 ptr++;
490 }
491
492 // Bot flag
493 if ((0xd0009 == protocolver) || (0xd000a == protocolver)) { // v13.9 or v13.10
494 // Bot flag is a single bit so need to realign everything from here on in :(
495 int i;
496 unsigned char *tp = (unsigned char *)ptr;
497 player->type_flag = (*tp) << 7;
498
499 // alignment is reset at the end
500 for (i = 0; i < 8 && tp < (unsigned char *)end; i++) {
501 *tp = (*tp) >> 1 | *(tp + 1) << 7;
502 tp++;
503 }
504 } else {
505 player->type_flag = *ptr++;
506 }
507
508 if (0xa0011 <= protocolver) { // clan tag since 10.17
509 debug(2, "Player[%d] = %s, ping %hu, rate %u, id %hhu, bot %hu, clan %s",
510 num_players, player->name, ping, rate, player_id, player->type_flag, player->tribe_tag);
511 } else {
512 debug(2, "Player[%d] = %s, ping %hu, rate %u, id %hhu, bot %hu",
513 num_players, player->name, ping, rate, player_id, player->type_flag);
514 }
515 break;
516
517 default:
518 debug(2, "Player[%d] = %s, ping %hu, rate %u, id %hhu", num_players, player->name, ping, rate, player_id);
519 }
520
521 ++num_players;
522 }
523
524 if (end - ptr >= 4) {
525 // OS Mask
526 snprintf(tmp, sizeof(tmp), "0x%X", swap_long_from_little(ptr));
527 add_rule(server, "osmask", tmp, NO_FLAGS);
528 debug(2, "osmask %s", tmp);
529 ptr += 4;
530
531 if ((5 == version) && (end - ptr >= 1)) {
532 // Ranked flag
533 snprintf(tmp, sizeof(tmp), "%hhu", *ptr++);
534 add_rule(server, "ranked", tmp, NO_FLAGS);
535
536 if (end - ptr >= 5) {
537 // Time Left
538 snprintf(tmp, sizeof(tmp), "%d", swap_long_from_little(ptr));
539 add_rule(server, "timeleft", tmp, NO_FLAGS);
540 ptr += 4;
541
542 // Game State
543 snprintf(tmp, sizeof(tmp), "%hhu", *ptr++);
544 add_rule(server, "gamestate", tmp, NO_FLAGS);
545
546 if (end - ptr >= 1) {
547 // Server Type
548 unsigned char servertype = *ptr++;
549 snprintf(tmp, sizeof(tmp), "%hhu", servertype);
550 add_rule(server, "servertype", tmp, NO_FLAGS);
551
552 switch (servertype) {
553 case 0: // Regular Server
554 // Interested Clients
555 snprintf(tmp, sizeof(tmp), "%hhu", *ptr++);
556 add_rule(server, "interested_clients", tmp, NO_FLAGS);
557 break;
558
559 case 1: // TV Server
560 // Connected Clients
561 snprintf(tmp, sizeof(tmp), "%d", swap_long_from_little(ptr));
562 ptr += 4;
563 add_rule(server, "interested_clients", tmp, NO_FLAGS);
564
565 // Max Clients
566 snprintf(tmp, sizeof(tmp), "%d", swap_long_from_little(ptr));
567 ptr += 4;
568 add_rule(server, "interested_clients", tmp, NO_FLAGS);
569 break;
570
571 default:
572 // Unknown
573 if (show_errors) {
574 fprintf(stderr, "Unknown server type %d\n", servertype);
575 }
576 }
577 }
578 }
579 }
580 } else {
581 malformed_packet(server, "osmask missing");
582 }
583
584 #if 0
585 if (end - ptr) {
586 malformed_packet(server, "%ld byes left", end - ptr);
587 }
588 #endif
589
590 if (0 == tv) {
591 debug(2, "Num players = %d", num_players);
592 server->num_players = num_players;
593 } else {
594 server->num_players = viewers;
595 }
596
597 return (DONE_FORCE);
598 }
599
600
601 query_status_t
deal_with_doom3_packet(struct qserver * server,char * rawpkt,int pktlen)602 deal_with_doom3_packet(struct qserver *server, char *rawpkt, int pktlen)
603 {
604 return (_deal_with_doom3_packet(server, rawpkt, pktlen, 1));
605 }
606
607
608 query_status_t
deal_with_quake4_packet(struct qserver * server,char * rawpkt,int pktlen)609 deal_with_quake4_packet(struct qserver *server, char *rawpkt, int pktlen)
610 {
611 return (_deal_with_doom3_packet(server, rawpkt, pktlen, 2));
612 }
613
614
615 query_status_t
deal_with_prey_demo_packet(struct qserver * server,char * rawpkt,int pktlen)616 deal_with_prey_demo_packet(struct qserver *server, char *rawpkt, int pktlen)
617 {
618 return (_deal_with_doom3_packet(server, rawpkt, pktlen, 4));
619 }
620
621
622 query_status_t
deal_with_prey_packet(struct qserver * server,char * rawpkt,int pktlen)623 deal_with_prey_packet(struct qserver *server, char *rawpkt, int pktlen)
624 {
625 return (_deal_with_doom3_packet(server, rawpkt, pktlen, 3));
626 }
627
628
629 query_status_t
deal_with_etqw_packet(struct qserver * server,char * rawpkt,int pktlen)630 deal_with_etqw_packet(struct qserver *server, char *rawpkt, int pktlen)
631 {
632 return (_deal_with_doom3_packet(server, rawpkt, pktlen, 5));
633 }
634
635
636 query_status_t
deal_with_wolf_packet(struct qserver * server,char * rawpkt,int pktlen)637 deal_with_wolf_packet(struct qserver *server, char *rawpkt, int pktlen)
638 {
639 return (_deal_with_doom3_packet(server, rawpkt, pktlen, 6));
640 }
641