1 /*
2  * qstat
3  * by Steve Jankowski
4  *
5  * New Half-Life2 query protocol
6  * Copyright 2005 Ludwig Nussel
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 <sys/socket.h>
15  #include <arpa/inet.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 #define A2S_GETCHALLENGE		"\xFF\xFF\xFF\xFF\x57"
25 #define A2S_CHALLENGERESPONSE		0x41
26 #define A2S_INFO			"\xFF\xFF\xFF\xFF\x54Source Engine Query"
27 #define A2S_INFORESPONSE_HL1		0x6D
28 #define A2S_INFORESPONSE_HL2		0x49
29 #define A2S_PLAYER			"\xFF\xFF\xFF\xFF\x55"
30 #define A2S_PLAYERRESPONSE		0x44
31 #define A2S_PLAYER_INVALID_CHALLENGE	"\xFF\xFF\xFF\xFF\x55\xFF\xFF\xFF\xFF"
32 #define A2S_RULES			"\xFF\xFF\xFF\xFF\x56"
33 #define A2S_RULESRESPONSE		0x45
34 
35 struct a2s_status {
36 	unsigned sent_challenge : 1;
37 	unsigned have_challenge : 1;
38 	unsigned sent_info : 1;
39 	unsigned have_info : 1;
40 	unsigned sent_player : 1;
41 	unsigned have_player : 1;
42 	unsigned sent_rules : 1;
43 	unsigned have_rules : 1;
44 	unsigned challenge;
45 	unsigned char type;
46 };
47 
48 query_status_t
send_a2s_request_packet(struct qserver * server)49 send_a2s_request_packet(struct qserver *server)
50 {
51 	struct a2s_status *status = (struct a2s_status *)server->master_query_tag;
52 
53 	debug(3, "sending info query");
54 	if (qserver_send_initial(server, A2S_INFO, sizeof(A2S_INFO)) == SOCKET_ERROR) {
55 		return (DONE_FORCE);
56 	}
57 
58 	status->sent_info = 1;
59 	status->type = 0;
60 
61 	if (get_server_rules || get_player_info) {
62 		server->next_rule = ""; // trigger calling send_a2s_rule_request_packet
63 	}
64 	return (INPROGRESS);
65 }
66 
67 
68 query_status_t
send_a2s_rule_request_packet(struct qserver * server)69 send_a2s_rule_request_packet(struct qserver *server)
70 {
71 	struct a2s_status *status = (struct a2s_status *)server->master_query_tag;
72 	debug(3, "rule request");
73 
74 	if (!get_server_rules && !get_player_info && status->have_info) {
75 		debug(3, "force done");
76 		return (DONE_FORCE);
77 	}
78 
79 	if (server->retry1 < 0) {
80 		debug(3, "too may retries");
81 		return (DONE_FORCE);
82 	}
83 
84 	while (1) {
85 		if (!status->have_challenge) {
86 			debug(3, "sending challenge");
87 
88 			// Challenge Request was broken so instead we use a player request with an invalid
89 			// challenge of -1 (0xFFFFFFFF) which prompts the server to send a valid challenge
90 			// This was fixed as of the update 2009-08-26 but then subsequently broken again.
91 			//if (SOCKET_ERROR == qserver_send_initial(server, A2S_GETCHALLENGE, sizeof(A2S_GETCHALLENGE)-1)) {
92 			char buf[sizeof(A2S_PLAYER) - 1 + 4] = A2S_PLAYER;
93 			// We use a challenge of -1 to ensure compatibility with 3rd party implementations
94 			// as that's whats documented: https://developer.valvesoftware.com/wiki/Server_queries#Request_Format_5
95 			status->challenge = -1;
96 			memcpy(buf + sizeof(A2S_PLAYER) - 1, &status->challenge, 4);
97 			if (qserver_send_initial(server, buf, sizeof(buf)) == SOCKET_ERROR) {
98 				return (SOCKET_ERROR);
99 			}
100 			status->sent_challenge = 1;
101 			break;
102 		} else if (status->sent_info && !status->have_info) {
103 			// Need to resend info due to enhanced DDoS protection
104 			// See: https://steamcommunity.com/discussions/forum/14/2974028351344359625/
105 			char buf[sizeof(A2S_INFO) + 4] = A2S_INFO;
106 			memcpy(buf + sizeof(A2S_INFO), &status->challenge, 4);
107 			debug(3, "sending info query with challenge");
108 			if (qserver_send_initial(server, buf, sizeof(buf)) == SOCKET_ERROR) {
109 				return (SOCKET_ERROR);
110 			}
111 			break;
112 		} else if (get_server_rules && !status->have_rules) {
113 			char buf[sizeof(A2S_RULES) - 1 + 4] = A2S_RULES;
114 			memcpy(buf + sizeof(A2S_RULES) - 1, &status->challenge, 4);
115 			debug(3, "sending rule query");
116 			if (qserver_send_initial(server, buf, sizeof(buf)) == SOCKET_ERROR) {
117 				return (SOCKET_ERROR);
118 			}
119 			status->sent_rules = 1;
120 			break;
121 		} else if (get_player_info && !status->have_player) {
122 			char buf[sizeof(A2S_PLAYER) - 1 + 4] = A2S_PLAYER;
123 			memcpy(buf + sizeof(A2S_PLAYER) - 1, &status->challenge, 4);
124 			debug(3, "sending player query");
125 			if (qserver_send_initial(server, buf, sizeof(buf)) == SOCKET_ERROR) {
126 				return (SOCKET_ERROR);
127 			}
128 			status->sent_player = 1;
129 			break;
130 		} else {
131 			debug(3, "timeout");
132 			// we are probably called due to timeout, restart.
133 			status->have_challenge = 0;
134 			status->have_rules = 0;
135 		}
136 	}
137 
138 	return (INPROGRESS);
139 }
140 
141 
142 query_status_t
deal_with_a2s_packet(struct qserver * server,char * rawpkt,int pktlen)143 deal_with_a2s_packet(struct qserver *server, char *rawpkt, int pktlen)
144 {
145 	struct a2s_status *status = (struct a2s_status *)server->master_query_tag;
146 	char *pkt = rawpkt;
147 	char buf[16];
148 	char *str;
149 	unsigned cnt;
150 
151 	if (server->server_name == NULL) {
152 		server->ping_total += time_delta(&packet_recv_time, &server->packet_time1);
153 		server->n_requests++;
154 	}
155 
156 	if (pktlen < 5) {
157 		goto out_too_short;
158 	}
159 
160 	if (0 == memcmp(pkt, "\xFE\xFF\xFF\xFF", 4)) {
161 		// fragmented packet
162 		unsigned char pkt_index, pkt_max;
163 		unsigned int pkt_id = 1;
164 		SavedData *sdata;
165 
166 		if (pktlen < 9) {
167 			goto out_too_short;
168 		}
169 
170 		pkt += 4;
171 
172 		// format:
173 		// int sequenceNumber
174 		// byte packetId
175 		// packetId format:
176 		// bits 0 - 3 = packets position in the sequence ( 0 .. N - 1 )
177 		// bits 4 - 7 = total number of packets
178 
179 		// sequenceId
180 		memcpy(&pkt_id, pkt, 4);
181 		debug(3, "sequenceId: %d", pkt_id);
182 		pkt += 4;
183 
184 		// packetId
185 		if ((1 == status->type) || (200 > server->protocol_version)) {
186 			// HL1 format
187 			// The lower four bits represent the number of packets (2 to 15) and
188 			// the upper four bits represent the current packet starting with 0
189 			pkt_max = ((unsigned char)*pkt) & 15;
190 			pkt_index = ((unsigned char)*pkt) >> 4;
191 			debug(3, "packetid[1]: 0x%hhx => idx: %hhu, max: %hhu", *pkt, pkt_index, pkt_max);
192 			pkt++;
193 			pktlen -= 9;
194 		} else if (2 == status->type) {
195 			// HL2 format
196 			// The next two bytes are:
197 			// 1. the max packets sent ( byte )
198 			// 2. the index of this packet starting from 0 ( byte )
199 			// 3. Size of the split ( short )
200 			if (pktlen < 10) {
201 				goto out_too_short;
202 			}
203 			pkt_max = ((unsigned char)*pkt);
204 			pkt_index = ((unsigned char)*(pkt + 1));
205 			debug(3, "packetid[2]: 0x%hhx => idx: %hhu, max: %hhu", *pkt, pkt_index, pkt_max);
206 			pkt += 4;
207 			pktlen -= 12;
208 		} else {
209 			malformed_packet(server, "Unable to determine packet format");
210 			return (PKT_ERROR);
211 		}
212 
213 		// pkt_max is the total number of packets expected
214 		// pkt_index is a bit mask of the packets received.
215 
216 		if (server->saved_data.data == NULL) {
217 			sdata = &server->saved_data;
218 		} else {
219 			sdata = (SavedData *)calloc(1, sizeof(SavedData));
220 			sdata->next = server->saved_data.next;
221 			server->saved_data.next = sdata;
222 		}
223 
224 		sdata->pkt_index = pkt_index;
225 		sdata->pkt_max = pkt_max;
226 		sdata->pkt_id = pkt_id;
227 		sdata->datalen = pktlen;
228 		sdata->data = (char *)malloc(sdata->datalen);
229 		if (NULL == sdata->data) {
230 			malformed_packet(server, "Out of memory");
231 			return (MEM_ERROR);
232 		}
233 		memcpy(sdata->data, pkt, sdata->datalen);
234 
235 		// combine_packets will call us recursively
236 		return (combine_packets(server));
237 	} else if (0 != memcmp(pkt, "\xFF\xFF\xFF\xFF", 4)) {
238 		malformed_packet(server, "invalid packet header");
239 		return (PKT_ERROR);
240 	}
241 
242 	pkt += 4;
243 	pktlen -= 4;
244 
245 	pktlen -= 1;
246 	debug(2, "A2S type = %x", *pkt);
247 	switch (*pkt++) {
248 	case A2S_CHALLENGERESPONSE:
249 		if (pktlen < 4) {
250 			goto out_too_short;
251 		}
252 		memcpy(&status->challenge, pkt, 4);
253 		// do not count challenge as retry
254 		if (!status->have_challenge && (server->retry1 != n_retries)) {
255 			++server->retry1;
256 			if (server->n_retries) {
257 				--server->n_retries;
258 			}
259 		}
260 		status->have_challenge = 1;
261 		debug(3, "challenge %x", status->challenge);
262 		return (send_a2s_rule_request_packet(server));
263 
264 	case A2S_INFORESPONSE_HL1:
265 		if (pktlen < 28) {
266 			goto out_too_short;
267 		}
268 		status->type = 1;
269 
270 		// ip:port
271 		str = memchr(pkt, '\0', pktlen);
272 		if (!str) {
273 			goto out_too_short;
274 		}
275 		//server->server_name = strdup(pkt);
276 		pktlen -= str - pkt + 1;
277 		pkt += str - pkt + 1;
278 
279 		// server name
280 		str = memchr(pkt, '\0', pktlen);
281 		if (!str) {
282 			goto out_too_short;
283 		}
284 		server->server_name = strdup(pkt);
285 		pktlen -= str - pkt + 1;
286 		pkt += str - pkt + 1;
287 
288 		// map
289 		str = memchr(pkt, '\0', pktlen);
290 		if (!str) {
291 			goto out_too_short;
292 		}
293 		server->map_name = strdup(pkt);
294 		pktlen -= str - pkt + 1;
295 		pkt += str - pkt + 1;
296 
297 		// mod dir
298 		str = memchr(pkt, '\0', pktlen);
299 		if (!str) {
300 			goto out_too_short;
301 		}
302 		server->game = strdup(pkt);
303 		add_rule(server, "gamedir", pkt, 0);
304 		pktlen -= str - pkt + 1;
305 		pkt += str - pkt + 1;
306 
307 		// mod description
308 		str = memchr(pkt, '\0', pktlen);
309 		if (!str) {
310 			goto out_too_short;
311 		}
312 		add_rule(server, "gamename", pkt, 0);
313 		pktlen -= str - pkt + 1;
314 		pkt += str - pkt + 1;
315 
316 		if (pktlen < 7) {
317 			goto out_too_short;
318 		}
319 
320 		// num players
321 		server->num_players = (unsigned char)pkt[0];
322 
323 		// max players
324 		server->max_players = (unsigned char)pkt[1];
325 
326 		// version
327 		sprintf(buf, "%hhu", pkt[2]);
328 		add_rule(server, "version", buf, 0);
329 
330 		// dedicated
331 		add_rule(server, "dedicated", pkt[3] == 'd' ? "1" : "0", 0);
332 
333 		// os
334 		switch (pkt[4]) {
335 		case 'l':
336 			add_rule(server, "sv_os", "linux", 0);
337 			break;
338 
339 		case 'w':
340 			add_rule(server, "sv_os", "windows", 0);
341 			break;
342 
343 		default:
344 			buf[0] = pkt[4];
345 			buf[1] = '\0';
346 			add_rule(server, "sv_os", buf, 0);
347 		}
348 
349 		// password
350 		add_rule(server, "password", pkt[5] ? "1" : "0", 0);
351 
352 		pkt += 6;
353 		pktlen -= 6;
354 
355 		// mod info
356 		if (pkt[0]) {
357 			pkt++;
358 			pktlen--;
359 
360 			// mod URL
361 			str = memchr(pkt, '\0', pktlen);
362 			if (!str) {
363 				goto out_too_short;
364 			}
365 			add_rule(server, "mod_url", strdup(pkt), 0);
366 			pktlen -= str - pkt + 1;
367 			pkt += str - pkt + 1;
368 
369 			// mod DL
370 			str = memchr(pkt, '\0', pktlen);
371 			if (!str) {
372 				goto out_too_short;
373 			}
374 			add_rule(server, "mod_dl", strdup(pkt), 0);
375 			pktlen -= str - pkt + 1;
376 			pkt += str - pkt + 1;
377 
378 			// mod Empty
379 			str = memchr(pkt, '\0', pktlen);
380 			pktlen -= str - pkt + 1;
381 			pkt += str - pkt + 1;
382 
383 			if (pktlen < 10) {
384 				goto out_too_short;
385 			}
386 
387 			// mod version
388 			sprintf(buf, "%u", swap_long_from_little(pkt));
389 			add_rule(server, "mod_ver", buf, 0);
390 			pkt += 4;
391 			pktlen -= 4;
392 
393 			// mod size
394 			sprintf(buf, "%u", swap_long_from_little(pkt));
395 			add_rule(server, "mod_size", buf, 0);
396 			pkt += 4;
397 			pktlen -= 4;
398 
399 			// svonly
400 			add_rule(server, "mod_svonly", (*pkt) ? "1" : "0", 0);
401 			pkt++;
402 			pktlen--;
403 
404 			// cldll
405 			add_rule(server, "mod_cldll", (*pkt) ? "1" : "0", 0);
406 			pkt++;
407 			pktlen--;
408 		}
409 
410 		if (pktlen < 2) {
411 			goto out_too_short;
412 		}
413 
414 		// Secure
415 		add_rule(server, "secure", *pkt ? "1" : "0", 0);
416 		pkt++;
417 		pktlen--;
418 
419 		// Bots
420 		sprintf(buf, "%hhu", *pkt);
421 		add_rule(server, "bots", buf, 0);
422 		pkt++;
423 		pktlen--;
424 
425 		status->have_info = 1;
426 
427 		server->retry1 = n_retries;
428 
429 		server->next_player_info = server->num_players;
430 
431 		break;
432 
433 	case A2S_INFORESPONSE_HL2:
434 		if (pktlen < 1) {
435 			goto out_too_short;
436 		}
437 		status->type = 2;
438 		snprintf(buf, sizeof(buf), "%hhX", *pkt);
439 		add_rule(server, "protocol", buf, 0);
440 		++pkt;
441 		--pktlen;
442 
443 		// server name
444 		str = memchr(pkt, '\0', pktlen);
445 		if (!str) {
446 			goto out_too_short;
447 		}
448 		server->server_name = strdup(pkt);
449 		pktlen -= str - pkt + 1;
450 		pkt += str - pkt + 1;
451 
452 		// map
453 		str = memchr(pkt, '\0', pktlen);
454 		if (!str) {
455 			goto out_too_short;
456 		}
457 		server->map_name = strdup(pkt);
458 		pktlen -= str - pkt + 1;
459 		pkt += str - pkt + 1;
460 
461 		// mod
462 		str = memchr(pkt, '\0', pktlen);
463 		if (!str) {
464 			goto out_too_short;
465 		}
466 		server->game = strdup(pkt);
467 		add_rule(server, "gamedir", pkt, 0);
468 		pktlen -= str - pkt + 1;
469 		pkt += str - pkt + 1;
470 
471 		// description
472 		str = memchr(pkt, '\0', pktlen);
473 		if (!str) {
474 			goto out_too_short;
475 		}
476 		add_rule(server, "gamename", pkt, 0);
477 		pktlen -= str - pkt + 1;
478 		pkt += str - pkt + 1;
479 
480 		if (pktlen < 9) {
481 			goto out_too_short;
482 		}
483 
484 		// pkt[0], pkt[1] steam appid
485 		server->protocol_version = (unsigned short)*pkt;
486 		server->num_players = (unsigned char)pkt[2];
487 		server->max_players = (unsigned char)pkt[3];
488 		// pkt[4] number of bots
489 		sprintf(buf, "%hhu", pkt[4]);
490 		add_rule(server, "bots", buf, 0);
491 
492 		add_rule(server, "dedicated", pkt[5] ? "1" : "0", 0);
493 		if (pkt[6] == 'l') {
494 			add_rule(server, "sv_os", "linux", 0);
495 		} else if (pkt[6] == 'w') {
496 			add_rule(server, "sv_os", "windows", 0);
497 		} else {
498 			buf[0] = pkt[6];
499 			buf[1] = '\0';
500 			add_rule(server, "sv_os", buf, 0);
501 		}
502 
503 		if (pkt[7]) {
504 			snprintf(buf, sizeof(buf), "%hhu", (unsigned char)pkt[7]);
505 			add_rule(server, "password", buf, 0);
506 		}
507 
508 		if (pkt[8]) {
509 			snprintf(buf, sizeof(buf), "%hhu", (unsigned char)pkt[8]);
510 			add_rule(server, "secure", buf, 0);
511 		}
512 
513 		pkt += 9;
514 		pktlen -= 9;
515 
516 		// version
517 		str = memchr(pkt, '\0', pktlen);
518 		if (!str) {
519 			goto out_too_short;
520 		}
521 		add_rule(server, "version", pkt, 0);
522 		pktlen -= str - pkt + 1;
523 		pkt += str - pkt + 1;
524 
525 		// EDF
526 		if (1 <= pktlen) {
527 			unsigned char edf = *pkt;
528 			debug(1, "EDF: 0x%02hhx", edf);
529 			pkt++;
530 			pktlen--;
531 			if (edf & 0x80) {
532 				// game port
533 				unsigned short gameport;
534 
535 				if (pktlen < 2) {
536 					goto out_too_short;
537 				}
538 				gameport = swap_short_from_little(pkt);
539 				sprintf(buf, "%hu", gameport);
540 				add_rule(server, "game_port", buf, 0);
541 				change_server_port(server, gameport, 0);
542 				pkt += 2;
543 				pktlen -= 2;
544 			}
545 
546 			if (edf & 0x10) {
547 				// SteamId (long long)
548 				if (pktlen < 8) {
549 					goto out_too_short;
550 				}
551 				pkt += 8;
552 				pktlen -= 8;
553 			}
554 
555 			if (edf & 0x40) {
556 				// spectator port
557 				unsigned short spectator_port;
558 				if (pktlen < 3) {
559 					goto out_too_short;
560 				}
561 				spectator_port = swap_short_from_little(pkt);
562 				sprintf(buf, "%hu", spectator_port);
563 				add_rule(server, "spectator_port", buf, 0);
564 				pkt += 2;
565 				pktlen -= 2;
566 
567 				// spectator server name
568 				str = memchr(pkt, '\0', pktlen);
569 				if (!str) {
570 					goto out_too_short;
571 				}
572 				add_rule(server, "spectator_server_name", pkt, 0);
573 				pktlen -= str - pkt + 1;
574 				pkt += str - pkt + 1;
575 			}
576 
577 			if (edf & 0x20) {
578 				// Keywords
579 				str = memchr(pkt, '\0', pktlen);
580 				if (!str) {
581 					goto out_too_short;
582 				}
583 				add_rule(server, "game_tags", pkt, 0);
584 				if (strncmp(pkt, "rust", 4) == 0) {
585 					// Rust is comma seperated tags
586 					char *keyword = strtok(pkt, ",");
587 					while (keyword != NULL) {
588 						if (strncmp(keyword, "cp", 2) == 0) {
589 							// current players override
590 							server->num_players = atoi(keyword + 2);
591 						} else if (strncmp(keyword, "mp", 2) == 0) {
592 							// max players override
593 							server->max_players = atoi(keyword + 2);
594 						}
595 						keyword = strtok(NULL, ",");
596 					}
597 				}
598 				pktlen -= str - pkt + 1;
599 				pkt += str - pkt + 1;
600 			}
601 
602 			if (edf & 0x01) {
603 				// GameId (long long)
604 				if (pktlen < 8) {
605 					goto out_too_short;
606 				}
607 				pkt += 8;
608 				pktlen -= 8;
609 			}
610 		}
611 
612 		status->have_info = 1;
613 
614 		server->retry1 = n_retries;
615 
616 		server->next_player_info = server->num_players;
617 
618 		break;
619 
620 	case A2S_RULESRESPONSE:
621 
622 		if (pktlen < 2) {
623 			goto out_too_short;
624 		}
625 
626 		cnt = (unsigned char)pkt[0] + ((unsigned char)pkt[1] << 8);
627 		pktlen -= 2;
628 		pkt += 2;
629 
630 		debug(3, "num_rules: %d", cnt);
631 
632 		for ( ; cnt && pktlen > 0; --cnt) {
633 			char *key, *value;
634 			str = memchr(pkt, '\0', pktlen);
635 			if (!str) {
636 				break;
637 			}
638 			key = pkt;
639 			pktlen -= str - pkt + 1;
640 			pkt += str - pkt + 1;
641 
642 			str = memchr(pkt, '\0', pktlen);
643 			if (!str) {
644 				break;
645 			}
646 			value = pkt;
647 			pktlen -= str - pkt + 1;
648 			pkt += str - pkt + 1;
649 
650 			add_rule(server, key, value, NO_FLAGS);
651 		}
652 
653 		if (cnt) {
654 			malformed_packet(server, "packet contains too few rules, missing %d", cnt);
655 			server->missing_rules = 1;
656 		}
657 		if (pktlen) {
658 			malformed_packet(server, "garbage at end of rules, %d bytes left", pktlen);
659 		}
660 
661 		status->have_rules = 1;
662 
663 		server->retry1 = n_retries;
664 
665 		break;
666 
667 	case A2S_PLAYERRESPONSE:
668 
669 		if (pktlen < 1) {
670 			goto out_too_short;
671 		}
672 
673 		cnt = (unsigned char)pkt[0];
674 		pktlen -= 1;
675 		pkt += 1;
676 
677 		debug(3, "num_players: %d", cnt);
678 
679 		for ( ; cnt && pktlen > 0; --cnt) {
680 			unsigned idx;
681 			const char *name;
682 			struct player *p;
683 
684 			idx = *pkt++;
685 			--pktlen;
686 
687 			str = memchr(pkt, '\0', pktlen);
688 			if (!str) {
689 				break;
690 			}
691 			name = pkt;
692 			pktlen -= str - pkt + 1;
693 			pkt += str - pkt + 1;
694 
695 			if (pktlen < 8) {
696 				goto out_too_short;
697 			}
698 
699 			debug(3, "player index %d = %s", idx, name);
700 			p = add_player(server, server->n_player_info);
701 			if (p) {
702 				p->name = strdup(name);
703 				p->frags = swap_long_from_little(pkt);
704 				p->connect_time = swap_float_from_little(pkt + 4);
705 			}
706 			pktlen -= 8;
707 			pkt += 8;
708 		}
709 
710 		if (pktlen) {
711 			malformed_packet(server, "garbage at end of player info, %d bytes left", pktlen);
712 		}
713 
714 		status->have_player = 1;
715 		// Workaround broken implementations which don't send a challenge to a player request
716 		// that hasn't seen a challenge yet. Without this we would end up in an infinite loop.
717 		if (status->have_challenge == 0) {
718 			if (show_errors) {
719 				fprintf(stderr, "server has broken challenge so is a DDoS source!\n");
720 			}
721 			status->have_challenge = 1;
722 		}
723 
724 		server->retry1 = n_retries;
725 
726 		break;
727 
728 	default:
729 		malformed_packet(server, "invalid packet id %hhx", *--pkt);
730 		return (PKT_ERROR);
731 	}
732 
733 	if (
734 		(!get_player_info || (get_player_info && status->have_player)) &&
735 		(!get_server_rules || (get_server_rules && status->have_rules))
736 		) {
737 		server->next_rule = NULL;
738 	}
739 
740 	return (DONE_AUTO);
741 
742 out_too_short:
743 	malformed_packet(server, "packet too short");
744 	return (PKT_ERROR);
745 }
746 
747 
748 // vim: sw=4 ts=4 noet
749