1 /* masterserver.c: A generic masterserver for various games. */
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 andre@malchen.de
20  */
21 /*
22  * vim:sw=4:ts=4
23  */
24 
25 #include <pthread.h>
26 #include <stdio.h>
27 #include <stdarg.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <signal.h>
32 #include <errno.h>
33 #include <dirent.h> 	// opendir()
34 #include <dlfcn.h> 		// for dlopen() etc.
35 #include <fcntl.h>
36 #include <grp.h> 		// for changing user
37 #include <pwd.h> 		// for changing user
38 
39 #if defined(SOLARIS)
40 #include <sys/time.h>
41 #include <limits.h>		// PATH_MAX
42 #elif defined(__FreeBSD__) || defined(__DragonFly__)
43 #include <limits.h>
44 #endif
45 
46 #include <sys/stat.h>
47 #include <sys/types.h>
48 #include <sys/select.h> // for select()
49 #include <sys/socket.h> // for socket() etc.
50 #include <netinet/in.h>
51 #include <arpa/inet.h>
52 #include <net/if.h> 	// IFNAMSIZ
53 
54 #include "masterserver.h" // masterserver stuff
55 
56 #define MAX_PKT_LEN	1024 // max packet length
57 
58 #undef LOG_SUBNAME
59 #define LOG_SUBNAME "main" // logging subcategory description
60 
61 // linked list for keeping track of plugins
62 // beware! it's a pointer array! :)
63 struct masterserver_plugin *plugins = NULL;
64 
65 
66 // function prototypes
67 void change_user_and_group_to(char *, char *); // self explanatory
68 extern void delete_server(struct masterserver_plugin *, int); // generic function for removing servers from server list
69 void exit_printhelp(void); // print help and exit
70 void exit_printversion(void); // print version and exit
71 int load_plugins(char *, void ***); // load plugins from the given directory
72 void plugin_thread(void *); // main thread calling plugin routines
73 void plugin_heartbeat_thread(void *); // remove dead servers from server list
74 extern void register_plugin(struct masterserver_plugin *); // plugins call this functions to register themselves
75 void sigint_handler(int); // SIGINT handler
76 
77 void
exit_printhelp(void)78 exit_printhelp(void)
79 {
80 	fprintf(stdout,
81 "Usage: masterserver [options]\n"
82 "Options:\n"
83 "  -D\t\tgo into daemon mode\n"
84 "  -d\t\tdebug mode\n"
85 "  -g groupname\tgroup under which masterserver shall run\n"
86 "    \t\t(only together with -u)\n"
87 "  -h\t\toutput this help text\n"
88 "  -i interface\tbind the masterserver to specific interfaces\n"
89 "    \t\t(use more than once for multiple interfaces)\n"
90 "  -l filename\tlog stdout to a file\n"
91 /*"  -L\tset log level\n"
92 "    \t0 = INFO\n"
93 "    \t1 = WARNING\n"
94 "    \t2 = ERROR\n"*/
95 "  -p path\tset location of plugins\n"
96 "  -u username\tusername under which masterserver shall run\n"
97 "  -V\t\tdisplay version information and exit\n"
98 "Report bugs to <chickenman@exhale.de>.\n");
99 }
100 
101 void
exit_printversion(void)102 exit_printversion(void)
103 {
104 	fprintf(stdout,
105 "Copyright (C) 2003,2004,2005 Andr� Schulz and Ingo Rohlfs\n"
106 "masterserver comes with NO WARRANTY,\n"
107 "to the extent permitted by law.\n"
108 "You may redistribute copies of masterserver\n"
109 "under the terms of the GNU General Public License.\n"
110 "For more information about these matters,\n"
111 "see the files named COPYING.\n\n");
112 
113 	exit(EXIT_SUCCESS);
114 }
115 
116 void
change_user_and_group_to(char * user,char * group)117 change_user_and_group_to(char *user, char *group)
118 {
119 	int retval = 0;
120 	struct passwd *passwd_temp;
121 	struct group *group_temp;
122 
123 	DEBUG("getting uid of user \"%s\"\n", user);
124 	// check if user exists and get user infos
125 	passwd_temp = getpwnam(user);
126 	if (passwd_temp == NULL) {
127 		ERRORV("getpwnam() (errno: %d - %s)\n", errno, strerror(errno));
128 		exit(EXIT_FAILURE);
129 	}
130 
131 	if (group == NULL)
132 		group_temp = getgrgid(passwd_temp->pw_gid);
133 	else
134 		group_temp = getgrnam(group);
135 	if (group_temp == NULL) {
136 		ERRORV("getgrgid() (errno: %d - %s)\n", errno, strerror(errno));
137 		exit(EXIT_FAILURE);
138 	}
139 	DEBUG("setting gid to %d (\"%s\")\n",
140 		group_temp->gr_gid, group_temp->gr_name);
141 	// change uid/gid to drop privileges
142 	retval = setgid(group_temp->gr_gid);
143 	if (retval == -1) {
144 		ERRORV("setgid() (errno: %d - %s)\n", errno, strerror(errno));
145 		exit(EXIT_FAILURE);
146 	}
147 
148 	DEBUG("setting uid to %d (\"%s\")\n",
149 		passwd_temp->pw_uid, passwd_temp->pw_name);
150 	retval = setuid(passwd_temp->pw_uid);
151 	if (retval == -1) {
152 		ERRORV("setuid() (errno: %d - %s)\n", errno, strerror(errno));
153 		exit(EXIT_FAILURE);
154 	}
155 	INFO("uid/gid change successful\n");
156 }
157 
158 int
load_plugins(char * masterserver_plugin_dir,void *** handle)159 load_plugins(char *masterserver_plugin_dir, void ***handle)
160 {
161 	int retval = 0;
162 	int num_plugins = 0;
163 	DIR *plugin_dir; // for opening the plugin dir
164 	struct dirent *plugin_dir_entry;
165 	char path[PATH_MAX]; // path to plugin dir
166 
167 	// open plugin directory
168 	DEBUG("opening %s\n", masterserver_plugin_dir);
169 	plugin_dir = opendir(masterserver_plugin_dir);
170 	if (plugin_dir == NULL) {
171 		ERRORV("opendir(%s) (errno: %d - %s)\n", masterserver_plugin_dir, errno, strerror(errno));
172 		return -1;
173 	}
174 
175 	// load all plugins in masterserver_plugin_dir
176 	while ((plugin_dir_entry = readdir(plugin_dir))) {
177 		// omit ., .. and files non-.so suffix
178 		if ((strcmp(plugin_dir_entry->d_name, ".") == 0)
179 				|| (strcmp(plugin_dir_entry->d_name, "..") == 0)
180 				|| (strcmp(plugin_dir_entry->d_name+strlen(plugin_dir_entry->d_name)-3, ".so") != 0))
181 			continue;
182 
183 		snprintf(path,
184 #ifdef __DragonFly__
185 			strlen(masterserver_plugin_dir)+_DIRENT_RECLEN(plugin_dir_entry->d_namlen)+2,
186 #else
187 			strlen(masterserver_plugin_dir)+plugin_dir_entry->d_reclen+2,
188 #endif
189 			"%s/%s", masterserver_plugin_dir, plugin_dir_entry->d_name);
190 		DEBUG("path: \"%s\"\n", path);
191 
192 		// allocate memory for the new handle
193 		*handle = realloc(*handle, (num_plugins+1)*sizeof(void*));
194 		if (*handle == NULL) {
195 			ERRORV("realloc() failed trying to get %d bytes!\n",
196 					(num_plugins+1)*sizeof(void *));
197 			return -1;
198 		}
199 		(*handle)[num_plugins] = dlopen(path, RTLD_NOW);
200 		if ((*handle)[num_plugins] == NULL)
201 		{
202 			ERRORV("dlopen (%s)\n", dlerror());
203 			return -1;
204 		}
205 		DEBUG("dlopen() successful (0x%x)\n", (*handle)[num_plugins]);
206 		INFO("%s loaded\n", plugin_dir_entry->d_name);
207 		num_plugins++;
208 	}
209 
210 	retval = closedir(plugin_dir);
211 	if (retval == -1)
212 	{
213 		ERRORV("closedir(%s) (errno: %d - %s)\n", plugin_dir, errno, strerror(errno));
214 		return -1;
215 	}
216 	DEBUG("closedir succeeded\n");
217 
218 	return num_plugins;
219 }
220 
221 extern void
register_plugin(struct masterserver_plugin * me)222 register_plugin(struct masterserver_plugin *me)
223 {
224 	struct masterserver_plugin **i;
225 
226 	if (strcmp(me->cversion, masterserver_version) != 0) {
227 		WARNING("plugin %s was compiled for masterserver version %s (this is %s)\n", me->name, me->cversion, masterserver_version);
228 		WARNING("plugin %s disabled\n", me->name);
229 		me->enabled = 0;
230 	} else {
231 		me->enabled = 1; // plugin is enabled
232 	}
233 
234 	// append to linked list
235 	for (i = &plugins; *i; i = &(*i)->next);
236 	me->next = NULL;
237 	*i = me;
238 
239 	// initialize plugin structure
240 	// me->mutex = PTHREAD_MUTEX_INITIALIZER;
241 	pthread_mutex_init(&me->mutex, NULL);
242 	me->num_servers = 0;
243 	me->list = calloc(1, sizeof(serverlist_t)); // initialize server list
244 	if (me->list == NULL) {
245 		ERRORV("calloc() failed to get %d bytes!\n", sizeof(serverlist_t));
246 		exit(EXIT_FAILURE);
247 	}
248 	me->num_sockets = 0;
249 	me->socket_d = NULL;
250 	me->server = calloc(me->num_ports, sizeof(struct sockaddr_in));
251 	if (me->server == NULL) {
252 		ERRORV("calloc() failed trying to get %d bytes!\n", me->num_ports*sizeof(struct sockaddr_in));
253 		exit(EXIT_FAILURE);
254 	}
255 	me->msg_out = NULL;
256 	me->msg_out_length = NULL;
257 	me->info(); // display plugin info
258 }
259 
260 int
main(int argc,char * argv[])261 main(int argc, char *argv[])
262 {
263 	// cmdline options
264 	int option_logfile = 0;
265 	int option_bind_to_interface = 0;
266 	int option_daemon = 0;
267 	int option_plugin_dir = 0;
268 	int option_change_user_and_group = 0;
269 	char *user = NULL;
270 	char *group = NULL;
271 	int i, k, l, num_plugins;
272 
273 	void **handle = NULL; // for dlopen() calls
274 	int retval;	// return value of syscalls
275 	unsigned int num_plugins_enabled, num_listen_interfaces = 0;
276 	char *logfile; // pointer to argv argument
277 	char *masterserver_plugin_dir; // pointer to argv argument
278 	char **listen_interface = NULL; // ptr array for storing interface/device names
279 	struct masterserver_plugin **j; // temporary variable
280 
281 	// temporary variables
282 	int setsockopt_temp = 1;
283 	pid_t temp_pid;
284 
285 	// seed the rng; needed for challenge creation in q3 plugin
286 	srand(time(NULL));
287 
288 	log_init(NULL, "masterserver");
289 	INFO("masterserver v%s\n", masterserver_version);
290 
291 	// TODO: read config
292 
293 	// cmdline parser
294 	while (1) {
295 		//retval = getopt(argc, argv, "?dDhi:l:L:p:V");
296 		retval = getopt(argc, argv, "?dDg:hi:l:p:u:V");
297 		if (retval == -1) break;
298 
299 		switch (retval) {
300 			// debug
301 			case 'd':
302 				debug = 1;
303 				break;
304 			// daemon mode
305 			case 'D':
306 				option_daemon = 1;
307 				break;
308 			// run masterserver under a certain group
309 			case 'g':
310 				group = argv[optind-1];
311 				break;
312 			// bind to interface
313 			case 'i':
314 				if (getuid() != 0) {
315 					ERRORV("you have to be root to bind to specific interfaces\n");
316 					exit(EXIT_FAILURE);
317 				}
318 				if (strlen(argv[optind-1]) > IFNAMSIZ) {
319 					ERRORV("interface/device name is longer than IFNAMSIZ = %d"
320 							" chars\n", IFNAMSIZ);
321 					exit(EXIT_FAILURE);
322 				}
323 
324 				num_listen_interfaces++;
325 				listen_interface = realloc(listen_interface, num_listen_interfaces*sizeof(char *));
326 				listen_interface[num_listen_interfaces-1] = argv[optind-1];
327 				option_bind_to_interface = 1;
328 				break;
329 			// log messages to a file
330 			case 'l':
331 				option_logfile = 1;
332 				logfile = argv[optind-1];
333 				break;
334 			/*case 'L':
335 				_log_level = atoi(argv[optind-1]);
336 				if ((_log_level < 0) || (_log_level > 2)) {
337 					ERROR("log level must be 0 <= x <= 2\n");
338 					return -1;
339 				}
340 				break;*/
341 			// plugin path
342 			case 'p':
343 				masterserver_plugin_dir = argv[optind-1];
344 				option_plugin_dir = 1;
345 				break;
346 			// run masterserver as a certain user
347 			case 'u':
348 				if (getuid() != 0) {
349 					ERRORV("you have to be root to change user/group\n");
350 					exit(EXIT_FAILURE);
351 				}
352 				user = argv[optind-1];
353 				option_change_user_and_group = 1;
354 				break;
355 			// version information
356 			case 'V':
357 				exit_printversion();
358 			// help
359 			case 'h':
360 			case '?':
361 			default:
362 				exit_printhelp();
363 				return EXIT_FAILURE;
364 		} // switch(retval)
365 	} // while(1)
366 
367 	if ((group != NULL) && !option_change_user_and_group) {
368 		ERROR("-g can only be used together with -u\n");
369 		return EXIT_FAILURE;
370 	}
371 	// XXX: this is a hack to get multi port working
372 	if ((num_listen_interfaces == 0) && !option_bind_to_interface) num_listen_interfaces = 1;
373 
374 	// check -l cmdline argument
375 	if (option_logfile) {
376 		INFO("masterserver: logging stdout to %s\n", logfile);
377 
378 		// initialize log file
379 		retval = log_init(logfile, "masterserver");
380 		if (retval == -1) {
381 			ERROR("log_init()\n");
382 			return EXIT_FAILURE;
383 		}
384 
385 		// log stdout to log file
386 		/*if (freopen(logfile, "a", stdout) != stdout) {
387 			ERRORV("freopen() (errno: %d - %s)\n", errno, strerror(errno));
388 			return -1;
389 		}*/
390 
391 		// change buffering to per line so we actually see something in the logfile
392 		setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
393 	}
394 
395 	// check -D cmdline argument
396 	if (option_daemon) {
397 		INFO("masterserver: becoming a daemon ... bye, bye\n");
398 		if ( (temp_pid = fork()) < 0) {
399 			ERRORV("fork() (errno: %d - %s)\n", errno, strerror(errno));
400 			return -1;
401 		} else if (temp_pid != 0) {
402 			exit(EXIT_SUCCESS);
403 		}
404 
405 		retval = setsid();
406 		if (retval == -1) {
407 			ERRORV("setsid() (errno: %d - %s)\n", errno, strerror(errno));
408 			exit(EXIT_FAILURE);
409 		}
410 
411 		retval = chdir("/");
412 		if (retval == -1) {
413 			ERRORV("chdir() (errno: %d - %s)\n", errno, strerror(errno));
414 			exit(EXIT_FAILURE);
415 		}
416 
417 		umask(0);
418 
419 		if (option_logfile == 0) {
420 			if (freopen("/dev/null", "a", stdout) != stdout) {
421 				ERRORV("freopen() (errno: %d - %s)\n", errno, strerror(errno));
422 				return EXIT_FAILURE;
423 			}
424 		}
425 	}
426 
427 	// check if user specified an alternative plugin dir
428 	// if he did well we already set it above
429 	// else we set the default here
430 	if (!option_plugin_dir)
431 		masterserver_plugin_dir = MASTERSERVER_LIB_DIR;
432 
433 	// register signal handler
434 	signal(SIGINT, &sigint_handler);
435 
436 	// load all libs in plugin_dir
437 	num_plugins = load_plugins(masterserver_plugin_dir, &handle);
438 	if (num_plugins <= 0) {
439 		ERRORV("no plugins found in \"%s\"\n", masterserver_plugin_dir);
440 		return EXIT_FAILURE;
441 	}
442 
443 	// print out a summary
444 	INFO("%d plugins loaded\n", num_plugins);
445 
446 	// create sockets and bind them
447 	// had to be done because threads inherit the original user
448 	// and we don't want the threads to be root
449 	// TODO: sanity checks (e.g. duplicate ports)
450 	// TODO: check for plugin protocol
451 	j = &plugins;
452 	DEBUG("going to listen on %d interfaces ...\n", num_listen_interfaces);
453 	for (i = 0; i < num_plugins; i++) {
454 		if (*j == NULL) break;
455 		if ((*j)->enabled == 0) {
456 			WARNING("plugin nr %d %s disabled\n", i, (*j)->name);
457 			continue;
458 		}
459 
460 		// create socket(s) for plugin
461 		for (k = 0; k < (*j)->num_ports; k++) {
462 			// fill sockaddr_in structure
463 			(*j)->server[k].sin_family = AF_INET;
464 			(*j)->server[k].sin_port = htons((*j)->port[k].num); // port number from plugin
465 			(*j)->server[k].sin_addr.s_addr = htonl(INADDR_ANY);
466 
467 			for (l = 0; l < num_listen_interfaces; l++, (*j)->num_sockets++) {
468 				(*j)->socket_d = realloc((*j)->socket_d, ((*j)->num_sockets+1)*sizeof(int));
469 				if ((*j)->socket_d == NULL) {
470 					ERRORV("realloc() failed trying to get %d bytes\n",
471 							(*j)->num_sockets+1*sizeof(int));
472 					return EXIT_FAILURE;
473 				}
474 
475 				(*j)->socket_d[(*j)->num_sockets] = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
476 				DEBUG("plugin #%d %s | socket_d[%d] is %d\n", i, (*j)->name,
477 						(*j)->num_sockets, (*j)->socket_d[(*j)->num_sockets]);
478 
479 				// receive broadcast packets
480 				retval = setsockopt((*j)->socket_d[(*j)->num_sockets], SOL_SOCKET,
481 						SO_BROADCAST, &setsockopt_temp, sizeof(setsockopt_temp));
482 				if (retval == -1) {
483 					ERRORV("setsockopt() (errno: %d - %s)\n", errno,
484 							strerror(errno));
485 					return EXIT_FAILURE;
486 				}
487 
488 				// bind socket to the interfaces specified in -i
489 				if (option_bind_to_interface) {
490 #ifdef __linux__
491 					DEBUG("setsockopt(..., \"%s\", %d+1);\n",
492 							listen_interface[l], strlen(listen_interface[l]));
493 					retval = setsockopt((*j)->socket_d[(*j)->num_sockets],
494 							SOL_SOCKET, SO_BINDTODEVICE, listen_interface[l],
495 							strlen(listen_interface[l])+1);
496 					if (retval == -1) {
497 						ERRORV("setsockopt() (errno: %d - %s)\n", errno,
498 								strerror(errno));
499 						return EXIT_FAILURE;
500 					}
501 					DEBUG("%s socket #%d successfully bound to %s\n",
502 							(*j)->name, (*j)->num_sockets, listen_interface[l]);
503 					INFO("listening on %s UDP port %d\n", listen_interface[l],
504 							(*j)->port[k].num);
505 #endif
506 				} else INFO("listening on UDP port %d\n", (*j)->port[k].num);
507 
508 				// bind socket to structure
509 				retval = bind((*j)->socket_d[(*j)->num_sockets],
510 						(struct sockaddr *) &(*j)->server[k],
511 						sizeof(struct sockaddr_in));
512 				if (retval == -1) {
513 					ERRORV("bind() (errno: %d - %s)\n", errno, strerror(errno));
514 					return EXIT_FAILURE;
515 				}
516 			}
517 		}
518 		j = &(*j)->next;
519 	}
520 	DEBUG("sockets successfully created and bound\n");
521 
522 	if (option_bind_to_interface
523 		|| option_change_user_and_group)
524 	{
525 		change_user_and_group_to(user, group);
526 	}
527 
528 	// main part
529 	DEBUG("creating plugin threads...\n");
530 	j = &plugins;
531 	for (i = 0; i < num_plugins; i++) {
532 		if (*j == NULL) break;
533 		if ((*j)->enabled == 0) continue;
534 
535 		// create plugin thread
536 		retval = pthread_create(&(*j)->thread_nr, NULL, (void *) plugin_thread, (void *) *j);
537 		if (retval != 0) {
538 			switch(retval) {
539 				case EAGAIN:
540 					ERROR("pthread_create returned an error; not enough system"
541 						" resources to create a process for the new thread\n");
542 					ERRORV("or more than %d threads are already active\n", PTHREAD_THREADS_MAX);
543 					return EXIT_FAILURE;
544 			}
545 		}
546 		INFO("created %s plugin thread\n", (*j)->name);
547 		j = &(*j)->next; // point j to next plugin in linked list
548 	}
549 
550 	// create heartbeat threads
551 	DEBUG("creating heartbeat threads...\n");
552 	j = &plugins;
553 	for (i = 0; i < num_plugins; i++) {
554 		if (*j == NULL) break;
555 		if ((*j)->enabled == 0) continue;
556 
557 		retval = pthread_create(&(*j)->heartbeat_thread_nr, NULL,
558 				(void *) plugin_heartbeat_thread, (void *) *j);
559 		if (retval != 0) {
560 			switch(retval) {
561 				case EAGAIN:
562 					ERROR("pthread_create returned an error; not enough system"
563 						" resources to create a process for the new thread\n");
564 					ERRORV("or more than %d threads are already active\n", PTHREAD_THREADS_MAX);
565 	                return EXIT_FAILURE;
566 			}
567 		}
568 		INFO("created heartbeat thread for %s\n", (*j)->name);
569 		j = &(*j)->next;
570 	}
571 
572 	// admin interface
573 	// TODO
574 
575 	// cleanup and exit
576 	// (not really; this is just to stop the parent from eating cpu time)
577 	// XXX: paranoid cleanup ?
578 	//		check if pointers are != NULL
579 	//		destroy mutexes
580 	//		free private data
581 	INFO("joining plugin threads for graceful cleanup/shutdown... \n");
582 	for (j = &plugins; *j; j = &(*j)->next) {
583 		DEBUG("joining %s thread (#%ld)\n", (*j)->name, (*j)->thread_nr);
584 		retval = pthread_join((*j)->thread_nr, NULL);
585 		if (retval != 0) {
586 			ERROR("pthread_join()\n");
587 			return EXIT_FAILURE;
588 		}
589 
590 		DEBUG("joining %s heartbeat thread (#%ld)\n", (*j)->name, (*j)->heartbeat_thread_nr);
591 		retval = pthread_join((*j)->heartbeat_thread_nr, NULL);
592 		if (retval != 0) {
593 			ERROR("pthread_join()\n");
594 			return EXIT_FAILURE;
595 		}
596 		DEBUG("thread #%ld exited; calling plugin cleanup() function\n", (*j)->heartbeat_thread_nr);
597 
598 		// free private data
599 		// to really free all private data we have to call a cleanup function
600 		// of the plugin
601 		if ((*j)->cleanup != NULL) (*j)->cleanup();
602 
603 		// free server list
604 		free((*j)->list);
605 		for (i = 0; i < (*j)->num_sockets; i++) close((*j)->socket_d[i]);
606 		for (i = 0; i < (*j)->num_msgs; i++) free((*j)->msg_out[i]);
607 		free((*j)->msg_out);
608 		free((*j)->msg_out_length);
609 		DEBUG("%s clean up successful\n", (*j)->name);
610 	}
611 
612 	DEBUG("closing dynamic libs ...\n");
613 	INFO("unload plugins\n");
614 	while (num_plugins-- > 0) {
615 		DEBUG("closing dynamic lib %d (0x%x)\n", num_plugins, handle[num_plugins]);
616 		dlclose(handle[num_plugins]);
617 	}
618 	DEBUG("dynamic libs successfully closed\n");
619 
620 	log_close();
621 	return EXIT_SUCCESS;
622 }
623 
624 void
plugin_thread(void * arg)625 plugin_thread(void *arg)
626 {
627 	int retval; // temp var for return values
628 	int i, j, packetlen;
629 	char msg_in[MAX_PKT_LEN]; // buffer for incoming packet
630 	struct masterserver_plugin *me = (struct masterserver_plugin *) arg;
631 	unsigned int client_len = sizeof(me->client);
632 	int n = 0; // for select()
633 	fd_set rfds;
634 
635 	DEBUG("%s_thread: hello world\n", me->name);
636 
637 	// initialize msg_in buffer
638 	memset(msg_in, 0, MAX_PKT_LEN);
639 
640 	// main loop
641 	while (!master_shutdown) {
642 		FD_ZERO(&rfds);
643 		for (i = 0; i < me->num_sockets; i++) {
644 			if (me->socket_d[i] > n) n = me->socket_d[i];
645 			FD_SET(me->socket_d[i], &rfds);
646 		}
647 		retval = select(n+1, &rfds, NULL, NULL, NULL);
648 		if (retval == -1) {
649 			if (errno == EINTR) continue;
650 			ERRORV("%s_thread: select() (errno: %d - %s)\n", me->name, errno,
651 					strerror(errno));
652 			pthread_exit((void *) -1);
653 		}
654 		for (i = 0; i < me->num_sockets; i++) {
655 			if (FD_ISSET(me->socket_d[i], &rfds)) {
656 				packetlen = recvfrom(me->socket_d[i], &msg_in, MAX_PKT_LEN-1, 0,
657 						(struct sockaddr *) &me->client, &client_len);
658 				if (packetlen == -1) {
659 					ERRORV("%s_thread: recvfrom() (errno: %d - %s)\n", me->name,
660 							errno, strerror(errno));
661 					ERRORV("%s_thread: socket_d is %d\n", me->name,
662 							me->socket_d[i]);
663 					ERRORV("%s_thread: MAX_PKT_LEN is %d\n",
664 						me->name, MAX_PKT_LEN);
665 					pthread_exit((void *) -1);
666 				}
667 				DEBUG("%d bytes received\n", packetlen);
668 
669 				DEBUG("locking mutex\n");
670 				retval = pthread_mutex_lock(&me->mutex);
671 				if (retval != 0) {
672 					ERRORV("%s_thread: pthread_mutex_lock() (retval: %d)\n",
673 							me->name, retval);
674 					pthread_exit((void *) -1);
675 				}
676 				DEBUG("mutex succesfully locked\n");
677 
678 				retval = me->process(msg_in, packetlen);
679 				if (retval == -2) {
680 					ERRORV("%s_thread: plugin reported: out of memory\n", me->name);
681 					// TODO: cleanup?
682 					pthread_exit((void *) -1);
683 				} else if (retval == -1) {
684 					//WARNING("%s_thread: plugin reported: invalid packet received\n", me->name);
685 				} else if (retval == 0) {
686 					//INFO("%s_thread: plugin reported: server successfully added\n", me->name);
687 				} else if (retval == 1) {
688 					DEBUG("sending %d packets to %s:%u\n",
689 							me->num_msgs, inet_ntoa(me->client.sin_addr),
690 							ntohs(me->client.sin_port));
691 
692 					for (j = 0; j < me->num_msgs; j++) {
693 						retval = sendto(me->socket_d[i], me->msg_out[j],
694 								me->msg_out_length[j], 0,
695 								(struct sockaddr *) &me->client, client_len);
696 						if (retval == -1) {
697 							ERRORV("sendto() (errno: %d - %s)\n",
698 									errno, strerror(errno));
699 						} else DEBUG("%d bytes sent\n", retval);
700 					}
701 				} else if (retval == 2) {
702 					// INFO("%s_thread: plugin reported: server deleted\n", me->name);
703 				}
704 
705 				// clean up
706 				memset(msg_in, 0, MAX_PKT_LEN);
707 				if (me->num_msgs > 0) {
708 					DEBUG("freeing outgoing packets\n");
709 					for (j = 0; j < me->num_msgs; j++)
710 						free(me->msg_out[j]);
711 					me->num_msgs = 0;
712 					free(me->msg_out);
713 					free(me->msg_out_length);
714 					me->msg_out = NULL;
715 					me->msg_out_length = NULL;
716 				}
717 
718 				DEBUG("unlocking mutex\n");
719 				retval = pthread_mutex_unlock(&me->mutex);
720 				if (retval != 0) {
721 					ERROR("pthread_mutex_unlock()\n");
722 					pthread_exit((void *) -1);
723 				}
724 			} // if(FD_ISSET())
725 		} // for(i)
726 	} // while()
727 }
728 
729 void
plugin_heartbeat_thread(void * arg)730 plugin_heartbeat_thread(void *arg)
731 {
732 	struct masterserver_plugin *me = (struct masterserver_plugin *) arg;
733 	int i = 0;
734 	int heartbeat_diff = 0;
735 	int retval; // temp var for return values
736 
737 	DEBUG("%s_heartbeat_thread: hello world\n", me->name);
738 
739 	// main loop
740 	while (!master_shutdown) {
741 		DEBUG("sleeping %d seconds ...\n", me->heartbeat_timeout);
742 		sleep(me->heartbeat_timeout);
743 		DEBUG("waking up\n");
744 
745 		DEBUG("locking plugin mutex\n");
746 		retval = pthread_mutex_lock(&me->mutex);
747 		if (retval != 0) {
748 			ERROR("pthread_mutex_lock()\n");
749 			pthread_exit((void *) -1);
750 		}
751 
752 		for (i = 0; i < me->num_servers; i++) {
753 			heartbeat_diff = time(NULL) - me->list[i].lastheartbeat;
754 			if (heartbeat_diff > 300) {
755 				INFO("%s_heartbeat_thread: server %s:%d died (heartbeat_diff %d)\n",
756 					me->name, inet_ntoa(me->list[i].ip), ntohs(me->list[i].port), heartbeat_diff);
757 				delete_server(me, i);
758 				i--;
759 			} else {
760 				DEBUG("server %s:%d is alive (heartbeat_diff %d)\n",
761 						inet_ntoa(me->list[i].ip), ntohs(me->list[i].port),
762 						heartbeat_diff);
763 			}
764 		}
765 
766 		DEBUG("unlocking mutex\n");
767 		retval = pthread_mutex_unlock(&me->mutex);
768 		if (retval != 0) {
769 			ERROR("pthread_mutex_unlock\n");
770 			pthread_exit((void *) -1);
771 		}
772 	} // while()
773 }
774 
775 extern void
delete_server(struct masterserver_plugin * me,int server_num)776 delete_server(struct masterserver_plugin *me, int server_num)
777 {
778 	if (me->free_privdata != NULL) me->free_privdata(me->list[server_num].private_data);
779 	me->num_servers--;
780 	me->list[server_num] = me->list[me->num_servers];
781 
782 	DEBUG("reallocating server list (old size: %d -> new size: %d)\n",
783 			(me->num_servers+2)*sizeof(serverlist_t),
784 			(me->num_servers+1)*sizeof(serverlist_t));
785 	me->list = (serverlist_t *) realloc(me->list, (me->num_servers+1)*sizeof(serverlist_t));
786 	if (me->list == NULL) {
787 		ERROR("(__)\n");
788 		ERROR(" ��\\\\\\~\n");
789 		ERROR("  !!!!\n");
790 		pthread_exit((void *) -1);
791 	}
792 	DEBUG("reallocation successful\n");
793 }
794 
795 void
sigint_handler(int signum)796 sigint_handler(int signum)
797 {
798 	DEBUG("caught SIGINT!\n");
799 	master_shutdown = 1;
800 }
801 
802