1 /*
2  * qstat
3  * by Steve Jankowski
4  *
5  * New Haze query protocol
6  * Copyright 2005 Steven Hartland
7  *
8  * Licensed under the Artistic License, see LICENSE.txt for license terms
9  *
10  */
11 
12 #include <sys/types.h>
13 #ifndef _WIN32
14  #include <netinet/in.h>
15  #include <sys/socket.h>
16 #endif
17 #include <stdlib.h>
18 #include <stdio.h>
19 
20 #include "debug.h"
21 #include "qstat.h"
22 #include "packet_manip.h"
23 
24 // Format:
25 // 1 - 8: Challenge Request / Response
26 char haze_challenge[] =
27 {
28 	'f', 'r', 'd', 'c', '_', '_', '_', '_'
29 };
30 
31 int process_haze_packet(struct qserver *server);
32 
33 // Player headers
34 #define PLAYER_NAME_HEADER	1
35 #define PLAYER_SCORE_HEADER	2
36 #define PLAYER_DEATHS_HEADER	3
37 #define PLAYER_PING_HEADER	4
38 #define PLAYER_KILLS_HEADER	5
39 #define PLAYER_TEAM_HEADER	6
40 #define PLAYER_OTHER_HEADER	7
41 
42 // Team headers
43 #define TEAM_NAME_HEADER	1
44 #define TEAM_OTHER_HEADER	2
45 
46 // Challenge response algorithum
47 // Before sending a qr2 query (type 0x00) the client must first send a
48 // challenge request (type 0x09).  The host will respond with the same
49 // packet type containing a string signed integer.
50 //
51 // Once the challenge is received the client should convert the string to a
52 // network byte order integer and embed it in the keys query.
53 //
54 // Example:
55 //
56 //  challenge request: [0xFE][0xFD][0x09][0x.. 4-byte-instance]
57 //	challenge response: [0x09][0x.. 4-byte-instance]["-1287574694"]
58 //	query: [0xFE][0xFD][0x00][0x.. 4-byte-instance][0xb3412b5a "-1287574694"]
59 //
60 
61 query_status_t
deal_with_haze_packet(struct qserver * server,char * rawpkt,int pktlen)62 deal_with_haze_packet(struct qserver *server, char *rawpkt, int pktlen)
63 {
64 	char *ptr = rawpkt;
65 	unsigned int pkt_id;
66 	unsigned short len;
67 	unsigned char pkt_max, pkt_index;
68 
69 	debug(2, "packet...");
70 
71 	if (pktlen < 8) {
72 		// invalid packet
73 		malformed_packet(server, "too short");
74 		return (PKT_ERROR);
75 	}
76 
77 	if (0 == strncmp(ptr, "frdcr", 5)) {
78 		// challenge response
79 		ptr += 8;
80 		server->challenge = 1;
81 
82 		// Correct the stats due to two phase protocol
83 		server->retry1++;
84 		server->n_packets--;
85 		if ((server->retry1 == n_retries) || server->flags & FLAG_BROADCAST) {
86 			//server->n_requests--;
87 		} else {
88 			server->n_retries--;
89 		}
90 		return (send_haze_request_packet(server));
91 	}
92 
93 	if (pktlen < 12) {
94 		// invalid packet
95 		malformed_packet(server, "too short");
96 		return (PKT_ERROR);
97 	}
98 
99 	server->n_servers++;
100 	if (server->server_name == NULL) {
101 		server->ping_total += time_delta(&packet_recv_time, &server->packet_time1);
102 	} else {
103 		gettimeofday(&server->packet_time1, NULL);
104 	}
105 
106 	// Query version ID
107 	ptr += 4;
108 
109 	// Could check the header here should
110 	// match the 4 byte id sent
111 	memcpy(&pkt_id, ptr, 4);
112 	ptr += 4;
113 
114 	// Max plackets
115 	pkt_max = ((unsigned char)*ptr);
116 	ptr++;
117 
118 	// Packet ID
119 	pkt_index = ((unsigned char)*ptr);
120 	ptr++;
121 
122 	// Query Length
123 	debug(1, "%04hx, %04hx", (unsigned short)ptr[0], (unsigned short)(ptr[1] << 8));
124 	// TODO: fix this crap
125 	memcpy(&len, ptr + 1, 1);
126 	ptr += 2;
127 
128 	debug(1, "pkt_index = %d, pkt_max = %d, len = %d", pkt_index, pkt_max, len);
129 	if (0 != pkt_max) {
130 		// not a single packet response or callback
131 		debug(2, "pkt_max %d", pkt_max);
132 
133 		if (0 == pkt_index) {
134 			// to prevent reprocessing when we get the call back
135 			// override the packet flag so it looks like a single
136 			// packet response
137 			rawpkt[8] = '\0';
138 		}
139 
140 		// add the packet recalcing maxes
141 		if (!add_packet(server, pkt_id, pkt_index, pkt_max, pktlen, rawpkt, 1)) {
142 			// fatal error e.g. out of memory
143 			return (MEM_ERROR);
144 		}
145 
146 		// combine_packets will call us recursively
147 		return (combine_packets(server));
148 	}
149 
150 	// if we get here we have what should be a full packet
151 	return (process_haze_packet(server));
152 }
153 
154 
155 query_status_t
deal_with_haze_status(struct qserver * server,char * rawpkt,int pktlen)156 deal_with_haze_status(struct qserver *server, char *rawpkt, int pktlen)
157 {
158 	char *pkt = rawpkt;
159 	int len;
160 
161 	debug(1, "status packet");
162 
163 	// Server name
164 	server->server_name = strdup(pkt);
165 	pkt += strlen(pkt) + 1;
166 
167 	// gametype
168 	add_rule(server, "gametype", pkt, NO_FLAGS);
169 	pkt += strlen(pkt) + 1;
170 
171 	// map
172 	len = strlen(pkt);
173 	// remove .res from map names
174 	if (0 == strncmp(pkt + len - 4, ".res", 4)) {
175 		*(pkt + len - 4) = '\0';
176 	}
177 	server->map_name = strdup(pkt);
178 	pkt += len + 1;
179 
180 	// num players
181 	server->num_players = atoi(pkt);
182 	pkt += strlen(pkt) + 1;
183 
184 	// max_players
185 	server->max_players = atoi(pkt);
186 	pkt += strlen(pkt) + 1;
187 
188 	// hostport
189 	change_server_port(server, atoi(pkt), 0);
190 	pkt += strlen(pkt) + 1;
191 
192 	return (DONE_FORCE);
193 }
194 
195 
196 int
process_haze_packet(struct qserver * server)197 process_haze_packet(struct qserver *server)
198 {
199 	unsigned char state = 0;
200 	unsigned char no_players = 0;
201 	unsigned char total_players = 0;
202 	unsigned char no_teams = 0;
203 	unsigned char total_teams = 0;
204 	int pkt_index = 0;
205 	SavedData *fragment;
206 
207 	debug(2, "processing packet...");
208 
209 	while (NULL != (fragment = get_packet_fragment(pkt_index++))) {
210 		int pktlen = fragment->datalen;
211 		char *ptr = fragment->data;
212 		char *end = ptr + pktlen;
213 		debug(2, "processing fragment[%d]...", fragment->pkt_index);
214 
215 		// check we have a full header
216 		if (pktlen < 12) {
217 			// invalid packet
218 			malformed_packet(server, "too short");
219 			return (PKT_ERROR);
220 		}
221 
222 		// skip over the header
223 		//server->protocol_version = atoi( val+1 );
224 		ptr += 12;
225 
226 		// 4 * null's signifies the end of a section
227 
228 		// Basic Info
229 		while (0 == state && ptr < end) {
230 			// name value pairs null seperated
231 			char *var, *val;
232 			int var_len, val_len;
233 
234 			if ((ptr + 4 <= end) && (0x00 == ptr[0]) && (0x00 == ptr[1]) && (0x00 == ptr[2]) && (0x00 == ptr[3])) {
235 				// end of rules
236 				state++;
237 				ptr += 4;
238 				break;
239 			}
240 
241 			var = ptr;
242 			var_len = strlen(var);
243 			ptr += var_len + 1;
244 
245 			if (ptr + 1 > end) {
246 				malformed_packet(server, "no basic value");
247 				return (PKT_ERROR);
248 			}
249 
250 			val = ptr;
251 			val_len = strlen(val);
252 			ptr += val_len + 1;
253 			debug(2, "var:%s (%d)=%s (%d)\n", var, var_len, val, val_len);
254 
255 			// Lets see what we've got
256 			if (0 == strcmp(var, "serverName")) {
257 				server->server_name = strdup(val);
258 			} else if (0 == strcmp(var, "map")) {
259 				// remove .res from map names
260 				if (0 == strncmp(val + val_len - 4, ".res", 4)) {
261 					*(val + val_len - 4) = '\0';
262 				}
263 				server->map_name = strdup(val);
264 			} else if (0 == strcmp(var, "maxPlayers")) {
265 				server->max_players = atoi(val);
266 			} else if (0 == strcmp(var, "currentPlayers")) {
267 				server->num_players = no_players = atoi(val);
268 			} else {
269 				add_rule(server, var, val, NO_FLAGS);
270 			}
271 		}
272 
273 		// rules
274 		while (1 == state && ptr < end) {
275 			// name value pairs null seperated
276 			char *var, *val;
277 			int var_len, val_len;
278 
279 			if ((ptr + 4 <= end) && (0x00 == ptr[0]) && (0x00 == ptr[1]) && (0x00 == ptr[2]) && (0x00 == ptr[3])) {
280 				// end of basic
281 				state++;
282 				ptr += 4;
283 				break;
284 			}
285 			var = ptr;
286 			var_len = strlen(var);
287 			ptr += var_len + 1;
288 
289 			if (ptr + 1 > end) {
290 				malformed_packet(server, "no basic value");
291 				return (PKT_ERROR);
292 			}
293 
294 			val = ptr;
295 			val_len = strlen(val);
296 			ptr += val_len + 1;
297 			debug(2, "var:%s (%d)=%s (%d)\n", var, var_len, val, val_len);
298 
299 			// add the rule
300 			add_rule(server, var, val, NO_FLAGS);
301 		}
302 
303 		// players
304 		while (2 == state && ptr < end) {
305 			// first we have the header
306 			char *header = ptr;
307 			int head_len = strlen(header);
308 			ptr += head_len + 1;
309 
310 			if ((ptr + 2 <= end) && (0x00 == ptr[0]) && (0x00 == ptr[1])) {
311 				// end of player headers
312 				state++;
313 				ptr += 2;
314 				break;
315 			}
316 
317 			if (0 == head_len) {
318 				// no more info
319 				debug(3, "All done");
320 				return (DONE_FORCE);
321 			}
322 
323 			debug(2, "player header '%s'", header);
324 
325 			if (ptr > end) {
326 				malformed_packet(server, "no details for header '%s'", header);
327 				return (PKT_ERROR);
328 			}
329 		}
330 
331 		while (3 == state && ptr < end) {
332 			char *header = ptr;
333 			int head_len = strlen(header);
334 			int header_type;
335 			// the next byte is the starting number
336 			total_players = *ptr++;
337 
338 			if ((0 == strcmp(header, "player_")) || (0 == strcmp(header, "name_"))) {
339 				header_type = PLAYER_NAME_HEADER;
340 			} else if (0 == strcmp(header, "score_")) {
341 				header_type = PLAYER_SCORE_HEADER;
342 			} else if (0 == strcmp(header, "deaths_")) {
343 				header_type = PLAYER_DEATHS_HEADER;
344 			} else if (0 == strcmp(header, "ping_")) {
345 				header_type = PLAYER_PING_HEADER;
346 			} else if (0 == strcmp(header, "kills_")) {
347 				header_type = PLAYER_KILLS_HEADER;
348 			} else if (0 == strcmp(header, "team_")) {
349 				header_type = PLAYER_TEAM_HEADER;
350 			} else {
351 				header_type = PLAYER_OTHER_HEADER;
352 			}
353 
354 			while (ptr < end) {
355 				// now each player details
356 				// add the player
357 				struct player *player;
358 				char *val;
359 				int val_len;
360 
361 				// check for end of this headers player info
362 				if (0x00 == *ptr) {
363 					debug(3, "end of '%s' detail", header);
364 					ptr++;
365 					// Note: can't check ( total_players != no_players ) here as we may have more packets
366 					if ((ptr < end) && (0x00 == *ptr)) {
367 						debug(3, "end of players");
368 						// end of all player headers / detail
369 						state = 2;
370 						ptr++;
371 					}
372 					break;
373 				}
374 
375 				player = get_player_by_number(server, total_players);
376 				if (NULL == player) {
377 					player = add_player(server, total_players);
378 				}
379 
380 				if (ptr >= end) {
381 					malformed_packet(server, "short player detail");
382 					return (PKT_ERROR);
383 				}
384 				val = ptr;
385 				val_len = strlen(val);
386 				ptr += val_len + 1;
387 
388 				debug(2, "Player[%d][%s]=%s\n", total_players, header, val);
389 
390 				// lets see what we got
391 				switch (header_type) {
392 				case PLAYER_NAME_HEADER:
393 					player->name = strdup(val);
394 					break;
395 
396 				case PLAYER_SCORE_HEADER:
397 					player->score = atoi(val);
398 					break;
399 
400 				case PLAYER_DEATHS_HEADER:
401 					player->deaths = atoi(val);
402 					break;
403 
404 				case PLAYER_PING_HEADER:
405 					player->ping = atoi(val);
406 					break;
407 
408 				case PLAYER_KILLS_HEADER:
409 					player->frags = atoi(val);
410 					break;
411 
412 				case PLAYER_TEAM_HEADER:
413 					player->team = atoi(val);
414 					break;
415 
416 				case PLAYER_OTHER_HEADER:
417 				default:
418 					if ('_' == header[head_len - 1]) {
419 						header[head_len - 1] = '\0';
420 						player_add_info(player, header, val, NO_FLAGS);
421 						header[head_len - 1] = '_';
422 					} else {
423 						player_add_info(player, header, val, NO_FLAGS);
424 					}
425 					break;
426 				}
427 
428 				total_players++;
429 
430 				if (total_players > no_players) {
431 					malformed_packet(server, "to many players %d > %d", total_players, no_players);
432 					return (PKT_ERROR);
433 				}
434 			}
435 		}
436 
437 		if (3 == state) {
438 			no_teams = (unsigned char)*ptr;
439 			ptr++;
440 
441 			debug(2, "No teams:%d\n", no_teams);
442 			state = 3;
443 		}
444 
445 		while (4 == state && ptr < end) {
446 			// first we have the header
447 			char *header = ptr;
448 			int head_len = strlen(header);
449 			int header_type;
450 			ptr += head_len + 1;
451 
452 			if (0 == head_len) {
453 				// no more info
454 				debug(3, "All done");
455 				return (DONE_FORCE);
456 			}
457 
458 			debug(2, "team header '%s'", header);
459 			if (0 == strcmp(header, "team_t")) {
460 				header_type = TEAM_NAME_HEADER;
461 			} else {
462 				header_type = TEAM_OTHER_HEADER;
463 			}
464 
465 			// the next byte is the starting number
466 			total_teams = *ptr++;
467 
468 			while (ptr < end) {
469 				// now each teams details
470 				char *val;
471 				int val_len;
472 				char rule[512];
473 
474 				if (ptr >= end) {
475 					malformed_packet(server, "short team detail");
476 					return (PKT_ERROR);
477 				}
478 				val = ptr;
479 				val_len = strlen(val);
480 				ptr += val_len + 1;
481 
482 				debug(2, "Team[%d][%s]=%s\n", total_teams, header, val);
483 
484 				// lets see what we got
485 				switch (header_type) {
486 				case TEAM_NAME_HEADER:
487 					// BF being stupid again teams 1 based instead of 0
488 					players_set_teamname(server, total_teams + 1, val);
489 				// N.B. yes no break
490 
491 				case TEAM_OTHER_HEADER:
492 				default:
493 					// add as a server rule
494 					sprintf(rule, "%s%d", header, total_teams);
495 					add_rule(server, rule, val, NO_FLAGS);
496 					break;
497 				}
498 
499 				total_teams++;
500 				if (0x00 == *ptr) {
501 					// end of this headers teams
502 					ptr++;
503 					break;
504 				}
505 			}
506 		}
507 	}
508 
509 	return (DONE_FORCE);
510 }
511 
512 
513 query_status_t
send_haze_request_packet(struct qserver * server)514 send_haze_request_packet(struct qserver *server)
515 {
516 	char *packet;
517 	char query_buf[128];
518 	size_t len;
519 	unsigned char required = HAZE_BASIC_INFO;
520 
521 	if (get_server_rules) {
522 		required |= HAZE_GAME_RULES;
523 		server->flags |= TF_PLAYER_QUERY;
524 	}
525 
526 	if (get_player_info) {
527 		required |= HAZE_PLAYER_INFO;
528 		required |= HAZE_TEAM_INFO;
529 		server->flags |= TF_RULES_QUERY;
530 	}
531 
532 	server->flags |= TF_STATUS_QUERY;
533 
534 	if (server->challenge) {
535 		// we've recieved a challenge response, send the query + challenge id
536 		len = sprintf(
537 			query_buf,
538 			"frdquery%c%c%c%c%c",
539 			(unsigned char)(server->challenge >> 24),
540 			(unsigned char)(server->challenge >> 16),
541 			(unsigned char)(server->challenge >> 8),
542 			(unsigned char)(server->challenge >> 0),
543 			required
544 			);
545 		packet = query_buf;
546 	} else {
547 		// Either basic v3 protocol or challenge request
548 		packet = haze_challenge;
549 		len = sizeof(haze_challenge);
550 	}
551 
552 	return (send_packet(server, packet, len));
553 }
554