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