1 /*
2  * qstat
3  * by Steve Jankowski
4  *
5  * Terraria / TShock query protocol
6  * Copyright 2012 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 <sys/socket.h>
15  #include <netinet/in.h>
16  #include <arpa/inet.h>
17 	#define strtok_ret	strtok_r
18 #else
19  #include <winsock.h>
20 	#define strtok_ret	strtok_s
21 #endif
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <ctype.h>
25 
26 #include "debug.h"
27 #include "qstat.h"
28 #include "packet_manip.h"
29 
30 query_status_t
send_terraria_request_packet(struct qserver * server)31 send_terraria_request_packet(struct qserver *server)
32 {
33 	return (send_packet(server, server->type->status_packet, server->type->status_len));
34 }
35 
36 
37 query_status_t
deal_with_terraria_packet(struct qserver * server,char * rawpkt,int pktlen)38 deal_with_terraria_packet(struct qserver *server, char *rawpkt, int pktlen)
39 {
40 	char *s, *key, *val, *sep, *linep, *varp;
41 	int complete_response = 0;
42 	char last_char;
43 	unsigned short port = 0;
44 
45 	debug(2, "processing...");
46 
47 	if (0 == pktlen) {
48 		// Disconnect?
49 		return (REQ_ERROR);
50 	}
51 
52 	complete_response = (0 == strncmp("HTTP/1.1 200", rawpkt, 12) && '}' == rawpkt[pktlen - 1]) ? 1 : 0;
53 	last_char = rawpkt[pktlen - 1];
54 	rawpkt[pktlen - 1] = '\0';
55 
56 	debug(3, "packet: combined = %d, complete = %d", server->combined, complete_response);
57 	if (!complete_response) {
58 		if (!server->combined) {
59 			// response fragment recieved
60 			int pkt_id;
61 			int pkt_max;
62 			server->retry1 = n_retries;
63 			if (0 == server->n_requests) {
64 				server->ping_total = time_delta(&packet_recv_time, &server->packet_time1);
65 				server->n_requests++;
66 			}
67 
68 			// We're expecting more to come
69 			debug(5, "fragment recieved...");
70 			pkt_id = packet_count(server);
71 			pkt_max = pkt_id + 1;
72 			rawpkt[pktlen - 1] = last_char; // restore the last character
73 			if (!add_packet(server, 0, pkt_id, pkt_max, pktlen, rawpkt, 1)) {
74 				// fatal error e.g. out of memory
75 				return (MEM_ERROR);
76 			}
77 
78 			if (0 == pkt_id) {
79 				return (INPROGRESS);
80 			}
81 
82 			// combine_packets will call us recursively
83 			return (combine_packets(server));
84 		}
85 
86 		// recursive call which is still incomplete
87 		return (INPROGRESS);
88 	}
89 
90 	// find the end of the headers
91 	s = strstr(rawpkt, "\x0d\x0a\x0d\x0a");
92 
93 	if (NULL == s) {
94 		debug(1, "Error: missing end of headers");
95 		return (REQ_ERROR);
96 	}
97 
98 	s += 4;
99 
100 	// Correct ping
101 	// Not quite right but gives a good estimate
102 	server->ping_total = (server->ping_total * server->n_requests) / 2;
103 
104 	debug(3, "processing response...");
105 
106 	s = strtok_ret(s, "\x0d\x0a", &linep);
107 
108 	// NOTE: id=XXX and msg=XXX will be processed by the mod following the one they where the response of
109 	while (NULL != s) {
110 		debug(2, "LINE: %s\n", s);
111 		if (0 == strcmp(s, "{")) {
112 			s = strtok_ret(NULL, "\x0d\x0a", &linep);
113 			continue;
114 		}
115 
116 		s = strtok_ret(s, "\"", &varp);
117 		key = strtok_ret(NULL, "\"", &varp);
118 		sep = strtok_ret(NULL, " ", &varp);
119 		val = strtok_ret(NULL, "\"", &varp);
120 		if (NULL == val) {
121 			// world etc may be empty which results in NULL val
122 			s = strtok_ret(NULL, "\x0d\x0a", &linep);
123 			continue;
124 		}
125 		//if ( NULL == val && sep
126 		debug(2, "var: '%s' = '%s', sep: '%s'\n", key, val, sep);
127 		if (0 == strcmp(key, "name")) {
128 			server->server_name = strdup(val);
129 		} else if (0 == strcmp(key, "port")) {
130 			port = atoi(val);
131 			change_server_port(server, port, 0);
132 			add_rule(server, key, val, NO_FLAGS);
133 		} else if (0 == strcmp(key, "playercount")) {
134 			server->num_players = atoi(val);
135 		} else if (0 == strcmp(key, "maxplayers")) {
136 			server->max_players = atoi(val);
137 		} else if (0 == strcmp(key, "world")) {
138 			server->map_name = strdup(val);
139 		} else if (0 == strcmp(key, "players")) {
140 			// Players are ", " seperated but potentially player names can have spaces
141 			// so we manually check for the leading space and fix if found
142 			char *playersp;
143 			char *player_name = strtok_ret(val, ",", &playersp);
144 			while (NULL != player_name) {
145 				struct player *player = add_player(server, server->n_player_info);
146 				if (NULL != player) {
147 					if (' ' == player_name[0]) {
148 						player_name++;
149 					}
150 					player->name = strdup(player_name);
151 					debug(4, "Player: %s\n", player->name);
152 				}
153 				player_name = strtok_ret(NULL, ",", &playersp);
154 			}
155 		} else if (0 == strcmp(key, "status")) {
156 			if (200 != atoi(val)) {
157 				server->server_name = DOWN;
158 				return (DONE_FORCE);
159 			}
160 		} else {
161 			add_rule(server, key, val, NO_FLAGS);
162 		}
163 
164 		s = strtok_ret(NULL, "\x0d\x0a", &linep);
165 	}
166 
167 	gettimeofday(&server->packet_time1, NULL);
168 
169 	return (DONE_FORCE);
170 }
171