tidied up main.c code
[NeonServV5.git] / src / main.c
1 /* main.c - NeonServ v5.6
2  * Copyright (C) 2011-2012  Philipp Kreil (pk910)
3  * 
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  * 
14  * You should have received a copy of the GNU General Public License 
15  * along with this program. If not, see <http://www.gnu.org/licenses/>. 
16  */
17
18 #define DEFAULT_PID_FILE "neonserv.pid"
19 #define DEFAULT_CONF_FILE "neonserv.conf"
20 #define DEFAULT_LOG_FILE "neonserv.log"
21
22 #include "main.h"
23 #include "signal.h"
24 #include "ClientSocket.h"
25 #include "UserNode.h"
26 #include "ChanNode.h"
27 #include "IRCEvents.h"
28 #include "IRCParser.h"
29 #include "modcmd.h"
30 #include "WHOHandler.h"
31 #include "bots.h"
32 #include "mysqlConn.h"
33 #include "HandleInfoHandler.h"
34 #include "lang.h"
35 #include "tools.h"
36 #include "timeq.h"
37 #include "EventLogger.h"
38 #include "ModeNode.h"
39 #include "IRCQueue.h"
40 #include "DBHelper.h"
41 #include "ConfigParser.h"
42 #include "QServer.h"
43 #include "version.h"
44 #include "modules.h"
45 #include "module_commands.h"
46 #include "ModuleFunctions.h"
47 #include "IOHandler.h"
48 #include "statistics.h"
49
50 static struct {
51     time_t start_time;
52     int running : 1;
53     int restart : 1;
54     int run_as_daemon : 1;
55     int daemonized : 1;
56     int loglevel : 8;
57     int loaded_config : 1;
58     int running_threads : 8;
59     
60     int argc;
61     char **argv;
62     
63     char config[MAXLEN];
64     char pidfile[MAXLEN];
65 } process_state;
66
67 static FILE *log_fptr = NULL;
68
69 #ifdef HAVE_THREADS
70 pthread_mutex_t cache_sync;
71 pthread_mutex_t whohandler_sync, whohandler_mass_sync;
72 static pthread_mutex_t log_sync;
73 static pthread_t *current_threads = NULL;
74 #endif
75
76 static void main_tread(void *empty);
77 static TIMEQ_CALLBACK(clear_cache);
78 static TIMEQ_CALLBACK(main_checkauths);
79 static void check_firstrun();
80
81
82 static void main_parse_arguments() {
83     int c;
84     struct option options[] = {
85         {"show", 1, 0, 's'},
86         {"foreground", 0, 0, 'f'},
87         {"config", 1, 0, 'c'},
88         {"pid", 1, 0, 'p'},
89         {"help", 0, 0, 'h'},
90         {"version", 0, 0, 'v'},
91         {0, 0, 0, 0}
92     };
93     while ((c = getopt_long(process_state.argc, process_state.argv, "s:fvh", options, NULL)) != -1) {
94         switch (c) {
95         case 'c':
96             strncpy(process_state.config, optarg, MAXLEN-1);
97             break;
98         case 'p':
99             strncpy(process_state.pidfile, optarg, MAXLEN-1);
100             break;
101         case 's':
102             process_state.loglevel = atoi(optarg);
103             break;
104         case 'f':
105             process_state.run_as_daemon = 0;
106             break;
107         case 'v':
108             printf("Version: %s.%d (%s)\n", NEONSERV_VERSION, patchlevel, (strcmp(revision, "") ? revision : "-"));
109             printf("Build: #%s %s (%s lines, " COMPILER ")\n", compilation, creation, codelines);
110             exit(0);
111             break;
112         case 'h':
113             printf("Usage: ./neonserv [-c neonserv.conf] [-p neonserv.pid] [-s loglevel] [-f] [-h|-v]\n");
114             printf(" -c, --config         use this configuration file.\n");
115             printf(" -f, --foreground     run NeonServ in the foreground.\n");
116             printf(" -h, --help           prints this usage message.\n");
117             printf(" -p, --pid            use this pid file.\n");
118             printf(" -s, --show           show log lines matching loglevel in stdout.\n");
119             printf(" -v, --version        prints this program's version.\n");
120             exit(0);
121             break;
122         }
123     }
124 }
125
126 static void main_daemon_exit() {
127     remove(process_state.pidfile);
128 }
129
130 static void main_daemonize() {
131     #ifndef WIN32
132     /* Attempt to fork into the background if daemon mode is on. */
133     pid_t pid = fork();
134     if (pid < 0) {
135         fprintf(stderr, "Unable to fork: %s\n", strerror(errno));
136     } else if (pid > 0) {
137         printf("Forking into the background (pid: %d)...\n", pid);
138         putlog(LOGLEVEL_INFO, "Forking into the background (pid: %d)...\n", pid);
139         exit(0);
140     }
141     setsid();
142     process_state.daemonized = 1;
143     atexit(main_daemon_exit);
144     FILE *pidfile = fopen(process_state.pidfile, "w");
145     if (pidfile == NULL) {
146         fprintf(stderr, "Unable to create PID file: %s\n", strerror(errno));
147         putlog(LOGLEVEL_ERROR, "Unable to create PID file: %s\n", strerror(errno));
148     } else {
149         fprintf(pidfile, "%i\n", (int)getpid());
150         fclose(pidfile);
151     }
152     FILE *retn;
153     fclose(stdin); retn = fopen("/dev/null", "r");
154     fclose(stdout); retn = fopen("/dev/null", "w");
155     fclose(stderr); retn = fopen("/dev/null", "w");
156     #endif
157 }
158
159 static int reload_configuration() {
160     if(!loadConfig(process_state.config))
161         return 1;
162     if(process_state.loaded_config) {
163         if(!reload_mysql())
164             return 2;
165         char **modulelist = get_all_fieldnames("modules");
166         if(!modulelist || !modulelist[0]) {
167             free(modulelist);
168             return 3;
169         }
170         free(modulelist);
171     }
172     event_reload(!process_state.loaded_config);
173     process_state.loaded_config = 1;
174     return 0;
175 }
176
177
178 /* INITIALISATION OF SUBSYSTEMS */
179 void initialize_subsystems() {
180     init_bind();
181     init_lang();
182     init_parser();
183     init_UserNode();
184     init_ChanNode();
185     init_ModeNode();
186     init_bind();
187         init_modcmd();
188     register_module_commands();
189     init_handleinfohandler();
190     init_tools();
191     init_modulefunctions();
192     loadModules();
193     init_bots();
194     init_DBHelper();
195     qserver_init();
196     load_languages();
197     init_statistics();
198 }
199
200 void shutdown_subsystems() {
201     stop_modules();
202     free_sockets();
203     qserver_free();
204     free_parser();
205     free_UserNode();
206     free_ChanNode();
207     free_bind();
208     free_modcmd();
209     free_whoqueue();
210     free_mysql();
211     free_handleinfohandler();
212     free_lang();
213 }
214
215 /* THREAD CONTROL */
216 #ifdef HAVE_THREADS
217 int getCurrentThreadID() {
218     if(!current_threads) return 0;
219     int i;
220     unsigned int my_tid = (unsigned int) pthread_self_tid();
221     for(i = 0; i < process_state.running_threads; i++) {
222         #ifdef WIN32
223         if((unsigned int) current_threads[i].p == my_tid)
224         #else
225         if((unsigned int) current_threads[i] == my_tid)
226         #endif
227             return i+1;
228     }
229     return 0;
230 }
231 #endif
232
233 int getRunningThreads() {
234         #ifdef HAVE_THREADS
235     return process_state.running_threads;
236         #else
237         return 1;
238         #endif
239 }
240
241 static void main_start_threads() {
242     int tid_id = 0;
243     int worker_threads = get_int_field("General.worker_threads");
244     if(!worker_threads) worker_threads = 1;
245     #ifdef HAVE_THREADS
246     {
247         current_threads = calloc(worker_threads, sizeof(*current_threads));
248         for(tid_id = 0; tid_id < worker_threads; tid_id++) {
249             process_state.running_threads++;
250             pthread_create(&current_threads[tid_id], NULL, main_tread, NULL);
251         }
252     }
253     #endif
254     main_tread(NULL);
255     #ifdef HAVE_THREADS
256     {
257         for(tid_id = 0; tid_id < worker_threads; tid_id++) {
258             pthread_join(current_threads[tid_id], NULL);
259         }
260         process_state.running_threads = 0;
261     }
262     #endif
263 }
264
265 /* MAIN FUNCTION(S) */
266
267 static void main_tread(void *empty) {
268     while(process_state.running) {
269         iohandler_poll();
270     }
271 }
272
273 static void main_restart_process() {
274     /* Append a NULL to the end of argv[]. */
275     char **restart_argv = (char **)alloca((process_state.argc + 1) * sizeof(char *));
276     memcpy(restart_argv, process_state.argv, process_state.argc * sizeof(char *));
277     restart_argv[process_state.argc] = NULL;
278     #ifdef WIN32
279     execv(process_state.argv[0], (const char * const*)restart_argv);
280     #else
281     execv(process_state.argv[0], restart_argv);
282     #endif
283 }
284
285 int main(int argc, char *argv[]) {
286     memset(&process_state, 0, sizeof(process_state));
287     printf("NeonServ v%s\n\n", NEONSERV_VERSION);
288     
289     process_state.argv = argv;
290     process_state.argc = argc;
291     process_state.run_as_daemon = 1;
292     strcpy(process_state.config, DEFAULT_CONF_FILE);
293     strcpy(process_state.pidfile, DEFAULT_PID_FILE);
294     
295     //parse argv
296     main_parse_arguments();
297     
298     //initialize memory debugger BEFORE allocating memory
299     #ifdef ENABLE_MEMORY_DEBUG
300     initMemoryDebug();
301     #endif
302     
303     //deny root startup
304     #ifndef WIN32
305     if(geteuid() == 0 || getuid() == 0) {
306         fprintf(stderr, "NeonServ may not be run with super user privileges.\n");
307         exit(0);
308     }
309     #endif
310     
311     //load configuration
312     int errid;
313     if(errid = reload_configuration()) {
314         fprintf(stderr, "Unable to load configuration file `%s`. (errid: %d)\n", process_state.config, errid);
315         exit(0);
316     }
317     
318     //check mysql configuration
319     if(!reload_mysql()) {
320         fprintf(stderr, "Unable to load MySQL configuration.\n");
321         exit(0);
322     }
323     
324     //check module configuration
325     char **modulelist = get_all_fieldnames("modules");
326     if(!modulelist || !modulelist[0]) {
327         fprintf(stderr, "Unable to load Module configuration.\n");
328         exit(0);
329     }
330     free(modulelist);
331     
332     #if HAVE_THREADS
333     THREAD_MUTEX_INIT(log_sync);
334     THREAD_MUTEX_INIT(cache_sync);
335     THREAD_MUTEX_INIT(whohandler_sync);
336     THREAD_MUTEX_INIT(whohandler_mass_sync);
337     #endif
338     
339     //connect to mysql and check if it's the frst bot startup
340     init_mysql();
341     check_firstrun();
342     
343     //deamonize if wanted
344     if(process_state.run_as_daemon)
345         main_daemonize();
346     
347     //set signal handlers
348     signal(SIGABRT, sighandler);
349     signal(SIGFPE, sighandler);
350     signal(SIGILL, sighandler);
351     signal(SIGINT, sighandler);
352     signal(SIGSEGV, sighandler);
353     signal(SIGTERM, sighandler);
354     
355     //set start time and initialize other code parts
356     process_state.running = 1;
357     process_state.start_time = time(0);
358     initialize_subsystems();
359     
360     //start timers
361     timeq_add(CLEAR_CACHE_INTERVAL, 0, clear_cache, NULL);
362     timeq_add(90, 0, main_checkauths, NULL);
363     
364     //start worker threads
365     main_start_threads();  //BLOCKING
366     
367     //shutdown sequence...
368     shutdown_subsystems();
369     if(process_state.restart)
370         main_restart_process(); //terminates the current process on success
371     
372     //eop (end of program :P)
373     //trust me, thats the end!
374     exit(0);
375 }
376
377 /* BOT INFORMATION */
378 time_t getStartTime() {
379     return process_state.start_time;
380 }
381
382 /* BOT CONTROL */
383 void restart_bot(int crash) {
384     if(crash) {
385         main_daemon_exit();
386         main_restart_process();
387     } else {
388         process_state.restart = 1;
389         process_state.running = 0;
390     }
391 }
392
393 void stop_bot() {
394     process_state.running = 0;
395 }
396
397 void reload_config() {
398     reload_configuration();
399 }
400
401 /* TIMER FUNCTIONS */
402
403 static TIMEQ_CALLBACK(clear_cache) {
404     timeq_add(CLEAR_CACHE_INTERVAL, 0, clear_cache, NULL);
405     clearTempUsers();
406     destroyEvents();
407     mysql_free();
408 }
409
410 static AUTHLOOKUP_CALLBACK(main_checkauths_callback) {
411     //check if registered is still valid
412     MYSQL_RES *res;
413     MYSQL_ROW row;
414     printf_mysql_query("SELECT `user_id`, `user_registered` FROM `users` WHERE `user_user` = '%s'", escape_string(auth));
415     res = mysql_use();
416     if ((row = mysql_fetch_row(res)) != NULL) {
417         int diff = registered - atoi(row[1]);
418         if(diff < 0)
419             diff *= -1;
420         if(!exists || (strcmp(row[1], "0") && diff > 86400)) {
421             //User is no longer valid! Delete it...
422             deleteUser(atoi(row[0]));
423             char *alertchan = get_string_field("General.CheckAuths.alertchan");
424             if(alertchan) {
425                 char reason[MAXLEN];
426                 if(!exists) {
427                     strcpy(reason, "USER_NOT_EXISTS");
428                 } else {
429                     sprintf(reason, "USER_REGISTERED_MISSMATCH: %lu, expected %d (diff: %d)", (unsigned long) registered, atoi(row[1]), diff);
430                 }
431                 struct ChanNode *alertchan_chan = getChanByName(alertchan);
432                 struct ClientSocket *alertclient;
433                 if(alertchan_chan && (alertclient = getChannelBot(alertchan_chan, 0)) != NULL) {
434                     putsock(alertclient, "PRIVMSG %s :Deleted User %s (%s)", alertchan_chan->name, auth, reason);
435                 }
436             }
437         } else if(exists && !strcmp(row[1], "0")) {
438             printf_mysql_query("UPDATE `users` SET `user_registered` = '%lu', `user_lastcheck` = UNIX_TIMESTAMP() WHERE `user_id` = '%s'", (unsigned long) registered, row[0]);
439         } else {
440             printf_mysql_query("UPDATE `users` SET `user_lastcheck` = UNIX_TIMESTAMP() WHERE `user_id` = '%s'", row[0]);
441         }
442     }
443 }
444
445 static TIMEQ_CALLBACK(main_checkauths) {
446     int next_call = 600;
447     if(get_int_field("General.CheckAuths.enabled")) {
448         int check_start_time = get_int_field("General.CheckAuths.start_time") * 3600;
449         int duration = get_int_field("General.CheckAuths.duration") * 60;
450         int now = getCurrentSecondsOfDay();
451         if(now < check_start_time && check_start_time+duration >= 86400) {
452             check_start_time -= 86400;
453         }
454         if(now >= check_start_time && now < (check_start_time + duration)) {
455             next_call = get_int_field("General.CheckAuths.interval");
456             //get the "longest-unchecked-user"
457             MYSQL_RES *res;
458             MYSQL_ROW row;
459             int lastcheck;
460             time_t unixtime = time(0);
461             int min_unckecked = get_int_field("General.CheckAuths.min_unckecked");
462             printf_mysql_query("SELECT `user_user`, `user_lastcheck` FROM `users` ORDER BY `user_lastcheck` ASC LIMIT 1");
463             res = mysql_use();
464             if ((row = mysql_fetch_row(res)) != NULL) {
465                 lastcheck = atoi(row[1]);
466                 if(!lastcheck || unixtime - lastcheck >= min_unckecked) {
467                     lookup_authname(row[0], 0, main_checkauths_callback, NULL);
468                 } else 
469                     next_call = 300;
470             }
471         } else {
472             int pending;
473             if(now > check_start_time)
474                 pending = 86400 - now + check_start_time;
475             else
476                 pending = check_start_time - now;
477             if(pending < 600)
478                 next_call = pending;
479         }
480         
481     }
482     timeq_add(next_call, 0, main_checkauths, NULL);
483 }
484
485 /* LOG BACKEND */
486
487 void write_log(int loglevel, const char *line, int len) {
488     SYNCHRONIZE(log_sync);
489     if(!process_state.daemonized && (process_state.loglevel & loglevel)) {
490         printf("%s", line);
491     } else if(!process_state.daemonized && loglevel == LOGLEVEL_ERROR) {
492         fprintf(stderr, "%s", line);
493     }
494     if(get_int_field("log.loglevel") & loglevel) {
495         if(!log_fptr) {
496             log_fptr = fopen(DEFAULT_LOG_FILE, "a");
497             if(!log_fptr) goto write_log_end;
498         }
499         time_t rawtime;
500         struct tm *timeinfo;
501         time(&rawtime);
502         timeinfo = localtime(&rawtime);
503         char timestr[20];
504         int timepos = strftime(timestr, 20, "%x %X ", timeinfo);
505         fwrite(timestr, 1, timepos, log_fptr);
506         fwrite(line, 1, len, log_fptr);
507     }
508     write_log_end:
509     DESYNCHRONIZE(log_sync);
510 }
511
512 void putlog(int loglevel, const char *text, ...) {
513     va_list arg_list;
514     char logBuf[MAXLOGLEN];
515     int pos;
516     logBuf[0] = '\0';
517     va_start(arg_list, text);
518     pos = vsnprintf(logBuf, MAXLOGLEN - 1, text, arg_list);
519     va_end(arg_list);
520     if (pos < 0 || pos > (MAXLOGLEN - 1)) pos = MAXLOGLEN - 1;
521     logBuf[pos] = '\0';
522     write_log(loglevel, logBuf, pos);
523 }
524
525 /* INSTALLATION SCRIPT */
526
527 static void check_firstrun() {
528     MYSQL_RES *res;
529     MYSQL_ROW row;
530     printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_access` = 1000 LIMIT 1");
531     res = mysql_use();
532     if (mysql_fetch_row(res) == NULL) {
533         //first run!
534         printf("No superuser found...\n");
535         check_firstrun_admin:
536         printf("AuthServ account name of admin user: ");
537         char *ptr;
538         char adminuser[31];
539         ptr = fgets(adminuser, 30, stdin);
540         for(ptr = adminuser; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
541         if(strlen(adminuser) < 2) goto check_firstrun_admin;
542         printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(adminuser));
543         res = mysql_use();
544         if ((row = mysql_fetch_row(res)) != NULL)
545             printf_mysql_query("UPDATE `users` SET `user_access` = 1000 WHERE `user_id` = '%s'", row[0]);
546         else
547             printf_mysql_query("INSERT INTO `users` (`user_user`, `user_access`) VALUES ('%s', 1000)", escape_string(adminuser));
548     }
549     printf_mysql_query("SELECT `id` FROM `bots` WHERE `active` = 1 LIMIT 1");
550     res = mysql_use();
551     if (mysql_fetch_row(res) == NULL) {
552         //no bot active
553         printf("No active bot found...\n\n");
554         printf("ADD NEW BOT\n");
555         char *ptr;
556         char bot_nick[31];
557         check_firstrun_bot_nick:
558         printf("Nick: ");
559         ptr = fgets(bot_nick, 30, stdin);
560         for(ptr = bot_nick; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
561         if(strlen(bot_nick) < 2) goto check_firstrun_bot_nick;
562         char bot_ident[16];
563         check_firstrun_bot_ident:
564         printf("Ident: ");
565         ptr = fgets(bot_ident, 15, stdin);
566         for(ptr = bot_ident; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
567         if(strlen(bot_ident) < 2) goto check_firstrun_bot_ident;
568         char bot_realname[101];
569         check_firstrun_bot_realname:
570         printf("Realname: ");
571         ptr = fgets(bot_realname, 100, stdin);
572         for(ptr = bot_realname; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
573         if(strlen(bot_realname) < 2) goto check_firstrun_bot_realname;
574         char bot_server[101];
575         check_firstrun_bot_server:
576         printf("Server: [irc.onlinegamesnet.net] ");
577         ptr = fgets(bot_server, 100, stdin);
578         for(ptr = bot_server; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
579         if(*bot_server && strlen(bot_nick) < 5) goto check_firstrun_bot_server;
580         if(!*bot_server)
581             strcpy(bot_server, "irc.onlinegamesnet.net");
582         int bot_port;
583         char bot_port_buf[7];
584         printf("Port: [6667] ");
585         ptr = fgets(bot_port_buf, 6, stdin);
586         for(ptr = bot_port_buf; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
587         if(!*bot_port_buf)
588             bot_port = 6667;
589         else
590             bot_port = atoi(bot_port_buf);
591         int bot_ssl;
592         char bot_ssl_buf[5];
593         check_firstrun_bot_ssl:
594         printf("SSL: [y/N] ");
595         ptr = fgets(bot_ssl_buf, 4, stdin);
596         for(ptr = bot_ssl_buf; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
597         if(!*bot_ssl_buf || tolower(*bot_ssl_buf) == 'n')
598             bot_ssl = 0;
599         else if(tolower(*bot_ssl_buf) == 'y')
600             bot_ssl = 1;
601         else
602             goto check_firstrun_bot_ssl;
603         char bot_pass[101];
604         printf("Server Password: [] ");
605         ptr = fgets(bot_pass, 100, stdin);
606         for(ptr = bot_pass; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
607         int bot_maxchan;
608         char bot_maxchan_buf[5];
609         printf("MaxChannel: [20] ");
610         ptr = fgets(bot_maxchan_buf, 5, stdin);
611         for(ptr = bot_maxchan_buf; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
612         if(*bot_maxchan_buf)
613             bot_maxchan = atoi(bot_maxchan_buf);
614         else
615             bot_maxchan = 20;
616         int bot_queue;
617         char bot_queue_buf[5];
618         check_firstrun_bot_queue:
619         printf("Queue (prevents excess floods): [Y/n] ");
620         ptr = fgets(bot_queue_buf, 4, stdin);
621         for(ptr = bot_queue_buf; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
622         if(!*bot_queue_buf || tolower(*bot_queue_buf) == 'y')
623             bot_queue = 1;
624         else if(tolower(*bot_queue_buf) == 'n')
625             bot_queue = 0;
626         else
627             goto check_firstrun_bot_queue;
628         printf_mysql_query("INSERT INTO `bots` (`active`, `nick`, `server`, `port`, `pass`, `ssl`, `ident`, `realname`, `botclass`, `textbot`, `queue`, `defaulttrigger`, `max_channels`) VALUES ('1', '%s', '%s', '%d', '%s', '%d', '%s', '%s', '1', '1', '%d', '+', '%d')", escape_string(bot_nick), escape_string(bot_server), bot_port, escape_string(bot_pass), bot_ssl, escape_string(bot_ident), escape_string(bot_realname), bot_queue, bot_maxchan);
629     }
630 }
631