X-Git-Url: http://git.pk910.de/?p=NeonServV5.git;a=blobdiff_plain;f=src%2Fmain.c;h=fb16300da97ac5697e13adfcc59b85811a8468d3;hp=183ebaedb3e1e205215b51ed0a8c7395d9beb91d;hb=HEAD;hpb=be180588e158f3b1297ffaf3a715577fb3d73f85 diff --git a/src/main.c b/src/main.c index 183ebae..fb16300 100644 --- a/src/main.c +++ b/src/main.c @@ -1,5 +1,5 @@ -/* main.c - NeonServ v5.2 - * Copyright (C) 2011 Philipp Kreil (pk910) +/* main.c - NeonServ v5.6 + * Copyright (C) 2011-2012 Philipp Kreil (pk910) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,7 +15,11 @@ * along with this program. If not, see . */ +#define DEFAULT_PID_FILE "neonserv.pid" +#define DEFAULT_CONF_FILE "neonserv.conf" + #include "main.h" +#include "signal.h" #include "ClientSocket.h" #include "UserNode.h" #include "ChanNode.h" @@ -33,135 +37,564 @@ #include "ModeNode.h" #include "IRCQueue.h" #include "DBHelper.h" -#include "commands.h" #include "ConfigParser.h" +#include "QServer.h" +#include "version.h" +#include "modules.h" +#include "module_commands.h" +#include "ModuleFunctions.h" +#include "IOHandler.h" +#include "statistics.h" +#include "log.h" + +struct ProcessState process_state; + +#ifdef HAVE_THREADS +pthread_mutex_t cache_sync; +pthread_mutex_t whohandler_sync, whohandler_mass_sync; +static pthread_t *current_threads = NULL; +#endif + +static void *main_tread(void *empty); +static TIMEQ_CALLBACK(clear_cache); +static TIMEQ_CALLBACK(main_checkauths); +static void check_firstrun(); + + +static void main_parse_arguments() { + int c; + struct option options[] = { + {"show", 1, 0, 's'}, + {"foreground", 0, 0, 'f'}, + {"config", 1, 0, 'c'}, + {"pid", 1, 0, 'p'}, + {"help", 0, 0, 'h'}, + {"version", 0, 0, 'v'}, + {0, 0, 0, 0} + }; + while ((c = getopt_long(process_state.argc, process_state.argv, "s:fvh", options, NULL)) != -1) { + switch (c) { + case 'c': + strncpy(process_state.config, optarg, MAXLEN-1); + break; + case 'p': + strncpy(process_state.pidfile, optarg, MAXLEN-1); + break; + case 's': + process_state.loglevel = atoi(optarg); + break; + case 'f': + process_state.run_as_daemon = 0; + break; + case 'v': + printf("Version: %s.%d (%s)\n", NEONSERV_VERSION, patchlevel, (strcmp(revision, "") ? revision : "-")); + printf("Build: #%s %s (%s lines, " COMPILER ")\n", compilation, creation, codelines); + exit(0); + break; + case 'h': + printf("Usage: ./neonserv [-c neonserv.conf] [-p neonserv.pid] [-s loglevel] [-f] [-h|-v]\n"); + printf(" -c, --config use this configuration file.\n"); + printf(" -f, --foreground run NeonServ in the foreground.\n"); + printf(" -h, --help prints this usage message.\n"); + printf(" -p, --pid use this pid file.\n"); + printf(" -s, --show show log lines matching loglevel in stdout.\n"); + printf(" -v, --version prints this program's version.\n"); + exit(0); + break; + } + } +} + +static void main_daemon_exit() { + remove(process_state.pidfile); +} + +static void main_daemonize() { + #ifndef WIN32 + /* Attempt to fork into the background if daemon mode is on. */ + pid_t pid = fork(); + if (pid < 0) { + fprintf(stderr, "Unable to fork: %s\n", strerror(errno)); + } else if (pid > 0) { + printf("Forking into the background (pid: %d)...\n", pid); + printf_log("main", LOG_INFO, "Forking into the background (pid: %d)...\n", pid); + exit(0); + } + setsid(); + process_state.daemonized = 1; + atexit(main_daemon_exit); + FILE *pidfile = fopen(process_state.pidfile, "w"); + if (pidfile == NULL) { + fprintf(stderr, "Unable to create PID file: %s\n", strerror(errno)); + printf_log("main", LOG_ERROR, "Unable to create PID file: %s\n", strerror(errno)); + } else { + fprintf(pidfile, "%i\n", (int)getpid()); + fclose(pidfile); + } + FILE *retn; + fclose(stdin); retn = fopen("/dev/null", "r"); + fclose(stdout); retn = fopen("/dev/null", "w"); + fclose(stderr); retn = fopen("/dev/null", "w"); + #endif +} + +static int reload_configuration() { + printf_log("main", LOG_DEBUG, "reloading configuration file: %s", process_state.config); + if(!loadConfig(process_state.config)) { + printf_log("main", LOG_ERROR, "could not reload configuration file: %s", process_state.config); + return 1; + } + if(process_state.loaded_config) { + if(!reload_mysql()) + return 2; + char **modulelist = get_all_fieldnames("modules"); + if(!modulelist || !modulelist[0]) { + free(modulelist); + return 3; + } + free(modulelist); + event_reload(0); + } + process_state.loaded_config = 1; + return 0; +} + -time_t start_time; +/* INITIALISATION OF SUBSYSTEMS */ +void initialize_subsystems() { + init_bind(); + init_log(); + printf_log("main", LOG_INFO, "starting up NeonServ %s subsystems...", NEONSERV_VERSION); + init_lang(); + init_parser(); + init_UserNode(); + init_ChanNode(); + init_ModeNode(); + init_modcmd(); + register_module_commands(); + init_handleinfohandler(); + init_tools(); + init_modulefunctions(); + loadModules(); + init_bots(); + init_DBHelper(); + qserver_init(); + load_languages(); + init_statistics(); +} -void cleanup() { - free_sockets(); +void shutdown_subsystems() { + printf_log("main", LOG_INFO, "stopping NeonServ subsystems..."); + free_sockets(1); + //wait 50ms (run iohandler) + { + struct timeval timeout, ctime1, ctime2; + gettimeofday(&ctime1, NULL); + ctime1.tv_usec += 50000; + if(ctime1.tv_usec > 1000000) { + ctime1.tv_usec -= 1000000; + ctime1.tv_sec++; + } + do { + timeout.tv_sec = 0; + timeout.tv_usec = 10000; + iohandler_poll_timeout(timeout); + gettimeofday(&ctime2, NULL); + } while(timeval_is_bigger(ctime1, ctime2)); + } + stop_modules(); + free_sockets(0); + qserver_free(); free_parser(); free_UserNode(); free_ChanNode(); free_bind(); free_modcmd(); free_whoqueue(); - free_bots(); free_mysql(); free_handleinfohandler(); free_lang(); } -static int load_mysql_config() { - char *mysql_host, *mysql_user, *mysql_pass, *mysql_base; - int mysql_serverport; - if(loadConfig("neonserv.conf")) { - mysql_host = get_string_field("MySQL.host"); - if(!mysql_host) { - perror("invalid neonserv.conf: missing MySQL.host"); - return 0; - } - mysql_serverport = get_int_field("MySQL.port"); - if(!mysql_serverport) - mysql_serverport = 3306; - mysql_user = get_string_field("MySQL.user"); - if(!mysql_user) { - perror("invalid neonserv.conf: missing MySQL.user"); - return 0; - } - mysql_pass = get_string_field("MySQL.pass"); - if(!mysql_pass) { - perror("invalid neonserv.conf: missing MySQL.pass"); - return 0; +/* THREAD CONTROL */ +#ifdef HAVE_THREADS +int getCurrentThreadID() { + if(!current_threads) return 0; + int i; + unsigned int my_tid = (unsigned int) pthread_self_tid(); + for(i = 0; i < process_state.running_threads; i++) { + #ifdef WIN32 + if((unsigned int) current_threads[i].p == my_tid) + #else + if((unsigned int) current_threads[i] == my_tid) + #endif + return i+1; + } + return 0; +} +#endif + +int getRunningThreads() { + #ifdef HAVE_THREADS + return process_state.running_threads; + #else + return 1; + #endif +} + +static void main_start_threads() { + int worker_threads = get_int_field("General.worker_threads"); + if(!worker_threads) worker_threads = 1; + #ifdef HAVE_THREADS + int tid_id = 0; + { + current_threads = calloc(worker_threads, sizeof(*current_threads)); + for(tid_id = 0; tid_id < worker_threads; tid_id++) { + process_state.running_threads++; + pthread_create(¤t_threads[tid_id], NULL, main_tread, NULL); } - mysql_base = get_string_field("MySQL.base"); - if(!mysql_base) { - perror("invalid neonserv.conf: missing MySQL base"); - return 0; + } + #endif + main_tread(NULL); + #ifdef HAVE_THREADS + { + for(tid_id = 0; tid_id < worker_threads; tid_id++) { + pthread_join(current_threads[tid_id], NULL); } - } else { - perror("Unable to load neonserv.conf"); - return 0; + process_state.running_threads = 0; } - init_mysql(mysql_host, mysql_serverport, mysql_user, mysql_pass, mysql_base); - return 1; + #endif +} + +/* MAIN FUNCTION(S) */ + +static void *main_tread(void *empty) { + while(process_state.running) { + iohandler_poll(); + } + return NULL; +} + +static void main_restart_process() { + /* Append a NULL to the end of argv[]. */ + char **restart_argv = (char **)alloca((process_state.argc + 1) * sizeof(char *)); + memcpy(restart_argv, process_state.argv, process_state.argc * sizeof(char *)); + restart_argv[process_state.argc] = NULL; + #ifdef WIN32 + execv(process_state.argv[0], (const char * const*)restart_argv); + #else + execv(process_state.argv[0], restart_argv); + #endif } -int main(void) -{ +int main(int argc, char *argv[]) { + memset(&process_state, 0, sizeof(process_state)); + printf("NeonServ v%s\n\n", NEONSERV_VERSION); - start_time = time(0); + process_state.argv = argv; + process_state.argc = argc; + process_state.run_as_daemon = 1; + strcpy(process_state.config, DEFAULT_CONF_FILE); + strcpy(process_state.pidfile, DEFAULT_PID_FILE); - #ifdef WIN32 - int res; - WSADATA wsadata; - // Start Windows Sockets. - res = WSAStartup(MAKEWORD(2, 0), &wsadata); - if (res) - { - perror("Unable to start Windows Sockets"); - return 0; + //parse argv + main_parse_arguments(); + + //initialize memory debugger BEFORE allocating memory + #ifdef ENABLE_MEMORY_DEBUG + initMemoryDebug(); + #endif + + //initialize mutex debugger BEFORE using any mutexes + #ifdef ENABLE_MUTEX_DEBUG + initMutexDebug(); + #endif + + //deny root startup + #ifndef WIN32 + if(geteuid() == 0 || getuid() == 0) { + fprintf(stderr, "NeonServ may not be run with super user privileges.\n"); + exit(0); } #endif - if(!load_mysql_config()) return 0; + //load configuration + int errid; + if((errid = reload_configuration())) { + fprintf(stderr, "Unable to load configuration file `%s`. (errid: %d)\n", process_state.config, errid); + exit(0); + } - queue_init(); - init_lang(); - init_parser(); - init_UserNode(); - init_ChanNode(); - init_ModeNode(); - init_bind(); - init_modcmd(); - init_handleinfohandler(); - init_tools(); - register_commands(); - init_bots(); - init_DBHelper(); + //check mysql configuration + if(!reload_mysql()) { + fprintf(stderr, "Unable to load MySQL configuration.\n"); + exit(0); + } - load_languages(); + //check module configuration + char **modulelist = get_all_fieldnames("modules"); + if(!modulelist || !modulelist[0]) { + fprintf(stderr, "Unable to load Module configuration.\n"); + exit(0); + } + free(modulelist); - time_t socket_wait; - while(1) { - socket_wait = time(0) + SOCKET_SELECT_TIME; - do { - socket_loop(SOCKET_SELECT_TIME); - } while(time(0) < socket_wait); - timeq_tick(); - loop_bots(); - clearTempUsers(); - destroyEvents(); - queue_loop(); - } -} - -int stricmp (const char *s1, const char *s2) -{ - if (s1 == NULL) return s2 == NULL ? 0 : -(*s2); - if (s2 == NULL) return *s1; - char c1, c2; - while ((c1 = tolower (*s1)) == (c2 = tolower (*s2))) - { - if (*s1 == '\0') break; - ++s1; ++s2; - } - return c1 - c2; -} - -int stricmplen (const char *s1, const char *s2, int len) -{ - if (s1 == NULL) return s2 == NULL ? 0 : -(*s2); - if (s2 == NULL) return *s1; - char c1, c2; - int i = 0; - while ((c1 = tolower (*s1)) == (c2 = tolower (*s2))) - { - i++; - if (*s1 == '\0') break; - ++s1; ++s2; - if(i == len) break; - } - return c1 - c2; + #if HAVE_THREADS + THREAD_MUTEX_INIT(cache_sync); + THREAD_MUTEX_INIT(whohandler_sync); + THREAD_MUTEX_INIT(whohandler_mass_sync); + #endif + + //connect to mysql and check if it's the frst bot startup + init_mysql(); + check_firstrun(); + + //deamonize if wanted + if(process_state.run_as_daemon) + main_daemonize(); + + //set signal handlers + signal(SIGABRT, sighandler); + signal(SIGFPE, sighandler); + signal(SIGILL, sighandler); + signal(SIGINT, sighandler); + signal(SIGSEGV, sighandler); + signal(SIGTERM, sighandler); + + //set start time and initialize other code parts + process_state.running = 1; + process_state.start_time = time(0); + initialize_subsystems(); + + //start timers + timeq_add(CLEAR_CACHE_INTERVAL, 0, clear_cache, NULL); + timeq_add(90, 0, main_checkauths, NULL); + + //start worker threads + main_start_threads(); //BLOCKING + + //shutdown sequence... + shutdown_subsystems(); + if(process_state.restart) + main_restart_process(); //terminates the current process on success + + //eop (end of program :P) + //trust me, thats the end! + exit(0); +} + +/* BOT INFORMATION */ +time_t getStartTime() { + return process_state.start_time; +} + +/* BOT CONTROL */ +void restart_bot(int crash) { + if(crash) { + main_daemon_exit(); + main_restart_process(); + } else { + process_state.restart = 1; + process_state.running = 0; + } +} + +void stop_bot() { + process_state.running = 0; +} + +void reload_config() { + reload_configuration(); +} + +/* TIMER FUNCTIONS */ + +static TIMEQ_CALLBACK(clear_cache) { + timeq_add(CLEAR_CACHE_INTERVAL, 0, clear_cache, NULL); + clearTempUsers(); + destroyEvents(); + mysql_free(); +} + +static AUTHLOOKUP_CALLBACK(main_checkauths_callback) { + //check if registered is still valid + MYSQL_RES *res; + MYSQL_ROW row; + printf_mysql_query("SELECT `user_id`, `user_registered` FROM `users` WHERE `user_user` = '%s'", escape_string(auth)); + res = mysql_use(); + if ((row = mysql_fetch_row(res)) != NULL) { + int diff = registered - atoi(row[1]); + if(diff < 0) + diff *= -1; + if(!exists || (strcmp(row[1], "0") && diff > 86400)) { + //User is no longer valid! Delete it... + deleteUser(atoi(row[0])); + char *alertchan = get_string_field("General.CheckAuths.alertchan"); + if(alertchan) { + char reason[MAXLEN]; + if(!exists) { + strcpy(reason, "USER_NOT_EXISTS"); + } else { + sprintf(reason, "USER_REGISTERED_MISSMATCH: %lu, expected %d (diff: %d)", (unsigned long) registered, atoi(row[1]), diff); + } + struct ChanNode *alertchan_chan = getChanByName(alertchan); + struct ClientSocket *alertclient; + if(alertchan_chan && (alertclient = getChannelBot(alertchan_chan, 0)) != NULL) { + putsock(alertclient, "PRIVMSG %s :Deleted User %s (%s)", alertchan_chan->name, auth, reason); + } + } + } else if(exists && !strcmp(row[1], "0")) { + printf_mysql_query("UPDATE `users` SET `user_registered` = '%lu', `user_lastcheck` = UNIX_TIMESTAMP() WHERE `user_id` = '%s'", (unsigned long) registered, row[0]); + } else { + printf_mysql_query("UPDATE `users` SET `user_lastcheck` = UNIX_TIMESTAMP() WHERE `user_id` = '%s'", row[0]); + } + } +} + +static TIMEQ_CALLBACK(main_checkauths) { + int next_call = 600; + if(get_int_field("General.CheckAuths.enabled")) { + int check_start_time = get_int_field("General.CheckAuths.start_time") * 3600; + int duration = get_int_field("General.CheckAuths.duration") * 60; + int now = getCurrentSecondsOfDay(); + if(now < check_start_time && check_start_time+duration >= 86400) { + check_start_time -= 86400; + } + if(now >= check_start_time && now < (check_start_time + duration)) { + next_call = get_int_field("General.CheckAuths.interval"); + //get the "longest-unchecked-user" + MYSQL_RES *res; + MYSQL_ROW row; + int lastcheck; + time_t unixtime = time(0); + int min_unckecked = get_int_field("General.CheckAuths.min_unckecked"); + printf_mysql_query("SELECT `user_user`, `user_lastcheck` FROM `users` ORDER BY `user_lastcheck` ASC LIMIT 1"); + res = mysql_use(); + if ((row = mysql_fetch_row(res)) != NULL) { + lastcheck = atoi(row[1]); + if(!lastcheck || unixtime - lastcheck >= min_unckecked) { + lookup_authname(row[0], 0, main_checkauths_callback, NULL); + } else + next_call = 300; + } + } else { + int pending; + if(now > check_start_time) + pending = 86400 - now + check_start_time; + else + pending = check_start_time - now; + if(pending < 600) + next_call = pending; + } + + } + timeq_add(next_call, 0, main_checkauths, NULL); +} + +/* INSTALLATION SCRIPT */ + +static void check_firstrun() { + MYSQL_RES *res; + MYSQL_ROW row; + printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_access` = 1000 LIMIT 1"); + res = mysql_use(); + if (mysql_fetch_row(res) == NULL) { + //first run! + printf("No superuser found...\n"); + check_firstrun_admin: + printf("AuthServ account name of admin user: "); + char *ptr; + char adminuser[31]; + ptr = fgets(adminuser, 30, stdin); + for(ptr = adminuser; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; } + if(strlen(adminuser) < 2) goto check_firstrun_admin; + printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(adminuser)); + res = mysql_use(); + if ((row = mysql_fetch_row(res)) != NULL) + printf_mysql_query("UPDATE `users` SET `user_access` = 1000 WHERE `user_id` = '%s'", row[0]); + else + printf_mysql_query("INSERT INTO `users` (`user_user`, `user_access`) VALUES ('%s', 1000)", escape_string(adminuser)); + } + printf_mysql_query("SELECT `id` FROM `bots` WHERE `active` = 1 LIMIT 1"); + res = mysql_use(); + if (mysql_fetch_row(res) == NULL) { + //no bot active + printf("No active bot found...\n\n"); + printf("ADD NEW BOT\n"); + char *ptr; + char bot_nick[31]; + check_firstrun_bot_nick: + printf("Nick: "); + ptr = fgets(bot_nick, 30, stdin); + for(ptr = bot_nick; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; } + if(strlen(bot_nick) < 2) goto check_firstrun_bot_nick; + char bot_ident[16]; + check_firstrun_bot_ident: + printf("Ident: "); + ptr = fgets(bot_ident, 15, stdin); + for(ptr = bot_ident; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; } + if(strlen(bot_ident) < 2) goto check_firstrun_bot_ident; + char bot_realname[101]; + check_firstrun_bot_realname: + printf("Realname: "); + ptr = fgets(bot_realname, 100, stdin); + for(ptr = bot_realname; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; } + if(strlen(bot_realname) < 2) goto check_firstrun_bot_realname; + char bot_server[101]; + check_firstrun_bot_server: + printf("Server: [irc.onlinegamesnet.net] "); + ptr = fgets(bot_server, 100, stdin); + for(ptr = bot_server; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; } + if(*bot_server && strlen(bot_nick) < 5) goto check_firstrun_bot_server; + if(!*bot_server) + strcpy(bot_server, "irc.onlinegamesnet.net"); + int bot_port; + char bot_port_buf[7]; + printf("Port: [6667] "); + ptr = fgets(bot_port_buf, 6, stdin); + for(ptr = bot_port_buf; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; } + if(!*bot_port_buf) + bot_port = 6667; + else + bot_port = atoi(bot_port_buf); + int bot_ssl; + char bot_ssl_buf[5]; + check_firstrun_bot_ssl: + printf("SSL: [y/N] "); + ptr = fgets(bot_ssl_buf, 4, stdin); + for(ptr = bot_ssl_buf; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; } + if(!*bot_ssl_buf || tolower(*bot_ssl_buf) == 'n') + bot_ssl = 0; + else if(tolower(*bot_ssl_buf) == 'y') + bot_ssl = 1; + else + goto check_firstrun_bot_ssl; + char bot_pass[101]; + printf("Server Password: [] "); + ptr = fgets(bot_pass, 100, stdin); + for(ptr = bot_pass; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; } + int bot_maxchan; + char bot_maxchan_buf[5]; + printf("MaxChannel: [20] "); + ptr = fgets(bot_maxchan_buf, 5, stdin); + for(ptr = bot_maxchan_buf; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; } + if(*bot_maxchan_buf) + bot_maxchan = atoi(bot_maxchan_buf); + else + bot_maxchan = 20; + int bot_queue; + char bot_queue_buf[5]; + check_firstrun_bot_queue: + printf("Queue (prevents excess floods): [Y/n] "); + ptr = fgets(bot_queue_buf, 4, stdin); + for(ptr = bot_queue_buf; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; } + if(!*bot_queue_buf || tolower(*bot_queue_buf) == 'y') + bot_queue = 1; + else if(tolower(*bot_queue_buf) == 'n') + bot_queue = 0; + else + goto check_firstrun_bot_queue; + 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); + } }