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