1 /* libqw.c: masterserver plugin for QuakeWorld servers. */
2 /* Copyright (C) 2003  Andre' Schulz
3  * This file is part of masterserver.
4  *
5  * masterserver is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * masterserver is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with masterserver; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  *
19  * The author can be contacted at chickenman@exhale.de
20  */
21 /*
22  * vim:sw=4:ts=4
23  */
24 
25 #include <pthread.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <time.h>
30 #include <sys/socket.h> // for socket() etc.
31 #include <netinet/in.h>
32 #include <arpa/inet.h>
33 
34 #include "../masterserver.h"
35 
36 #define HEARTBEAT_TIMEOUT 300
37 
38 // for logging stuff
39 #undef LOG_SUBNAME
40 #define LOG_SUBNAME "libqw" // logging subcategory description
41 
42 // qw packet stuff
43 // many thanks go to id software and quakeforge
44 const char	qw_pkt_header[]		= "\xff\xff\xff\xff";
45 const int	qw_pkt_header_len	= 4;
46 const char	qw_pkt_heartbeat[]	= "a"; // more data
47 const int	qw_pkt_heartbeat_len= 1;
48 const char	qw_pkt_shutdown[]	= "C";
49 const int	qw_pkt_shutdown_len	= 1;
50 const char	qw_pkt_slistreq[]	= "c";
51 const int	qw_pkt_slistreq_len	= 1;
52 const char	qw_pkt_slistrep[]	= "d\n"; // more data
53 const int	qw_pkt_slistrep_len	= 2;
54 const char	qw_pkt_ping[]		= "k";
55 const int	qw_pkt_ping_len		= 1;
56 const char	qw_pkt_ack[]		= "l";
57 const int	qw_pkt_ack_len		= 1;
58 const char	qw_pkt_nack[]		= "m";
59 const int	qw_pkt_nack_len		= 1;
60 
61 // extended stuff
62 /*
63 const char	qw_pkt_connreq[]	= "b\n%s\n%s\n%s\n"; // connection request
64 const int	qw_pkt_connreq_len	= 2;
65 const char	qw_pkt_nuser_req[]	= "e\n%s\n"; // new user
66 const int	qw_pkt_nuser_req_len= 2;
67 const char	qw_pkt_nuser_rep[]	= "f\n%d"; // new user reply
68 const int	qw_pkt_nuser_rep_len= 2;
69 const char	qw_pkt_connreqs[]	= "i\n%s:%d\n%d\n%s\n";
70 const int	qw_pkt_connreqs_len	= 2;
71 const char	qw_pkt_userp_req[]	= "o\n%s\n%s";
72 const int	qw_pkt_userp_req_len= 2;
73 const char	qw_pkt_userp_rep[]	= "p\n\\*userid\\%d%s";
74 const int	qw_pkt_userp_rep_len= 2;
75 const char	qw_pkt_setinfo[]	= "r\n%s\n%s\n%s\n%s\n";
76 const int	qw_pkt_setinfo_len	= 2;
77 const char	qw_pkt_seen_req[]	= "u\n%s\n";
78 const int	qw_pkt_seen_req_len	= 2;
79 const char	qw_pkt_seen_rep[]	= "v\n%s";
80 const int	qw_pkt_seen_rep_len	= 2;
81 const char	qw_pkt_clientcmd[]	= "B"; // more data
82 const int	qw_pkt_clientcmd_len= 1;
83 const char	qw_pkt_print[]		= "n"; // more data
84 const int	qw_pkt_print_len	= 1;
85 const char	qw_pkt_echo[]		= "e"; // more data?
86 const int	qw_pkt_echo_len		= 1;
87 */
88 
89 const char qwm_plugin_version[] = "0.2";
90 static port_t qwm_ports[] = { { IPPROTO_UDP, 27000 } };
91 
92 // player info
93 typedef struct {
94 	int score;
95 	int ping;
96 	char *name;
97 } qwm_player_data_t;
98 
99 // q3 plugin private data
100 typedef struct {
101 	// statusResponse vars
102 	int fraglimit;
103 	int timelimit;
104 	int teamplay;
105 	int samelevel;
106 	int maxspectators;
107 	int deathmatch;
108 	int spawn;
109 	int watervis;
110 	char *version;
111 	char *progs;
112 
113 	qwm_player_data_t *_player; // player info
114 	// following is information not in packet
115 	int _players; // # of players
116 } qwm_private_data_t;
117 
118 static void	info(void); // print information about plugin
119 static int	process(char *, int); // process packet and return a value
120 static int	process_heartbeat(char *);
121 static int	process_slistreq();
122 static int	process_ping();
123 static int	process_shutdown();
124 //static void cleanup(void);
125 void		init_plugin(void) __attribute__ ((constructor));
126 
127 static
128 struct masterserver_plugin qwm
129 = { "qwm",
130 	qwm_plugin_version,
131 	masterserver_version,
132 	qwm_ports,
133 	1,
134 	HEARTBEAT_TIMEOUT,
135 	&info,
136 	&process,
137 	NULL,	// free_privdata()
138 	NULL	// cleanup()
139 };
140 
141 static void
info(void)142 info(void)
143 {
144 	INFO("quakeworld masterserver plugin v%s\n", qwm_plugin_version);
145 	INFO("  compiled for masterserver v%s\n", masterserver_version);
146 }
147 
148 static int
process_heartbeat(char * packet)149 process_heartbeat(char *packet)
150 {
151 	int server_dup = 0;
152 	int time_diff, i;
153 
154 	// first, check if server is already in our list
155 	for (i = 0; i < qwm.num_servers; i++) {
156 		if ((qwm.list[i].ip.s_addr == qwm.client.sin_addr.s_addr)
157 				&& (qwm.list[i].port == qwm.client.sin_port)) {
158 			DEBUG("duplicate server detected! (%s:%d)\n",
159 					inet_ntoa(qwm.client.sin_addr), ntohs(qwm.client.sin_port));
160 			server_dup = 1;
161 			break;
162 		}
163 	}
164 
165 	INFO("heartbeat from %s:%u\n",
166 			inet_ntoa(qwm.client.sin_addr), ntohs(qwm.client.sin_port));
167 	// if not, then add it to the list
168 	if (!server_dup) {
169 		qwm.list[qwm.num_servers].ip = qwm.client.sin_addr;
170 		qwm.list[qwm.num_servers].port = qwm.client.sin_port;
171 		qwm.list[qwm.num_servers].lastheartbeat = time(NULL);
172 		DEBUG("this is server no.: %d | lastheartbeat: %d\n",
173 				qwm.num_servers, qwm.list[qwm.num_servers].lastheartbeat);
174 		// allocate memory for private data
175 		// XXX: disabled for now
176 		//qwm.list[qwm.num_servers].private_data = calloc(1, sizeof(qwm_private_data_t));
177 
178 		qwm.num_servers++;
179 
180 		DEBUG("reallocating server list (old size: %d -> new size: %d)\n",
181 				qwm.num_servers * sizeof(serverlist_t),
182 				(qwm.num_servers+1) * sizeof(serverlist_t));
183 		qwm.list = (serverlist_t *) realloc(qwm.list, ((qwm.num_servers+1)*sizeof(serverlist_t)));
184 		if (qwm.list == NULL) {
185 			//WARNING("can't increase qwm.list size; out of memory!\n");
186 			ERRORV("realloc() failed trying to get %d bytes!\n",
187 					(qwm.num_servers+1)*sizeof(serverlist_t));
188 			// since the pointer is overwritten with NULL
189 			// we can't recover; so just exit here
190 			// XXX: maybe save the old pointer somewhere so
191 			//		we can continue?
192 			// FIXME: don't pthread_exit() here instead return -3 or so
193 			pthread_exit((void *) -1);
194 		} else DEBUG("reallocation successful\n");
195 	} else {
196 		time_diff = time(NULL) - qwm.list[i].lastheartbeat;
197 		// server is in already in our list so we just update the timestamp
198 		qwm.list[i].lastheartbeat = time(NULL);
199 	}
200 	// server added/updated
201 	return 0;
202 }
203 
204 static int
process_slistreq()205 process_slistreq()
206 {
207 	int i, pkt_offset, pkt_servers = 0; // temp vars
208 	//qwm_private_data_t *temp_priv_data;
209 
210 	INFO("slist_req from %s:%u\n",
211 			inet_ntoa(qwm.client.sin_addr), ntohs(qwm.client.sin_port));
212 
213 	/*
214 	 * This is the new, badly documented packet assembler.
215 	 */
216 	qwm.msg_out = malloc(sizeof(char *));
217 	if (qwm.msg_out == NULL) {
218 		ERRORV("malloc() failed trying to get %d bytes!\n", sizeof(char *));
219 		return -2;
220 	}
221 
222 	qwm.msg_out_length = malloc(sizeof(int));
223 	if (qwm.msg_out_length == NULL) {
224 		ERRORV("malloc() failed trying to get %d bytes!\n", sizeof(int));
225 		return -2;
226 	}
227 
228 	qwm.msg_out_length[0] = qw_pkt_header_len + qw_pkt_slistrep_len
229 							+ (qwm.num_servers*6);
230 
231 	// get memory for header and command
232 	qwm.msg_out[0] = calloc(qwm.msg_out_length[0]+1, 1);
233 	if (qwm.msg_out[0] == NULL) {
234 		ERRORV("calloc() failed trying to get %d bytes!\n",
235 				qwm.msg_out_length[0]+1);
236 		return -2;
237 	}
238 
239 	DEBUG("assembling server list packet\n");
240 
241 	// write header and command into packet
242 	memcpy(qwm.msg_out[0], qw_pkt_header, qw_pkt_header_len);
243 	pkt_offset = qw_pkt_header_len;
244 	memcpy(qwm.msg_out[0]+pkt_offset, qw_pkt_slistrep, qw_pkt_slistrep_len);
245 	pkt_offset += qw_pkt_slistrep_len;
246 
247 	for (i = 0; i < qwm.num_servers; i++) {
248 		//temp_priv_data = (qwm_private_data_t *) qwm.list[i].private_data;
249 		DEBUG("pkt_offset: %d\n", pkt_offset);
250 
251 		// copy data from server list into packet
252 		memcpy(qwm.msg_out[0]+pkt_offset, &qwm.list[i].ip, 4);
253 		pkt_offset += 4;
254 		memcpy(qwm.msg_out[0]+pkt_offset, &qwm.list[i].port, 2);
255 		pkt_offset += 2;
256 		pkt_servers++;
257 	}
258 
259 	DEBUG("pkt_offset: %d\n", pkt_offset);
260 	qwm.num_msgs = 1;
261 
262 	// packet with server list is ready
263 	return 1;
264 }
265 
266 static int
process_ping()267 process_ping()
268 {
269 	INFO("ping from %s:%u\n",
270 			inet_ntoa(qwm.client.sin_addr), ntohs(qwm.client.sin_port));
271 
272 	// prepare qwm.msg_out
273 	qwm.num_msgs = 1;
274 	qwm.msg_out_length = calloc(1, sizeof(int));
275 	if (qwm.msg_out_length == NULL) {
276 		ERRORV("calloc() failed trying to get %d bytes!\n", sizeof(int));
277 		return -2; // TODO: define retval for errors
278 	}
279 	DEBUG("allocated %d bytes for msg_out_length[]\n", sizeof(int));
280 
281 	qwm.msg_out_length[0] = qw_pkt_header_len + qw_pkt_ack_len;
282 
283 	// allocate the memory for the outgoing packet
284 	qwm.msg_out = calloc(1, sizeof(char *));
285 	if (qwm.msg_out == NULL) {
286 		ERRORV("calloc() failed trying to get %d bytes!\n", sizeof(char *));
287 		return -2; // TODO: define retval for errors
288 	}
289 
290 	qwm.msg_out[0] = calloc(qwm.msg_out_length[0]+1, 1);
291 	if (qwm.msg_out[0] == NULL) {
292 		ERRORV("calloc() failed trying to get %d bytes!\n",
293 				qwm.msg_out_length[0]+1);
294 		return -2; // TODO: define retval for errors
295 	}
296 	DEBUG("allocated %d bytes for msg_out[0]\n", qwm.msg_out_length[0]);
297 
298 	memcpy(qwm.msg_out[0], qw_pkt_header, qw_pkt_header_len);
299 	memcpy(qwm.msg_out[0]+qw_pkt_header_len, qw_pkt_ack, qw_pkt_ack_len);
300 
301 	return 1; // send "ack" packet
302 }
303 
304 static int
process_shutdown()305 process_shutdown()
306 {
307 	int i, time_diff, server_dup = 0;
308 
309 	for (i = 0; i < qwm.num_servers; i++) {
310 		if ((qwm.list[i].ip.s_addr == qwm.client.sin_addr.s_addr)
311 				&& (qwm.list[i].port == qwm.client.sin_port)) {
312 			server_dup = 1;
313 			break;
314 		}
315 	}
316 
317 	if (server_dup) {
318 		time_diff = time(NULL) - qwm.list[i].lastheartbeat;
319 		INFO("%s:%u is shutting down (time_diff %d)\n",
320 			inet_ntoa(qwm.list[i].ip), ntohs(qwm.list[i].port), time_diff);
321 		delete_server(&qwm, i);
322 		return 2; // return "server shutdown" code
323 	} else return -1; // invalid packet
324 } // process_shutdown()
325 
326 
327 
328 static int
process(char * packet,int packetlen)329 process(char *packet, int packetlen)
330 {
331 	switch(packet[0]) {
332 		// which packet did we receive?
333 		case 'a':
334 			return process_heartbeat(packet);
335 			break;
336 		case 'c':
337 			return process_slistreq();
338 			break;
339 		case 'k':
340 			return process_ping();
341 			break;
342 		case 'C':
343 			return process_shutdown();
344 			break;
345 		default:
346 			WARNING("unknown packet received!\n");
347 			return -1;
348 	} // end switch()
349 }
350 
351 /*
352 static void
353 cleanup(void)
354 {
355 	int i, j;
356 	qwm_private_data_t *tmp_privdata;
357 
358 	if (qwm.num_servers > 0) {
359 		for (i = 0; i < qwm.num_servers; i++) {
360 			tmp_privdata = (qwm_private_data_t *) qwm.list[i].private_data;
361 			for (j = 0; j < tmp_privdata->_players; j++)
362 				free(tmp_privdata->_player[j].name);
363 			free(tmp_privdata->_player);
364 			free(tmp_privdata->version);
365 			free(tmp_privdata->mapname);
366 			free(tmp_privdata->gamename);
367 			free(tmp_privdata->sv_hostname);
368 			free(tmp_privdata);
369 			free(qwm.list[i].private_data);
370 		}
371 	}
372 }*/
373 
374 void
init_plugin(void)375 init_plugin(void)
376 {
377 	register_plugin(&qwm);
378 }
379 
380