/* * POP3Lite - 3lite POP3 Daemon * Copyright (C) 2000, 2001 Gergely Nagy <8@free.bsd.hu> * * This file is part of POP3Lite. * * POP3Lite is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * POP3Lite is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "main.h" #include #include #include #ifdef HAVE_GETOPT_H # include #endif #include #include #include #include #include #include "cfg.h" #include "core.h" #include "module.h" #ifdef WITH_STANDALONE_SUPPORT # include # include # include # include # include # include # include "standalone.h" int errno; unsigned long child_cnt = 0; char *pidfn = LOCALSTATEDIR "/run/pop3lite.pid"; pid_t main_pid; #endif #define LINELEN 512 /* RFC 2449 */ static const char rcsid[]="$Id: main.c,v 1.29.2.3 2001/05/30 12:38:57 algernon Exp $"; extern int opterr; static gboolean logout_timer_reset = 1; static unsigned long logout_iv; static void sig_handler ( int number ); static void sigalrm_handler ( int sig ); static void setup_signal_handlers ( void ); static void setup_sigalrm_handler ( void ); static int auto_logout_handler ( P3LControl *control ); static void main_loop_body ( GHashTable *command_table ); static void exit_hook ( void ); static void print_version ( void ); static void pop3lite_main_loop ( P3LControl *control ); /** * control: the main P3LControl struct * * This is declared global, becuase sig_handler uses it. * BTW, this is the struct through which the whole daemon is * implemented. **/ static P3LControl *control; /** * setup_sigalrm_handler: sets up SIGALRM hander * * This is used to setup/reset the SIGALRM handler * * Returns: nothing **/ static void setup_sigalrm_handler ( void ) { /* This clears the handler, just in case... */ signal ( SIGALRM, SIG_IGN ); signal ( SIGALRM, &sigalrm_handler ); alarm ( 0 ); alarm ( SIGALRM_INTERVAL ); } /** * sigchld_handler: SIGC(H)LD handler * * SIGC(H)LD handler * * Returns: nothing **/ #ifdef WITH_STANDALONE_SUPPORT static void sigchld_handler ( int signo ) { pid_t child; int status; while ( ( child = waitpid ( -1, &status, WNOHANG ) ) > (pid_t) 0 ) child_cnt--; signal ( signo, &sigchld_handler ); } #endif /** * setup_signal_handlers: setup signal handlers * * This function sets up the signal handlers * * Returns: nothing **/ static void setup_signal_handlers ( void ) { /* * Is use signal here, because... * well. sigaction wouldn't provide any * benefit here. */ signal ( SIGHUP, sig_handler ); signal ( SIGINT, sig_handler ); signal ( SIGQUIT, sig_handler ); signal ( SIGILL, sig_handler ); signal ( SIGABRT, sig_handler ); signal ( SIGFPE, sig_handler ); signal ( SIGSEGV, sig_handler ); signal ( SIGPIPE, SIG_IGN ); signal ( SIGTERM, sig_handler ); signal ( SIGUSR1, sig_handler ); signal ( SIGUSR2, sig_handler ); setup_sigalrm_handler (); } /** * sig_handler: signal handler * @number: signal number * * Handles signals gracefully. * * Returns: nothing. it exits. **/ static void sig_handler ( int number ) { control->system->log ( control, LOG_CRIT, "Terminating on signal %d: %s", number, g_strsignal ( number ) ); if ( P3L_GET_DATA ( "USER" ) ) control->send_response ( control, POP3_FATAL, "internal error" ); exit(1); } /** * sigalrm_handler: SIGALRM handler * @sig: signal number, unused * * This is the master SIGALRM handler. It increments * the tick counter of all registered handlers, and * runs those that reach their time. * * Returns: nothing **/ static void sigalrm_handler ( int sig ) { unsigned long i; HandlerInfo *hinfo; GList *handlers = p3l_get_alarm_hooks (); for ( i = 0; i < g_list_length ( handlers ); i++ ) { hinfo = (HandlerInfo *) g_list_nth_data ( handlers, i ); hinfo->counter++; if ( hinfo->counter >= hinfo->tick_max ) { hinfo->counter=0; (* (hinfo->hook) ) ( control ); } } setup_sigalrm_handler (); } /** * auto_logout_handler: auto-logout SIGALRM handler * @control: the main control struct * * This function is called whenever auto-logout time is * reached. * * Returns: 0 **/ static int auto_logout_handler ( P3LControl *control ) { if ( logout_timer_reset == 0 ) { control->system->log ( control, LOG_NOTICE, "auto-logout time elapsed (%s)", P3L_GET_DATA ( "USER" ) ); control->send_response ( control, POP3_FATAL, "auto-logout time elapsed" ); exit ( 1 ); return 0; } if ( logout_timer_reset == 1 ) logout_timer_reset = 0; return 0; } /** * main_loop_body: main loop body * @command_table: table to search for commands * * This one is called in the main loops, this reads * input and executes commands. * * Returns: nothing. **/ static void main_loop_body ( GHashTable *command_table ) { char *line; char **cmdarg; unsigned int c, len; p3l_pop3_command cmd_ptr; CommandResponse *resp; /* * Read input, max LINELEN chars. Ended with \n or \r */ line = (char *) g_malloc ( LINELEN + 2 ); len = 0; logout_timer_reset = 0; while ( ( c = fgetc ( stdin ) ) != '\r' && c != '\n' && len < LINELEN ) { if ( c != '\r' && c != '\n' ) line[len++] = c; else len++; } logout_timer_reset = 2; /* * If the first character is 0xff, ignore it, because * under heavy load, that gets prepended to some commands. * FIXME: find the REAL reason for this! */ if ( line[0] == (char) 0xff ) { line = &line[1]; len--; } /* * Is it too long ? */ if ( len >= LINELEN ) { control->send_response ( control, POP3_ERR, "command too long"); exit ( 1 ); } /* * Split into command + arg, and execute. */ line[len] = '\0'; line = g_strdup ( g_strstrip ( line ) ); cmdarg = g_strsplit ( line, " ", 1 ); if ( cmdarg[0] != NULL ) { g_strup ( cmdarg[0] ); cmd_ptr = (p3l_pop3_command) g_hash_table_lookup ( command_table, cmdarg[0] ); if ( cmd_ptr == NULL ) control->send_response ( control, POP3_ERR, "Invalid command" ); else { resp = (*cmd_ptr) ( control, cmdarg[1] ); control->send_response ( control, resp->code, resp->message ); g_free ( resp ); } } g_strfreev ( cmdarg ); g_free ( line ); logout_timer_reset = 1; } /** * exit_hook: g_atexit hook * * Called when the program terminates. * * Returns: nothing. **/ static void exit_hook ( void ) { if ( p3l_is_enabled ( P3L_GET_FIRST_OPTION ( "QUIT_ON_ERROR" ) ) && P3L_GET_DATA ( "USER" ) ) control->update ( control ); #ifndef WITH_STANDALONE_SUPPORT free_modules ( control ); #endif control->system->closelog ( control ); #ifdef WITH_STANDALONE_SUPPORT if ( getpid () == main_pid ) { unlink ( pidfn ); free_modules ( control ); } #endif } /** * print_version: print version information * * Prints something like this: * POP3Lite version 0.1.4-devel (1.107) [i386-pc-gnu] * Copyright (C) 2000, 2001 Gergely Nagy <8@free.bsd.hu> * * Returns: nothing **/ static void print_version ( void ) { printf ( "POP3Lite version %s%s [%s]\n", POP3LITE_VERSION, EXTRA_VERSION, __MACHINE__ ); printf ( "%s\n", "Copyright (C) 2000, 2001 Gergely Nagy <8@free.bsd.hu>" ); } /** * pop3lite_main_loop: main loop * @control: the main control struct * * This is the main loop. In standalone mode, it is the only job * of the child process. * * Returns: nothing **/ static void pop3lite_main_loop ( P3LControl *control ) { CommandResponse *res; char *alt; setup_signal_handlers (); /* * Greeting */ res = control->greeting ( control ); control->send_response ( control, res->code, res->message ); g_free ( res ); /* * Setup auto-logout handler */ alt = P3L_GET_FIRST_OPTION ( "AUTO_LOGOUT_TIME" ); if ( alt == NULL ) alt = "10"; logout_iv = strtoul ( alt, (char **) NULL, 10 ) * 60; p3l_register_alarm ( auto_logout_handler, logout_iv ); /* * AUTHENTICATION state */ while ( control->state == POP3_STATE_AUTH ) main_loop_body ( control->auth_commands ); /* * If we're authenticated, we MUST get rid of all * our special privileges */ control->system->drop_privileges ( control ); /* * If we're still root, log a warning and exit * if ALLOW_ROOT is off */ if ( getuid () == 0 ) { control->system->log ( control, LOG_ALERT, "Still running as root!" ); if ( ! p3l_is_enabled ( P3L_GET_FIRST_OPTION ( "ALLOW_ROOT" ) ) ) { control->send_response ( control, POP3_FATAL, "Internal error" ); exit ( 1 ); /* In case send_response didn't do it... */ } } /* * Get into transaction state */ res = control->trans_init ( control ); control->send_response ( control, res->code, res->message ); g_free ( res ); /* * TRANSACTION state */ while ( control->state == POP3_STATE_TRANS ) main_loop_body ( control->trans_commands ); /* * Do update */ res = control->update ( control ); control->send_response ( control, res->code, res->message ); g_free ( res ); /* * Exit */ exit ( 0 ); } /** * main: CC hack * @argc: argument count * @argv: argument vector * * This is here, because stupid C compilers want to do * nasty things with main. ;> * * Returns: bogus numbers **/ int main ( int argc, char **argv ) { struct option long_options[] = { {"config", required_argument, 0, 'c'}, {"define", required_argument, 0, 'D'}, {"help", no_argument, 0, 'h'}, {"undefine", required_argument, 0, 'U'}, {"version", no_argument, 0, 'v'}, #ifdef WITH_STANDALONE_SUPPORT {"inetd", no_argument, 0, 'i'}, {"pidfile", required_argument, 0, 'p'}, #endif {0, 0, 0, 0} }; int optc; char *cfg_file = NULL; GList *cmd_def = NULL; GList *cmd_udef= NULL; #ifdef WITH_STANDALONE_SUPPORT FILE *pidfile; int sd; pid_t child, self; char *host, *port; gboolean from_inetd = FALSE; char *getopt_list = "c:hvD:U:ip:"; unsigned long MAX_CHILDREN = 20; #else char *getopt_list = "c:hvD:U:"; #endif opterr = 0; /* * Argument parsing */ while ( ( optc = getopt_long ( argc, argv, getopt_list, long_options, 0 ) ) != - 1 ) { switch ( optc ) { case 0: break; case 'c': cfg_file = optarg; break; case 'D': cmd_def = g_list_append ( cmd_def, g_strdup ( optarg ) ); break; case 'U': cmd_udef = g_list_append ( cmd_udef, g_strdup ( optarg ) ); break; case 'h': print_version (); printf ( "%s", "\nOptions:\n" "\t--config, -c [file] use [file] as the " "configuration file.\n" "\t--define, -D [field]=[value] define [field] as [value].\n" ); #ifdef WITH_STANDALONE_SUPPORT printf ( "%s", "\t--inetd, -i run from inetd.\n" ); #endif printf ( "%s", "\t--help, -h this help screen.\n" ); #ifdef WITH_STANDALONE_SUPPORT printf ( "%s", "\t--pidfile, -p [file] write the pid to [file] in daemon mode.\n" ); #endif printf ( "%s", "\t--undefine, -U [field] undefine [field].\n" "\t--version, -v print version information.\n" "\nReport bugs to <8@free.bsd.hu>\n" ); exit ( 0 ); break; case 'v': print_version (); printf ( "%s", "\nPOP3Lite comes with ABSOLUTELY NO WARRANTY.\n" "You may redistribute copies of POP3Lite under the terms of the GNU\n" "General Public License, either version two, or any later version.\n" ); #ifdef DEBIAN printf ( "%s", "For more information see /usr/share/common-licenses/GPL.\n" ); #else printf ( "%s", "For more information about these matters, see the file named COPYING.\n" ); #endif exit ( 0 ); break; #ifdef WITH_STANDALONE_SUPPORT case 'i': from_inetd=TRUE; break; case 'p': pidfn = g_strdup ( optarg ); break; #endif default: break; } } /* * Control structure initialisation */ control = (P3LControl *) g_malloc ( sizeof ( P3LControl ) ); control->greeting = NULL; control->send_response = NULL; control->send_raw = NULL; control->trans_init = NULL; control->state = POP3_STATE_AUTH; control->msg_info=NULL; control->auth_commands = g_hash_table_new ( (GHashFunc) g_str_hash, (GCompareFunc) g_str_equal ); control->trans_commands = g_hash_table_new ( (GHashFunc) g_str_hash, (GCompareFunc) g_str_equal ); control->data = g_hash_table_new ( (GHashFunc) g_str_hash, (GCompareFunc) g_str_equal ); control->config = g_hash_table_new ( (GHashFunc) g_str_hash, (GCompareFunc) g_str_equal ); control->capabilities = g_hash_table_new ( (GHashFunc) g_str_hash, (GCompareFunc) g_str_equal ); control->system = (SysControl *) g_malloc ( sizeof ( SysControl ) ); control->system->getuinam = NULL; control->system->authenticate = NULL; control->system->drop_privileges = NULL; control->system->openlog = NULL; control->system->log = NULL; control->system->closelog = NULL; control->system->users = NULL; control->system->data = g_hash_table_new ( (GHashFunc) g_str_hash, (GCompareFunc) g_str_equal ); control->hooks = g_hash_table_new ( (GHashFunc) g_str_hash, (GCompareFunc) g_str_equal ); control->modules = g_hash_table_new ( (GHashFunc) g_str_hash, (GCompareFunc) g_str_equal ); alarm_handlers = NULL; /* * Load default module */ core_init ( control ); /* * Setup signal handlers. */ setup_signal_handlers (); control->system->openlog ( control ); /* * AtExit magic. If we must exit, do it in an elegant way. */ if ( g_ATEXIT ( exit_hook ) < 0 ) { control->system->log ( control, LOG_ERR, "g_ATEXIT failed" ); exit(1); } /* * Load configuration */ p3l_load_config ( control, cfg_file, control->config ); /* * Setup configuration specified on the commandline */ for ( optc = 0; optc < g_list_length ( cmd_def ); optc++ ) cfg_parse_line ( control, g_list_nth_data ( cmd_def, optc ), 0, NULL, control->config ); /* * Undefine options specified with --undefine */ for ( optc = 0; optc < g_list_length ( cmd_udef ); optc++ ) g_hash_table_remove ( control->config, g_list_nth_data ( cmd_udef, optc ) ); /* * Reinit core, in case the configuration has changed something */ core_reinit ( control ); /* * Load modules */ load_modules ( control ); /* * Main loop */ #ifdef WITH_STANDALONE_SUPPORT if ( P3L_GET_FIRST_OPTION ( "MAX_CHILDREN" ) ) strtoul ( P3L_GET_FIRST_OPTION ( "MAX_CHILDREN" ), (char **) NULL, 10 ); if ( from_inetd || p3l_get_peer_name () != NULL ) { main_pid = getpid (); pop3lite_main_loop ( control ); } else if ( ( self = fork () ) == 0 ) { close ( 0 ); close ( 1 ); close ( 2 ); close ( 3 ); host = P3L_GET_FIRST_OPTION ( "LISTEN.HOST" ); port = P3L_GET_FIRST_OPTION ( "LISTEN.PORT" ); if ( port == NULL ) port = "110"; if ( ( sd = tcp_listen ( host, port ) ) < 0 ) { if ( host == NULL ) control->system->log ( control, LOG_ERR, g_strdup_printf ( "Cannot listen on port %s", port ) ); else control->system->log ( control, LOG_ERR, g_strdup_printf ( "Cannot listen on %s:%s", host, port ) ); exit ( 1 ); } #ifdef DEBUG if ( host == NULL ) control->system->log ( control, LOG_DEBUG, g_strdup_printf ( "Listening on port %s", port ) ); else control->system->log ( control, LOG_DEBUG, g_strdup_printf ( "Listening on %s:%s", host, port ) ); #endif pidfile = fopen ( pidfn, "w" ); if ( pidfile != NULL ) { fprintf ( pidfile, "%ld\n", (long) getpid () ); fclose ( pidfile ); } else control->system->log ( control, LOG_WARNING, g_strdup_printf ( "Cannot write to pidfile: %s", pidfn ) ); main_pid = getpid (); #if defined(SIGCHLD) signal ( SIGCHLD, &sigchld_handler ); #elif defined(SIGCLD) signal ( SIGCLD, &sigcld_handler ); #endif while ( 1 ) { struct sockaddr_in them; socklen_t len = sizeof them; int ns = accept ( sd, (struct sockaddr*) &them, &len ); if ( ns < 0 ) { if ( errno == EINTR || errno == ECONNRESET ) continue; control->system->log ( control, LOG_ERR, g_strdup_printf ( "Accept failed: %s", g_strerror ( errno ) ) ); exit ( 1 ); } if ( MAX_CHILDREN == 0 || child_cnt <= MAX_CHILDREN ) { switch ( child = fork() ) { case -1: control->system->log ( control, LOG_ERR, g_strdup_printf ( "Cannot fork child: %s", g_strerror ( errno ) ) ); break; case 0: close ( sd ); dup2 ( ns, 0 ); dup2 ( ns, 1 ); setsid (); if ( p3l_get_peer_name () ) pop3lite_main_loop ( control ); else { control->system->log ( control, LOG_WARNING, "Remote end not connected." ); return 1; } break; default: child_cnt++; close ( ns ); break; } } else { control->system->log ( control, LOG_ERR, "Too many open connections!" ); close ( ns ); } } } else if ( self == -1 ) control->system->log ( control, LOG_ERR, "Cannot fork main daemon" ); #else pop3lite_main_loop ( control ); #endif return 0; }