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