/***************************************************************************** POPular -- A POP3 server and proxy for large mail systems $Id: pserv.c,v 1.57 2002/09/15 12:27:16 sqrt Exp $ http://www.remote.org/jochen/mail/popular/ ****************************************************************************** Copyright (C) 1999-2002 Jochen Topf 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 the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 *****************************************************************************/ #if HAVE_CONFIG_H # include #endif #include "popular.h" #include "pserv.h" #include "daemon.h" #include "ctrl.h" /* capability list */ struct pserv_capa capa; volatile int got_shutdown = 0; static int delayed_shutdown = 0; struct pservconfig conf; int debug; struct shmem_backend *ss; int current_session; int lsocket = -1; /***************************************************************************** pserv_shutdown() *****************************************************************************/ void pserv_shutdown(int which) { if (which & SHUTDOWN_CHILDREN) { int i; for (i=0; i < conf.maxsession; i++) { if (ss->session[i].state != sstFree) kill(ss->session[i].pid, SIGTERM); } /* XLOG-DOC:INF:012d:shutdown_children * The pserv server is shutting down all its children. */ xlog_printf(xlog_inf, 0x012d, "shutdown_children"); } if (which & SHUTDOWN_DELAYED) { go_offline(); delayed_shutdown = 1; /* XLOG-DOC:INF:0132:shutdown_delayed * The pserv server closed the listening ports. After all children have * died, it will close down. */ xlog_printf(xlog_inf, 0x0132, "shutdown_delayed"); } if (which & SHUTDOWN_SERVER) { unlink_in_rundir(conf.rundir, PSERV_STATE_TEMPL, PSERV_STATE_FILE); unlink_in_rundir(conf.rundir, PSERV_SOCK_TEMPL, PSERV_SOCK_FILE); unlink_in_rundir(conf.rundir, PSERV_PID_TEMPL, PSERV_PID_FILE); /* XLOG-DOC:INF:0005:shutdown_server * The pserv server is shut down. */ xlog_printf(xlog_inf, 0x0005, "shutdown_server"); exit(RCODE_OK); } } /***************************************************************************** free_session() (This is O(n), would be nice to have a better data structure to make this faster.) *****************************************************************************/ void free_session(pid_t pid) { int i; for (i=0; i < conf.maxsession; i++) { if (ss->session[i].pid == pid) { ss->stat.msgread += ss->session[i].msgread; ss->stat.msgdel += ss->session[i].msgdel; if (ss->session[i].state == sstConnQuit) { ss->stat.connquit++; } else { ss->stat.connbroken++; } memset(&ss->session[i], 0, sizeof(struct backend_session)); ss->used_sessions--; return; } } /* XLOG-DOC:SOS:0019:no_session_found * The pserv parent process got a process id from wait() that he doesn't * know about, i.e. that shouldn't be one of his children. This should * never happen and probably means that there is some serious data * corruption or a bug in the program. pserv will immediately exit. */ xlog_printf(xlog_sos, 0x0019, "no_session_found pid=%d", pid); exit(RCODE_OK); } /***************************************************************************** go_online() returns -1 == already online 0 == can't go online 1 == ok *****************************************************************************/ int go_online() { struct sockaddr_in server_addr; const int one=1; if (lsocket != -1) return -1; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; if (! strcmp(conf.localip, "ANY")) { server_addr.sin_addr.s_addr = htonl(INADDR_ANY); } else { if (! inet_aton(conf.localip, &server_addr.sin_addr)) { /* XLOG-DOC:SOS:0x017f:illegal_local_ip * The 'localip' config var is not set to a legal IP number. */ xlog_printf(xlog_sos, 0x017f, "illegal_local_ip ip='%s'", conf.localip); return 0; } } server_addr.sin_port = htons(conf.servport); if ((lsocket=socket(AF_INET, SOCK_STREAM, 0)) == -1) { /* XLOG-DOC:SOS:003a:socket_call_failed * When trying to open a listening socket, the socket system call * failed. */ xlog_printf(xlog_sos, 0x003a, "socket_call_failed errno=%d errmsg='%s'", errno, strerror(errno)); lsocket = -1; return 0; } if (setsockopt(lsocket, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)) == -1) { /* XLOG-DOC:SOS:003b:setsockopt_failed * When trying to open a listening socket, the setsockopt call to set * SO_REUSEADDR failed. */ xlog_printf(xlog_sos, 0x003b, "setsockopt_failed errno=%d errmsg='%s'", errno, strerror(errno)); lsocket = -1; return 0; } if (bind(lsocket, (struct sockaddr*)&server_addr, sizeof(server_addr))==-1) { /* XLOG-DOC:SOS:003c:bind_failed * When trying to open a listening socket, the bind() failed. */ xlog_printf(xlog_sos, 0x003c, "bind_failed errno=%d errmsg='%s'", errno, strerror(errno)); lsocket = -1; return 0; } if (listen(lsocket, conf.backlog) == -1) { /* XLOG-DOC:SOS:003d:listen_failed * When trying to open a listening socket, the listen() failed. */ xlog_printf(xlog_sos, 0x003d, "listen_failed errno=%d errmsg='%s'", errno, strerror(errno)); lsocket = -1; return 0; } /* XLOG-DOC:ADM:014e:server_now_online * The server listens on a port now and accepts connections. You should * see this log message in response to a 'server online' command. */ xlog_printf(xlog_adm, 0x014e, "server_now_online"); return 1; } /***************************************************************************** go_offline() returns -1 == already offline 0 == can't go offline 1 == ok *****************************************************************************/ int go_offline() { if (lsocket == -1) return -1; close(lsocket); lsocket = -1; /* XLOG-DOC:ADM:014f:server_now_offline * The server closed its listening port and doesn't accept any new * connections. You should see this log message in response to a 'server * offline' command. */ xlog_printf(xlog_adm, 0x014f, "server_now_offline"); return 1; } /***************************************************************************** is_online() *****************************************************************************/ int is_online() { return lsocket == -1 ? 0 : 1; } /***************************************************************************** new_session() *****************************************************************************/ void new_session() { struct sockaddr remote; socklen_t remotelen=sizeof(remote); int clientfd; pid_t pid; do { clientfd = accept(lsocket, &remote, &remotelen); } while (clientfd < 0 && errno == EINTR); if (clientfd < 0) { /* XLOG-DOC:ERR:003e:accept_error * The accept() call failed. This probably means that the client closed * the connection before the server could accept it. If this occures only * occasionally, it is harmless. If it occures more often, it might * indicate network problems. */ xlog_printf(xlog_err, 0x003e, "accept_error errno=%d errmsg='%s'", errno, strerror(errno)); return; } /* find next free session */ do { current_session = (current_session + 1) % conf.maxsession; } while (ss->session[current_session].state != sstFree); ss->session[current_session].state = sstNew; ss->session[current_session].statetime = time(NULL); switch (pid = fork()) { case -1: /* error */ /* XLOG-DOC:SOS:003f:fork_failed * Pserv tried to fork a child and that failed. This probably means * that the system is overloaded. The connection to the client will * be closed and we try to keep going. */ xlog_printf(xlog_sos, 0x003f, "fork_failed errno=%d errmsg='%s'", errno, strerror(errno)); close(clientfd); ss->session[current_session].state = sstFree; break; case 0: /* child */ close(lsocket); child_main(clientfd, &(ss->session[current_session]), current_session); exit(RCODE_OK); default: /* parent */ close(clientfd); DEBUG1(DG_MAIN, "main", "new process %d", pid); ss->session[current_session].pid = pid; ss->stat.connections++; ss->used_sessions++; break; } } /***************************************************************************** print_usage() *****************************************************************************/ void print_usage(const char *prg) { printf("Usage: %s [OPTION]\n", prg); printf("POP server.\n"); printf("Options:\n"); printf(" -l, --logfile=FILE set name of logfile\n"); printf(" (default: `%s')\n", PSERV_LOG_FILE); printf(" -r, --rundir=DIR set name of directory for pid and state file,\n"); printf(" and for ctrl socket (default: `%s')\n", RUN_DIR); printf(" --help print this help message\n"); printf(" --version print version information\n"); printf("\n"); } /***************************************************************************** main() *****************************************************************************/ int main(int argc, char *argv[]) { int pcontrol_fd; int c; char *prgname = argv[0]; static struct option long_options[] = { { "help", no_argument, 0, 0 }, { "version", no_argument, 0, 0 }, { "rundir", required_argument, 0, 'r' }, { "logfile", required_argument, 0, 'l' }, { NULL, 0, 0, 0 } }; /*************************************************************************** Set defaults for various config options. ***************************************************************************/ init_config_vars(); debug = 0; /*************************************************************************** Parse command line options. ***************************************************************************/ while (1) { int option_index = 0; c = getopt_long(argc, argv, "l:r:", long_options, &option_index); if (c == -1) break; switch (c) { case 0: if (! strcmp(long_options[option_index].name, "version")) { printf("pserv - POPular POP server suite %s\n", VERSION); exit(RCODE_OK); } else if (! strcmp(long_options[option_index].name, "help")) { print_usage(prgname); exit(RCODE_OK); } else { fprintf(stderr, "%s: unknown option: %s\n", prgname, long_options[option_index].name); exit(RCODE_INTERNAL); } break; case 'l': /* logfile */ (void) strlcpy(conf.logfile, optarg, sizeof(conf.logfile)); break; case 'r': /* rundir */ (void) strlcpy(conf.rundir, optarg, sizeof(conf.rundir)); break; default: fprintf(stderr, "Try `%s --help' for more information.\n", prgname); exit(RCODE_CMDLINE); } } if (optind != argc) { fprintf(stderr, "%s: invalid extra arguments.\nTry `%s --help' for more information.\n", prgname, prgname); exit(RCODE_CMDLINE); } /*************************************************************************** This program should not be run as root and not be setuid or setgid. ***************************************************************************/ if (!getuid() || !geteuid()) { fprintf(stderr, "%s: don't start this program as `root'.\n", prgname); exit(RCODE_CMDLINE); } if (getuid() != geteuid() || getgid() != getegid()) { fprintf(stderr, "%s: don't make this program setuid or setgid.\n", prgname); exit(RCODE_CMDLINE); } /*************************************************************************** Fork to become a daemon. ***************************************************************************/ daemonize(prgname); conf.pid = getpid(); signal_init(0); /*************************************************************************** Open log. ***************************************************************************/ if (xlog_open(PSERV_PRG_NAME, conf.logfile, PSERV_LOG_MODE) < 0) { fprintf(stderr, "%s: couldn't open logfile '%s': %s\n", prgname, conf.logfile, strerror(errno)); exit(RCODE_ERR); } /* XLOG-DOC:INF:0001:start * Pserv has started. The server is mostly initialized. */ xlog_printf(xlog_inf, 0x0001, "start version='%s' rundir='%s'", VERSION, conf.rundir); /*************************************************************************** Open pid file now and write pid into it. ***************************************************************************/ write_pidfile(conf.rundir, PSERV_PID_TEMPL, PSERV_PID_FILE); /*************************************************************************** Open control socket. ***************************************************************************/ pcontrol_fd = open_ctrl_socket(conf.rundir, PSERV_SOCK_TEMPL, PSERV_SOCK_FILE, PSERV_SOCK_UMASK); /*************************************************************************** Preparing shared mmaped file for config, status and statistical infos. ***************************************************************************/ ss = prepare_statefile(conf.rundir, PSERV_STATE_TEMPL, PSERV_STATE_FILE, sizeof(struct shmem_backend), NULL); strlcpy(ss->magic, "POPULAR", 8); strlcpy(ss->type, "SERVER", 8); ss->version = SHMEM_VERSION; ss->starttime = time(NULL); ss->max_sessions = MAX_SESSION; ss->n_sessions = conf.maxsession; memcpy(&(ss->conf), &conf, sizeof(struct pservconfig)); /*************************************************************************** Main loop. ***************************************************************************/ current_session=0; DEBUG0(DG_MAIN, "main", "start main loop"); while (1) { fd_set set; int rc; int sig; FD_ZERO(&set); FD_SET(pcontrol_fd, &set); /* if we are online, if maxsessions is not reached, and if load is ok add listening socket to set */ if (lsocket != -1 && sess_ok(ss->used_sessions, conf.maxsession, conf.rundir, PSERV_MAX_FILE) && load_ok(conf.maxlocalload)) { FD_SET(lsocket, &set); } rc = rel_select(&set, 1, 0); if (rc < 0) { /* XLOG-DOC:SOS:0042:select_error * The select () syscall in the main loop returned an error that is * not handled. This should never happen. */ xlog_printf(xlog_sos, 0x0042, "select_error errno=%d errmsg='%s'", errno, strerror(errno)); continue; } while ((sig = signal_next())) { switch (sig) { case SIGQUIT: pserv_shutdown(SHUTDOWN_SERVER); break; case SIGINT: pserv_shutdown(SHUTDOWN_CHILDREN); break; case SIGTERM: pserv_shutdown(SHUTDOWN_CHILDREN | SHUTDOWN_SERVER); break; case SIGHUP: xlog_reopen(conf.logfile, PSERV_LOG_MODE); break; } } reapchildren(&free_session); if (delayed_shutdown && ! ss->used_sessions) { /* XLOG-DOC:INF:0131:all_children_gone * All children have died and the server got a delayed shutdown * command earlier. It will shutdown now. */ xlog_printf(xlog_inf, 0x0131, "all_children_gone"); pserv_shutdown(SHUTDOWN_SERVER); } /* we have to shortcut here, because content of 'set' is allowed to be garbage if rc < 0 */ if (rc <= 0) continue; /* any commands pending on the control socket ? */ if (FD_ISSET(pcontrol_fd, &set)) { got_shutdown = 0; control_command(pcontrol_fd); /* in case conf.maxsession was changed by a 'set' command. */ ss->n_sessions = conf.maxsession; if (got_shutdown) pserv_shutdown(got_shutdown); continue; } if (lsocket != -1 && FD_ISSET(lsocket, &set)) new_session(); } /* XLOG-DOC:BUG:0004:left_main_loop * Somehow the main loop was terminated. This should never happen. */ xlog_printf(xlog_bug, 0x0004, "left_main_loop"); exit(RCODE_ERR); } /** THE END *****************************************************************/