/* Initalization and related routines. * * IRC Services is copyright (c) 1996-2007 Andrew Church. * E-mail: * Parts written by Andrew Kempe and others. * This program is free but copyrighted software; see the file COPYING for * details. */ #include "services.h" #include "conffile.h" #include "messages.h" #include "modules.h" #include "language.h" #include "version.h" #if HAVE_SETGRENT # include #endif #if HAVE_UMASK # include /* for umask() on some systems */ #endif #if HAVE_GETSETRLIMIT # include #endif /*************************************************************************/ /* Callbacks used in this file: */ static int cb_introduce_user = -1; static int cb_cmdline = -1; /*************************************************************************/ /************************* Configuration options *************************/ /*************************************************************************/ /* Configurable variables: */ char **LoadModules; int LoadModules_count; char * RemoteServer; int32 RemotePort; char * RemotePassword; char * LocalHost; int32 LocalPort; char * ServerName; char * ServerDesc; char * ServiceUser; char * ServiceHost; char * LogFilename; char * PIDFilename; char * MOTDFilename; char * LockFilename; int16 DefTimeZone; int NoBouncyModes; int NoSplitRecovery; int StrictPasswords; int32 BadPassLimit; time_t BadPassTimeout; int32 BadPassWarning; time_t UpdateTimeout; time_t WarningTimeout; int32 ReadTimeout; int32 TimeoutCheck; time_t PingFrequency; int32 MergeChannelModes; int32 TotalNetBufferSize; int32 NetBufferSize; int32 NetBufferLimitInactive; int32 NetBufferLimitIgnore; char * GuestNickPrefix; int LogMaxUsers; int EnableGetpass; int WallGetpass; int WallSetpass; /* Routines to handle special directives: */ static int do_DefTimeZone(const char *filename, int linenum, char *param); static int do_LoadModule(const char *filename, int linenum, char *param); static int do_RunGroup(const char *filename, int linenum, char *param); static int do_ServiceUser(const char *filename, int linenum, char *param); static int do_Umask(const char *filename, int linenum, char *param); /*************************************************************************/ /* List of directives for ircservices.conf (main configuration file): */ static ConfigDirective main_directives[] = { { "BadPassLimit", { { CD_POSINT, 0, &BadPassLimit } } }, { "BadPassTimeout", { { CD_TIME, 0, &BadPassTimeout } } }, { "BadPassWarning", { { CD_POSINT, 0, &BadPassWarning } } }, { "DefTimeZone", { { CD_FUNC, 0, do_DefTimeZone } } }, { "EnableGetpass", { { CD_SET, 0, &EnableGetpass } } }, { "ExpireTimeout", { { CD_DEPRECATED, 0, NULL } } }, { "GuestNickPrefix", { { CD_STRING, CF_DIRREQ, &GuestNickPrefix } } }, { "LoadModule", { { CD_FUNC, 0, do_LoadModule } } }, { "LocalAddress", { { CD_STRING, 0, &LocalHost }, { CD_PORT, CF_OPTIONAL, &LocalPort } } }, { "LockFilename", { { CD_STRING, CF_DIRREQ, &LockFilename } } }, { "LogFilename", { { CD_STRING, CF_DIRREQ, &LogFilename } } }, { "LogMaxUsers", { { CD_SET, 0, &LogMaxUsers } } }, { "MergeChannelModes",{ { CD_TIMEMSEC, 0, &MergeChannelModes } } }, { "MOTDFilename", { { CD_STRING, CF_DIRREQ, &MOTDFilename } } }, { "NetBufferLimit", { { CD_POSINT, 0, &NetBufferLimitInactive }, { CD_POSINT, CF_OPTIONAL, &NetBufferLimitIgnore}}}, { "NetBufferSize", { { CD_POSINT, 0, &TotalNetBufferSize }, { CD_POSINT, CF_OPTIONAL, &NetBufferSize } } }, { "NoBouncyModes", { { CD_SET, 0, &NoBouncyModes } } }, { "NoSplitRecovery", { { CD_SET, 0, &NoSplitRecovery } } }, { "PIDFilename", { { CD_STRING, 0, &PIDFilename } } }, { "PingFrequency", { { CD_TIME, 0, &PingFrequency } } }, { "ReadTimeout", { { CD_TIMEMSEC, 0, &ReadTimeout } } }, { "RemoteServer", { { CD_STRING, CF_DIRREQ, &RemoteServer }, { CD_PORT, 0, &RemotePort }, { CD_STRING, 0, &RemotePassword } } }, { "RunGroup", { { CD_FUNC, 0, do_RunGroup } } }, { "ServerDesc", { { CD_STRING, CF_DIRREQ, &ServerDesc } } }, { "ServerName", { { CD_STRING, CF_DIRREQ, &ServerName } } }, { "ServiceUser", { { CD_FUNC, CF_DIRREQ, do_ServiceUser } } }, { "StrictPasswords", { { CD_SET, 0, &StrictPasswords } } }, { "TimeoutCheck", { { CD_TIMEMSEC, CF_DIRREQ, &TimeoutCheck } } }, { "Umask", { { CD_FUNC, 0, do_Umask } } }, { "UpdateTimeout", { { CD_TIME, CF_DIRREQ, &UpdateTimeout } } }, { "WallGetpass", { { CD_SET, 0, &WallGetpass } } }, { "WallSetpass", { { CD_SET, 0, &WallSetpass } } }, { "WarningTimeout", { { CD_TIME, CF_DIRREQ, &WarningTimeout } } }, { NULL } }; /*************************************************************************/ /* read_config(): Read the main configuration file. If an error occurs * while reading the file or a required directive is not * found, print and log an appropriate error message and * return 0; otherwise, return 1. */ static int read_config(void) { int retval = 1; if (!configure(NULL, main_directives, CONFIGURE_READ | CONFIGURE_SET)) return 0; if (TotalNetBufferSize) { if (TotalNetBufferSize < NET_MIN_BUFSIZE*2) { config_error(IRCSERVICES_CONF, 0, "Buffer size limit for NetBufferSize must be at" " least %d", NET_MIN_BUFSIZE*2); retval = 0; } else { /* Make sure it's a multiple of NET_MIN_BUFSIZE */ TotalNetBufferSize /= NET_MIN_BUFSIZE; TotalNetBufferSize *= NET_MIN_BUFSIZE; if (NetBufferSize) { if (NetBufferSize < NET_MIN_BUFSIZE*2) { config_error(IRCSERVICES_CONF, 0, "Per-connection buffer size limit for" " NetBufferSize must be at least %d", NET_MIN_BUFSIZE*2); retval = 0; } else if (NetBufferSize > TotalNetBufferSize) { config_error(IRCSERVICES_CONF, 0, "Per-connection buffer size limit for" " NetBufferSize must be no more than total" " limit"); retval = 0; } else { NetBufferSize /= NET_MIN_BUFSIZE; NetBufferSize *= NET_MIN_BUFSIZE; } } } } /* if (TotalNetBufferSize) */ if (NetBufferLimitInactive) { if (!TotalNetBufferSize) { NetBufferLimitInactive = NetBufferLimitIgnore = 0; } else { if (NetBufferLimitInactive > 99 || NetBufferLimitIgnore > 99) { config_error(IRCSERVICES_CONF, 0, "Thresholds for NetBufferLimit must be between" " 1 and 99 inclusive"); retval = 0; } if (NetBufferLimitIgnore && NetBufferLimitIgnore < NetBufferLimitInactive ) { config_error(IRCSERVICES_CONF, 0, "Ignore threshold for NetBufferLimit must be" " greater than or equal to inactive threshold"); retval = 0; } } } return retval; } /*************************************************************************/ /*************************************************************************/ /* Configuration directive callback functions: */ /*************************************************************************/ #define TZ_MAXLEN 16 static int do_DefTimeZone(const char *filename, int linenum, char *param) { static const char *origTZ = NULL; static char newTZ[TZ_MAXLEN+1]; static char tzbuf[TZ_MAXLEN+4]; /* for setting TZ */ if (!filename) { switch (linenum) { case CDFUNC_INIT: /* Prepare for reading config file */ *newTZ = 0; if (!origTZ) { /* Obtain current value of TZ environment variable (once * only, at start of program), and truncate to TZ_MAXLEN */ origTZ = getenv("TZ"); if (!origTZ) origTZ = ""; if (strlen(origTZ) > TZ_MAXLEN) { static char new_origTZ[TZ_MAXLEN+1]; memcpy(new_origTZ, origTZ, TZ_MAXLEN); new_origTZ[TZ_MAXLEN] = 0; origTZ = new_origTZ; } } break; case CDFUNC_SET: /* Copy data to config variables */ if (!origTZ) { log("BUG: origTZ not set in do_DefTimeZone/CDFUNC_SET"); break; } snprintf(tzbuf, sizeof(tzbuf), "TZ=%s", *newTZ ? newTZ : origTZ); if (putenv(tzbuf) < 0) { log("Warning: putenv(%s) failed, time zone may be incorrect", tzbuf); } break; case CDFUNC_DECONFIG: /* Reset to initial values */ if (!origTZ) { log("BUG: origTZ not set in do_DefTimeZone/CDFUNC_DECONFIG"); break; } snprintf(tzbuf, sizeof(tzbuf), "TZ=%s", origTZ); if (putenv(tzbuf) < 0) { log("Warning: putenv(%s) failed, time zone may be incorrect", tzbuf); } break; } /* switch (linenum) */ } else { /* filename != NULL, process parameter */ if (strlen(param) > TZ_MAXLEN) { config_error(filename, linenum, "DefTimeZone parameter must not" " be longer than %d characters", TZ_MAXLEN); return 0; } else { strscpy(newTZ, param, sizeof(newTZ)); } } return 1; } /*************************************************************************/ static int do_LoadModule(const char *filename, int linenum, char *param) { static char **new_LoadModules = NULL; static int new_LoadModules_count = 0; int i; if (!filename) { switch (linenum) { case CDFUNC_INIT: /* Prepare for reading config file: clear out "new" array */ ARRAY_FOREACH (i, new_LoadModules) free(new_LoadModules[i]); free(new_LoadModules); new_LoadModules = NULL; new_LoadModules_count = 0; break; case CDFUNC_SET: /* Copy data to config variables */ ARRAY_FOREACH (i, LoadModules) free(LoadModules[i]); free(LoadModules); LoadModules = new_LoadModules; LoadModules_count = new_LoadModules_count; new_LoadModules = NULL; new_LoadModules_count = 0; break; case CDFUNC_DECONFIG: /* Clear out config variables */ ARRAY_FOREACH (i, LoadModules) free(LoadModules[i]); free(LoadModules); LoadModules = NULL; LoadModules_count = 0; break; } return 1; } /* if (!filename) */ /* We can't use ARRAY_EXTEND because SIGUSR1 (for srealloc()) may not * be ready yet */ if (new_LoadModules_count+1 < new_LoadModules_count) { config_error(filename, linenum, "LoadModule: too many modules!"); return 0; } new_LoadModules = realloc(new_LoadModules, sizeof(char *) * (new_LoadModules_count+1)); param = strdup(param); if (!new_LoadModules || !param) { config_error(filename, linenum, "LoadModule: out of memory!"); return 0; } new_LoadModules[new_LoadModules_count++] = param; return 1; } /*************************************************************************/ static int do_RunGroup(const char *filename, int linenum, char *param) { #ifndef SIZEOF_GID_T config_error(filename, linenum, "RunGroup: groups not supported on this system"); return 0; #else static gid_t groupnum = -1; long tmp; if (filename) { if (*param == '=') { if (!param[1]) { config_error(filename, linenum, "RunGroup: group number required after `='"); return 0; } errno = 0; tmp = strtol(param+1, ¶m, 0); # if SIZEOF_GID_T >= 4 if (errno == ERANGE) # else if (tmp < -32768 || tmp > 65535) # endif { config_error(filename, linenum, "RunGroup: group number out of range (0..%ld)", # if SIZEOF_GID_T >= 4 0x7FFFFFFFL # else 65535L # endif ); return 0; } groupnum = tmp; } else { /* *param != '=' */ # if !HAVE_SETGRENT config_error(filename, linenum, "RunGroup: group names not supported on this system"); return 0; # else struct group *gr; setgrent(); while ((gr = getgrent()) != NULL) { if (strcmp(gr->gr_name, param) == 0) break; } endgrent(); if (!gr) { config_error(filename, linenum, "RunGroup: unknown group `%s'", param); return 0; } groupnum = gr->gr_gid; # endif } } else { /* !filename */ # if HAVE_SETREGID if (setregid(groupnum, groupnum) < 0) { # else if (setegid(groupnum) < 0 || setgid(groupnum) < 0) { # endif config_error(filename, linenum, "RunGroup: unable to set group: %s", strerror(errno)); return 0; } } /* if (filename) */ return 1; #endif /* have gid_t? */ } /*************************************************************************/ static int do_ServiceUser(const char *filename, int linenum, char *param) { char *s; static char *new_ServiceUser = NULL, *new_ServiceHost = NULL; if (filename) { /* Make sure the string has an @ and there's at least one character * on each side */ s = strchr(param, '@'); if (!s || s == param || s[1] == 0) { config_error(filename, linenum, "`user@host' string required for ServiceUser"); return 0; } /* Store user and hostname parts (after freeing any old ones) */ *s++ = 0; free(new_ServiceUser); free(new_ServiceHost); new_ServiceUser = strdup(param); new_ServiceHost = strdup(s); if (!new_ServiceUser || !new_ServiceHost) { free(new_ServiceUser); /* still alloc'ed if ServiceHost failed */ new_ServiceUser = NULL; config_error(filename, linenum, "Out of memory"); return 0; } } else if (linenum == CDFUNC_SET) { /* Copy new values to config variables and clear */ if (new_ServiceUser && new_ServiceHost) { /* paranoia */ free(ServiceUser); free(ServiceHost); ServiceUser = new_ServiceUser; ServiceHost = new_ServiceHost; } else { free(new_ServiceUser); free(new_ServiceHost); } new_ServiceUser = new_ServiceHost = NULL; } else if (linenum == CDFUNC_DECONFIG) { /* Reset to defaults */ free(ServiceUser); free(ServiceHost); ServiceUser = NULL; ServiceHost = NULL; } return 1; } /*************************************************************************/ static int do_Umask(const char *filename, int linenum, char *param) { #if !HAVE_UMASK config_error(filename, linenum, "Umask is not supported on this system"); return 0; #else char *s; static int umask_val = -1; if (filename) { umask_val = strtol(param, &s, 8); if (*s || umask_val < 0 || umask_val > 0777) { config_error(filename, linenum, "Expected an octal value between 000 and 777"); return 0; } } else { if (umask_val >= 0) umask(umask_val); umask_val = -1; } return 1; #endif /* HAVE_UMASK */ } /*************************************************************************/ /*********************** Introducing pseudoclients ***********************/ /*************************************************************************/ /* If `user' is the name of a Services pseudo-client, send a NICK command * for that given pseudo-client. If `user' is NULL, send NICK commands for * all the pseudo-clients. Return 1 if we sent a NICK command, else 0. */ #define NICK(nick,name,modes) do { \ snprintf(modebuf, sizeof(modebuf), "%s%s", (modes), pseudoclient_modes); \ send_nick((nick), ServiceUser, ServiceHost, ServerName, (name), modebuf); \ retval = 1; \ } while (0) int introduce_user(const char *user) { int retval; retval = call_callback_1(NULL, cb_introduce_user, user); if (user == NULL) { if (retval > 0) log("introduce_user(): callback returned nonzero for user==NULL"); retval = 1; } /* Watch out for infinite loops... */ if (retval) { #define LTSIZE 20 static int lasttimes[LTSIZE]; if (lasttimes[0] >= time(NULL)-3) fatal("introduce_user() loop detected"); memmove(lasttimes, lasttimes+1, sizeof(lasttimes)-sizeof(*lasttimes)); lasttimes[LTSIZE-1] = time(NULL); #undef LTSIZE } return retval; } #undef NICK /*************************************************************************/ /************************* Command-line parsing **************************/ /*************************************************************************/ /* Parse command-line options. If `call_modules' is zero, parse main * options and ignore unrecognized ones; if nonzero, ignore main options, * call "command line" callback to check module options, and fail on * unrecognized ones. Return 0 if all went well, -1 for an error with an * option, or 1 if Services should exit successfully. Calls exit(0) if * "-help", "--help", or "-h" is present. */ static int parse_options(int ac, char **av, int call_modules) { int i; char *s, *t; if (!call_modules) /* only initialize it once */ debug = 0; for (i = 1; i < ac; i++) { s = av[i]; if (*s == '-') { s++; if (strncmp(s, "dir", 3) == 0 && (!s[3] || s[3] == '=')) { if (!call_modules) { if (!s[3]) { fprintf(stderr, "-dir requires a parameter\n"); return -1; } services_dir = s+4; } } else if (strncmp(s, "remote", 6) == 0 && (!s[6] || s[6]=='=')) { if (!call_modules) { if (!s[6]) { fprintf(stderr, "-remote requires hostname[:port]\n"); return -1; } s += 7; t = strchr(s, ':'); if (t) { *t = 0; if (atoi(t+1) > 0) RemotePort = atoi(t+1); else { fprintf(stderr, "-remote: port number must be a positive integer. Using default."); return -1; } } free(RemoteServer); RemoteServer = strdup(s); if (!RemoteServer) { fprintf(stderr, "Out of memory\n"); exit(-1); } if (t) /* put the colon back for next time around */ *t = ':'; } } else if (strncmp(s, "log", 3) == 0 && (!s[3] || s[3] == '=')) { if (!call_modules) { if (!s[3]) { fprintf(stderr, "-log requires a parameter\n"); return -1; } free(LogFilename); LogFilename = s+4; } } else if (strcmp(s, "debug") == 0) { if (!call_modules) debug++; } else if (strcmp(s, "readonly") == 0) { if (!call_modules) readonly = 1; } else if (strcmp(s, "nofork") == 0) { if (!call_modules) nofork = 1; } else if (strcmp(s, "noexpire") == 0) { if (!call_modules) noexpire = 1; } else if (strcmp(s, "noakill") == 0) { if (!call_modules) noakill = 1; } else if (strcmp(s, "forceload") == 0) { if (!call_modules) forceload = 1; } else if (strcmp(s, "h") == 0 || strcmp(s, "help") == 0 || strcmp(s, "-help") == 0) { fputs( "The following options are recognized:\n" " -dir=directory Directory containing Services' data files\n" " (e.g. /usr/local/lib/ircservices)\n" " -remote=server[:port] Remote server to connect to\n" " -log=filename Services log filename (e.g. services.log)\n" " -debug Enable debugging mode--more info sent to log\n" " (give option more times for more info)\n" " -readonly Enable read-only mode--no changes to\n" " databases allowed, .db files and log\n" " not written\n" " -nofork Do not fork after startup; log messages will\n" " be written to terminal (as well as to\n" " the log file if not in read-only mode)\n" " -noexpire Prevents all expirations (nicknames, channels,\n" " akills, session limit exceptions, etc.)\n" " -noakill Disables autokill checking\n" " -forceload Try to load as much of the databases as\n" " possible, even if errors are encountered\n" "Other options may be available depending on loaded modules; see the manual\n" "for details.\n" , stdout); exit(0); } else if (call_modules) { int res; t = strchr(s, '='); if (t) *t++ = 0; res = call_callback_2(NULL, cb_cmdline, s, t); switch (res) { case 0: fprintf(stderr, "Unknown option -%s. Use \"-help\" for" " help.\n", s); return -1; case 1: break; case 2: return -1; case 3: return 1; default: log("init: bad return value (%d) from command line" " callback for `-%s%s%s'", res, s, t ? "=" : "", t); return -1; } } } else { fprintf(stderr, "Non-option arguments not allowed\n"); return -1; } } return 0; } /*************************************************************************/ /*************************** PID file handling ***************************/ /*************************************************************************/ /* Remove our PID file. Done at exit. */ static void remove_pidfile(void) { remove(PIDFilename); } /*************************************************************************/ /* Create our PID file and write the PID to it. Returns nonzero on * success, zero on failure. */ static int write_pidfile(void) { FILE *pidfile; pidfile = fopen(PIDFilename, "w"); if (!pidfile) return 0; fprintf(pidfile, "%d\n", (int)getpid()); fclose(pidfile); atexit(remove_pidfile); return 1; } /*************************************************************************/ /********************** Main initialization routine **********************/ /*************************************************************************/ /* Overall initialization routine. Returns 0 on success, -1 on failure. * Never returns failure after forking / closing standard file descriptors. */ int init(int ac, char **av) { int i; int openlog_failed = 0, openlog_errno = 0; int started_from_term = isatty(0) && isatty(1) && isatty(2); #ifdef MEMCHECKS /* Initialize memory log, to keep track of allocations. */ open_memory_log(); /* Account for runtime memory. */ init_memory(); #endif /* Parse command-line options; exit if an error occurs. */ if (parse_options(ac, av, 0) < 0) return -1; /* Chdir to Services data directory. */ if (chdir(services_dir) < 0) { fprintf(stderr, "chdir(%s): %s\n", services_dir, strerror(errno)); return -1; } /* Read configuration file; exit if there are problems. */ if (!read_config()) return -1; /* Re-parse command-line options (to override configuration file). */ parse_options(ac, av, 0); /* Open logfile, and complain if we couldn't. */ if (!open_log()) { openlog_errno = errno; if (started_from_term) { fprintf(stderr, "Warning: unable to open log file %s: %s\n", LogFilename, strerror(errno)); } else { openlog_failed = 1; } } /* Announce ourselves to the logfile. */ if (debug || readonly || noexpire) { log("IRC Services %s starting up (options:%s%s%s)", version_number, debug ? " debug" : "", readonly ? " readonly" : "", noexpire ? " noexpire" : ""); } else { log("IRC Services %s starting up", version_number); } #ifdef FORCED_GCC_2_96 log("Warning: This program was compiled with GCC 2.96, which is" " unsupported. Please read the documentation before reporting any" " problems."); #endif start_time = time(NULL); /* If in read-only mode, close the logfile again. */ if (readonly) close_log(); /* If DUMPCORE is defined and the OS supports get/setrlimit(), then * attempt to remove, or at least raise to maximum, the core file size * limit. */ #if defined(DUMPCORE) && HAVE_GETSETRLIMIT { struct rlimit rl = {RLIM_INFINITY,RLIM_INFINITY}; if (setrlimit(RLIMIT_CORE, &rl) < 0) { log_perror("setrlimit(RLIMIT_CORE, RLIM_INFINITY)"); if ((i = getrlimit(RLIMIT_CORE, &rl)) < 0 || rl.rlim_cur >= rl.rlim_max ) { if (i < 0) log_perror("getrlimit(RLIMIT_CORE)"); log("Unable to set core file size limit; core files %s.", i < 0 || rl.rlim_cur == 0 ? "will not be generated" : "may be truncated"); } else { rl.rlim_cur = rl.rlim_max; if (setrlimit(RLIMIT_CORE, &rl) < 0) { log_perror("setrlimit(RLIMIT_CORE, %ld)", (long)rl.rlim_cur); log("Unable to set core file size limit; core files may" " be truncated."); } else { log("Core file size limited to %ldkB; core files may be" " truncated.", (long)(rl.rlim_cur<1024 ? 1 : rl.rlim_cur/1024)); } } } /* if (setrlimit(...) < 0) */ } /* setrlimit() block */ #endif /* Initialize pseudo-random number generator. */ srand(time(NULL) ^ getppid() ^ getpid()<<16); /* Initialize module system. This should be called before any * callbacks are registered. */ if (!modules_init(ac,av)) return -1; /* Register our (and main.c's) callbacks. */ cb_cmdline = register_callback(NULL, "command line"); cb_introduce_user = register_callback(NULL, "introduce_user"); cb_connect = register_callback(NULL, "connect"); cb_save_data = register_callback(NULL, "save data"); cb_save_complete = register_callback(NULL, "save data complete"); if (cb_cmdline < 0 || cb_introduce_user < 0 || cb_connect < 0 || cb_save_data < 0 || cb_save_complete < 0 ) { log("init(): Unable to register callbacks"); return -1; } /* Call other initialization routines. These are mainly (right now * only) for adding callbacks. */ if (!user_init(ac,av) || !channel_init(ac,av) || !server_init(ac,av) || !process_init(ac,av) || !messages_init(ac,av) || !actions_init(ac,av) || !send_init(ac,av) ) { return -1; } /* Initialize multi-language support. */ if (!lang_init()) return -1; if (debug) log("debug: Loaded languages"); /* Load modules. */ ARRAY_FOREACH (i, LoadModules) { if (!load_module(LoadModules[i])) { log("Error loading modules, aborting"); return -1; } } if (debug) log("debug: Loaded modules"); /* Check command-line arguments in modules, and exit if a module directs * us to. */ i = parse_options(ac, av, 1); if (i != 0) { if (i < 0) { cleanup(); return -1; } else { call_callback(NULL, cb_save_data); cleanup(); exit(0); } } /* Make sure a protocol module was loaded. */ if (protocol_features & PF_UNSET) { fprintf(stderr, "No protocol module has been loaded! Make sure to include a LoadModule\n" "directive for the appropriate module in the `%s' file.\n", IRCSERVICES_CONF); cleanup(); return -1; } /* So far so good; let the user know everything is okay. */ if (!nofork) fprintf(stderr, "Initialization successful, starting IRC Services.\n"); /* Detach ourselves if requested. */ if (!nofork) { if ((i = fork()) < 0) { perror("fork()"); return -1; } else if (i != 0) { #ifdef MEMCHECKS /* Avoid a bogus "XXX bytes leaked on exit" message for the * parent. */ uninit_memory(); #endif exit(0); } if (started_from_term) { close(0); close(1); close(2); } if (setpgid(0, 0) < 0) { perror("setpgid()"); return -1; } } /* * Everything from here down needs to be done in the child process * (when forking). */ /* Write our PID to the PID file. */ if (!write_pidfile()) log_perror("Warning: cannot write to PID file %s", PIDFilename); /* Set up signal handlers. */ init_signals(); /* Connect to the remote server. */ servsock = sock_new(); if (!servsock) fatal_perror("Can't create server socket"); sock_setcb(servsock, SCB_CONNECT, connect_callback); sock_setcb(servsock, SCB_DISCONNECT, disconnect_callback); if (conn(servsock, RemoteServer, RemotePort, LocalHost, LocalPort) < 0) fatal_perror("Can't connect to server (%s:%d)", RemoteServer, RemotePort); if (debug) log("Initiated connection to %s:%d", RemoteServer, RemotePort); /* Return success (connect_callback() will handle the rest). */ return 0; } /*************************************************************************/ /**************************** Reconfiguration ****************************/ /*************************************************************************/ int reconfigure(void) { char *old_RemoteServer, *old_RemotePassword, *old_LocalHost; int old_RemotePort, old_LocalPort; char *old_ServerName, *old_ServerDesc, *old_ServiceUser, *old_ServiceHost; char *old_LogFilename, *old_PIDFilename; char **old_LoadModules; int old_LoadModules_count; int LoadModules_insert; /* where to insert unloadable modules */ int i, j; int retval = 1; /* First save any data that we need after re-reading the conf file */ old_RemoteServer = sstrdup(RemoteServer); old_RemotePort = RemotePort; old_RemotePassword = sstrdup(RemotePassword); old_LocalHost = LocalHost ? sstrdup(LocalHost) : NULL; old_LocalPort = LocalPort; old_ServerName = sstrdup(ServerName); old_ServerDesc = sstrdup(ServerDesc); old_ServiceUser = sstrdup(ServiceUser); old_ServiceHost = sstrdup(ServiceHost); old_LogFilename = sstrdup(LogFilename); old_PIDFilename = sstrdup(PIDFilename); old_LoadModules = LoadModules; old_LoadModules_count = LoadModules_count; /* Re-read the configuration */ if (!configure(NULL, main_directives, CONFIGURE_READ)) return 0; /* Prevent current LoadModules (now old_LoadModules) from being freed */ LoadModules = NULL; LoadModules_count = 0; /* Copy new values to configuration variables */ configure(NULL, main_directives, CONFIGURE_SET); /* Deal with configuration changes */ if (stricmp(RemoteServer, old_RemoteServer) != 0 || RemotePort != old_RemotePort || strcmp(RemotePassword, old_RemotePassword) != 0) log("warning: reconfigure: new RemoteServer value will not take" " effect until restart"); if ((!old_LocalHost && LocalHost) || (old_LocalHost && !LocalHost) || (LocalHost && stricmp(LocalHost, old_LocalHost) != 0) || LocalPort != old_LocalPort) log("warning: reconfigure: new LocalHost value will not take" " effect until restart"); if (strcmp(ServerName, old_ServerName) != 0) log("warning: reconfigure: new ServerName value will not take" " effect until restart"); if (strcmp(ServerDesc, old_ServerDesc) != 0) log("warning: reconfigure: new ServerDesc value will not take" " effect until restart"); if ((!old_ServiceUser && ServiceUser) || (!old_ServiceHost && ServiceHost) || (ServiceUser && strcmp(ServiceUser, old_ServiceUser) != 0) || (ServiceHost && strcmp(ServiceHost, old_ServiceHost) != 0)) log("warning: reconfigure: new ServiceUser value will not take" " effect until restart"); if (strcmp(LogFilename, old_LogFilename) != 0) { log("reconfigure: LogFilename changed, closing log file"); if (reopen_log()) { log("reconfigure: LogFilename changed, writing to new log file"); } else { log("warning: reconfigure: unable to open new log file `%s'," " reverting to old file `%s'", LogFilename, old_LogFilename); free(LogFilename); LogFilename = old_LogFilename; old_LogFilename = NULL; /* don't free it below */ } } if (strcmp(PIDFilename, old_PIDFilename) != 0) { if (write_pidfile()) { /* Successfully wrote the new PID file, so delete the old one */ remove(old_PIDFilename); } else { log("warning: reconfigure: unable to write new PID file `%s'," " reverting to old file `%s'", PIDFilename, old_PIDFilename); free(PIDFilename); PIDFilename = old_PIDFilename; old_PIDFilename = NULL; } } /* For modules, we need to: * - first unload any modules which don't have LoadModule lines * anymore--this has to be done in reverse order to avoid * dependency problems; * - next reconfigure any still-loaded modules (because newly-loaded * modules in the next step may depend on the new settings); * - finally load any modules which weren't loaded before but have * LoadModule lines now. */ LoadModules_insert = LoadModules_count; for (i = old_LoadModules_count - 1; i >= 0; i--) { ARRAY_SEARCH_PLAIN(LoadModules, old_LoadModules[i], strcmp, j); if (j >= LoadModules_count) { Module *mod = find_module(old_LoadModules[i]); if (!mod) { log("BUG: reconfigure: module `%s' not available", old_LoadModules[i]); retval = 0; } else if (!unload_module(mod)) { log("warning: reconfigure: module `%s' could not be unloaded", old_LoadModules[i]); ARRAY_INSERT(LoadModules, LoadModules_insert); LoadModules[LoadModules_insert] = sstrdup(old_LoadModules[i]); retval = 0; } } } if (retval && !reconfigure_modules()) { log("warning: reconfigure: module reconfiguration failed"); retval = 0; } if (retval) { ARRAY_FOREACH (i, LoadModules) { ARRAY_SEARCH_PLAIN(old_LoadModules, LoadModules[i], strcmp, j); if (j >= old_LoadModules_count) { if (!load_module(LoadModules[i])) { log("warning: reconfigure: new module `%s' could not" " be loaded", LoadModules[i]); ARRAY_REMOVE(LoadModules, i); i--; retval = 0; } } } } /* Free old configuration data and return */ free(old_RemoteServer); free(old_RemotePassword); free(old_LocalHost); free(old_ServerName); free(old_ServerDesc); free(old_ServiceUser); free(old_ServiceHost); free(old_LogFilename); free(old_PIDFilename); ARRAY_FOREACH (i, old_LoadModules) free(old_LoadModules[i]); free(old_LoadModules); return retval; } /*************************************************************************/ /******************************** Cleanup ********************************/ /*************************************************************************/ void cleanup(void) { char lognamebuf[BUFSIZE], *lognamesave; int i; if (!*quitmsg) strscpy(quitmsg, "Terminating, reason unknown", sizeof(quitmsg)); log("%s", quitmsg); set_cmode(NULL, NULL); unload_all_modules(); if (servsock) { if (sock_isconn(servsock)) { send_cmd(ServerName, "SQUIT %s :%s", ServerName, quitmsg); disconn(servsock); } sock_free(servsock); } lang_cleanup(); send_cleanup(); actions_cleanup(); messages_cleanup(); process_cleanup(); server_cleanup(); channel_cleanup(); user_cleanup(); unregister_callback(NULL, cb_save_complete); unregister_callback(NULL, cb_save_data); unregister_callback(NULL, cb_connect); unregister_callback(NULL, cb_introduce_user); unregister_callback(NULL, cb_cmdline); modules_cleanup(); /* Save a copy of LogFilename, because the log routines still need it */ strscpy(lognamebuf, LogFilename, sizeof(lognamebuf)); for (i = 0; strcmp(main_directives[i].name,"LogFilename") != 0; i++) ; lognamesave = main_directives[i].params[0].prev.ptrval; main_directives[i].params[0].prev.ptrval = lognamebuf; deconfigure(main_directives); free(lognamesave); free(RemoteServer); RemoteServer = NULL; close_log(); LogFilename = NULL; } /*************************************************************************/