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