1 /*
2  * qstat
3  * by Steve Jankowski
4  *
5  * Crysis 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 #else
18  #include <winsock.h>
19 #endif
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <ctype.h>
23 
24 #include "debug.h"
25 #include "utils.h"
26 #include "qstat.h"
27 #include "md5.h"
28 #include "packet_manip.h"
29 
30 char *
decode_farmsim_val(char * val)31 decode_farmsim_val(char *val)
32 {
33 	// Very basic html conversion
34 	val = str_replace(val, "&quot;", "\"");
35 	return (str_replace(val, "&amp;", "&"));
36 }
37 
38 
39 query_status_t
send_farmsim_request_packet(struct qserver * server)40 send_farmsim_request_packet(struct qserver *server)
41 {
42 	char buf[256], *code;
43 
44 	server->saved_data.pkt_max = -1;
45 	code = get_param_value(server, "code", "");
46 	sprintf(buf, "GET /feed/dedicated-server-stats.xml?code=%s HTTP/1.1\015\012User-Agent: qstat\015\012\015\012", code);
47 
48 	return (send_packet(server, buf, strlen(buf)));
49 }
50 
51 
52 query_status_t
valid_farmsim_response(struct qserver * server,char * rawpkt,int pktlen)53 valid_farmsim_response(struct qserver *server, char *rawpkt, int pktlen)
54 {
55 	char *s;
56 	int len;
57 	int cnt = packet_count(server);
58 
59 	if ((0 == cnt) && (0 != strncmp("HTTP/1.1 200 OK", rawpkt, 15))) {
60 		// not valid response
61 		debug(2, "Invalid");
62 		return (REQ_ERROR);
63 	}
64 
65 	s = strnstr(rawpkt, "Content-Length: ", pktlen);
66 	if (s == NULL) {
67 		// not valid response
68 		debug(2, "Invalid (no content-length)");
69 		return (INPROGRESS);
70 	}
71 	s += 16;
72 
73 	// TODO: remove this bug work around
74 	if (*s == ':') {
75 		s += 2;
76 	}
77 	if (sscanf(s, "%d", &len) != 1) {
78 		debug(2, "Invalid (no length)");
79 		return (INPROGRESS);
80 	}
81 
82 	s = strnstr(rawpkt, "\015\012\015\012", pktlen);
83 	if (s == NULL) {
84 		debug(2, "Invalid (no end of header");
85 		return (INPROGRESS);
86 	}
87 
88 	s += 4;
89 	if (pktlen != (s - rawpkt + len)) {
90 		debug(2, "Outstanding data");
91 		return (INPROGRESS);
92 	}
93 
94 	debug(2, "Valid data");
95 	return (DONE_FORCE);
96 }
97 
98 
99 char *
farmsim_xml_attrib(char * line,char * name)100 farmsim_xml_attrib(char *line, char *name)
101 {
102 	char *q, *p, *val;
103 
104 	p = strstr(line, name);
105 	if (p == NULL) {
106 		return (NULL);
107 	}
108 
109 	p += strlen(name);
110 	if (strlen(p) < 4) {
111 		return (NULL);
112 	}
113 	p += 2;
114 
115 	q = strchr(p, '"');
116 	if (q == NULL) {
117 		return (NULL);
118 	}
119 	*q = '\0';
120 
121 	val = strdup(p);
122 	*q = '"';
123 	debug(4, "%s = %s", name, val);
124 
125 	return (val);
126 }
127 
128 
129 query_status_t
deal_with_farmsim_packet(struct qserver * server,char * rawpkt,int pktlen)130 deal_with_farmsim_packet(struct qserver *server, char *rawpkt, int pktlen)
131 {
132 	char *s, *val, *line;
133 	query_status_t state = INPROGRESS;
134 
135 	debug(2, "processing...");
136 
137 	if (!server->combined) {
138 		state = valid_farmsim_response(server, rawpkt, pktlen);
139 		server->retry1 = n_retries;
140 		if (server->n_requests == 0) {
141 			server->ping_total = time_delta(&packet_recv_time, &server->packet_time1);
142 			server->n_requests++;
143 		}
144 
145 		switch (state) {
146 		case INPROGRESS:
147 		{
148 			// response fragment recieved
149 			int pkt_id;
150 			int pkt_max;
151 
152 			// We're expecting more to come
153 			debug(5, "fragment recieved...");
154 			pkt_id = packet_count(server);
155 			pkt_max = pkt_id + 1;
156 			if (!add_packet(server, 0, pkt_id, pkt_max, pktlen, rawpkt, 1)) {
157 				// fatal error e.g. out of memory
158 				return (MEM_ERROR);
159 			}
160 
161 			// combine_packets will call us recursively
162 			return (combine_packets(server));
163 		}
164 
165 		case DONE_FORCE:
166 			break;  // single packet response fall through
167 
168 		default:
169 			return (state);
170 		}
171 	}
172 
173 	if (state != DONE_FORCE) {
174 		state = valid_farmsim_response(server, rawpkt, pktlen);
175 		switch (state) {
176 		case DONE_FORCE:
177 			break;  // actually process
178 
179 		default:
180 			return (state);
181 		}
182 	}
183 
184 	// Correct ping
185 	// Not quite right but gives a good estimate
186 	server->ping_total = (server->ping_total * server->n_requests) / 2;
187 
188 	debug(3, "processing response...");
189 
190 	s = rawpkt;
191 	// Ensure we're null terminated (will only loose the last \x0a)
192 	s[pktlen - 1] = '\0';
193 	s = decode_farmsim_val(s);
194 	line = strtok(s, "\012");
195 
196 	// NOTE: id=XXX and msg=XXX will be processed by the mod following the one they where the response of
197 	while (line != NULL) {
198 		debug(4, "LINE: %s\n", line);
199 		if (strstr(line, "<Server") != NULL) {
200 			debug(1, "<Server...");
201 			// <Server
202 			// game="Farming Simulator 2013"
203 			// version="2.0.0.5 Public Beta 3"
204 			// server="International"
205 			// name="Multiplay :: liv3dz0r"
206 			// mapName="Hagenstedt"
207 			// money="5993"
208 			// dayTime="24687375"
209 			// mapOverviewFilename="data/maps/map01/pda_map.png">
210 
211 			// Server Name
212 			val = farmsim_xml_attrib(line, "name");
213 			if (val != NULL) {
214 				server->server_name = val;
215 			} else {
216 				server->server_name = strdup("Unknown");
217 			}
218 
219 			// Map Name
220 			val = farmsim_xml_attrib(line, "mapName");
221 			if (val != NULL) {
222 				server->map_name = val;
223 			} else {
224 				server->map_name = strdup("Default");
225 			}
226 		} else if (strstr(line, "<Slots") != NULL) {
227 			// <Slots capacity="16" numUsed="1">
228 			debug(1, "<Slots...");
229 
230 			// Max Players
231 			val = farmsim_xml_attrib(line, "capacity");
232 			if (val != NULL) {
233 				server->max_players = atoi(val);
234 				free(val);
235 			} else {
236 				server->max_players = get_param_ui_value(server, "maxplayers", 1);
237 			}
238 			// Num Players
239 			val = farmsim_xml_attrib(line, "numUsed");
240 			if (val != NULL) {
241 				server->num_players = atoi(val);
242 				free(val);
243 			} else {
244 				server->num_players = 0;
245 			}
246 		}
247 
248 		line = strtok(NULL, "\012");
249 	}
250 
251 	gettimeofday(&server->packet_time1, NULL);
252 
253 	return (DONE_FORCE);
254 }
255