/***************************************************************************** POPular -- A POP3 server and proxy for large mail systems $Id: pproxy.c,v 1.58 2003/11/24 19:23:30 sqrt Exp $ http://www.remote.org/jochen/mail/popular/ ****************************************************************************** Copyright (C) 1999-2003 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 "pproxy.h" #include "daemon.h" #include "ctrl.h" #include "ringd.h" /* pointer to the memory mapped file used for config, status and stats */ struct shmem_proxy *sp; /* configuration variables */ struct pproxyconfig conf; /* file descriptor of log file */ static int logfd; /* number of the last session slot that was allocated */ static int current_session; /* file descriptor of state file */ static int statusfd; /* first capability in linked list */ struct capa *first_capa = NULL; /* first pdm module in linked list */ struct pdm_module *first_pdm_module = NULL; /* pointer to pointer of last pdm module in linked list */ struct pdm_module **last_pdm_module = &first_pdm_module; int debug; int got_shutdown = 0; static int delayed_shutdown = 0; /***************************************************************************** free_session() (This is O(n), would be nice to have a better data structure to make this faster. But do profiling first... Maybe the child should return its session number as exit code?) *****************************************************************************/ void free_session(pid_t pid) { int i; for (i=0; i < conf.maxsession; i++) { if (sp->session[i].pid == pid) { sp->stat.bytesin += sp->session[i].bytesin; sp->stat.bytesout += sp->session[i].bytesout; if (sp->session[i].mailcheck_retry >= 0) { sp->stat.mailcheck_time += sp->session[i].mailcheck_time; sp->stat.mailcheck_retry[sp->session[i].mailcheck_retry]++; } memset(&sp->session[i], 0, sizeof(struct proxy_session)); sp->used_sessions--; return; } } /* XLOG-DOC:SOS:007c:no_session_found * The pproxy 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. pproxy will immediately exit. */ xlog_printf(xlog_sos, 0x007c, "no_session_found pid=%d", pid); exit(RCODE_OK); } /***************************************************************************** pproxy_shutdown() *****************************************************************************/ void pproxy_shutdown(int which) { if (which & SHUTDOWN_CHILDREN) { int i; for (i=0; i < conf.maxsession; i++) { if (sp->session[i].state != sstFree) kill(sp->session[i].pid, SIGTERM); } /* XLOG-DOC:INF:012c:shutdown_children * The pproxy server is shutting down all its children. */ xlog_printf(xlog_inf, 0x012c, "shutdown_children"); } if (which & SHUTDOWN_DELAYED) { flush_virt_serv(vsstOffline); delayed_shutdown = 1; /* XLOG-DOC:INF:012e:shutdown_delayed * The pproxy server closed all listening ports. After all children have * died, it will close down. */ xlog_printf(xlog_inf, 0x012e, "shutdown_delayed"); } if (which & SHUTDOWN_SERVER) { flush_virt_serv(vsstOffline); unlink_in_rundir(conf.rundir, PPROXY_STATE_TEMPL, PPROXY_STATE_FILE); unlink_in_rundir(conf.rundir, PPROXY_SOCK_TEMPL, PPROXY_SOCK_FILE); unlink_in_rundir(conf.rundir, PPROXY_PID_TEMPL, PPROXY_PID_FILE); /* XLOG-DOC:INF:007d:shutdown_server * The pproxy server is shut down. */ xlog_printf(xlog_inf, 0x007d, "shutdown_server"); exit(RCODE_OK); } } /***************************************************************************** connect_to_ringd() *****************************************************************************/ int connect_to_ringd() { int local_socket; char socket_name[MAXBUF]; snprintf(socket_name, sizeof(socket_name)-1, "%s/%s.sock.%d", RUN_DIR, PPROXY_PRG_NAME, getpid()); local_socket = uds_socket(socket_name, RINGD_SOCK_UMASK); if (local_socket < 0) { /* XLOG-DOC:SOS:0181:sock_open_failed * Pproxy tried to open a unix domain socket to talk to the ringd and * this failed */ xlog_printf(xlog_sos, 0x0181, "sock_open_failed errno=%d errmsg='%s'", errno, strerror(errno)); return -1; } if (uds_connect(local_socket, RINGD_SOCK_FILE) < 0) { /* XLOG-DOC:SOS:0182:sock_connect_failed * Pproxy tried to connect to the unix domain socket of the ringd and * this failed */ xlog_printf(xlog_sos, 0x0182, "sock_connect_failed errno=%d errmsg='%s'", errno, strerror(errno)); return -1; } return local_socket; } /***************************************************************************** close_connection_to_ringd() *****************************************************************************/ void close_connection_to_ringd(int c) { char socket_name[MAXBUF]; close(c); snprintf(socket_name, sizeof(socket_name)-1, "%s/%s.sock.%d", RUN_DIR, PPROXY_PRG_NAME, getpid()); unlink(socket_name); } /***************************************************************************** bind_and_listen() returns fd *****************************************************************************/ int bind_and_listen(const struct sockaddr_in *sin) { char buf[MAXBUF]; int local_socket; int fd; /* XLOG-DOC:INF:0084:open_port * Pproxy has been instructed to listen to a new IP number and port. */ xlog_printf(xlog_inf, 0x0084, "open_port ip='%s' port=%d", print_ip(sin, NULL), ntohs(sin->sin_port)); snprintf(buf, sizeof(buf)-1, "o %s %d", print_ip(sin, NULL), ntohs(sin->sin_port)); local_socket = connect_to_ringd(); if (local_socket < 0) return -1; if (send(local_socket, buf, strlen(buf)+1, 0) != strlen(buf)+1) { /* XLOG-DOC:SOS:0085:port_open_send_failed * Sending a port open request to the ringd process failed. */ xlog_printf(xlog_sos, 0x0085, "port_open_send_failed errno=%d errmsg='%s'", errno, strerror(errno)); close_connection_to_ringd(local_socket); return -1; } { char buf[MAXBUF]; int result = uds_recv(local_socket, NULL, buf, sizeof(buf), &fd); close_connection_to_ringd(local_socket); if (result < 0) { /* XLOG-DOC:SOS:0086:port_open_failed * Opening of the port failed for some reason on the pproxy side. */ xlog_printf(xlog_sos, 0x0086, "port_open_failed errno=%d errmsg='%s'", errno, strerror(errno)); return -1; } else if (result == 0) { /* XLOG-DOC:SOS:0143:port_open_no_fd * The opening and binding of the port by the ringd process failed * for some reason. The message from the ringd process is printed * with this message. */ xlog_printf(xlog_sos, 0x0143, "port_open_no_fd msg='%s'", buf); return -1; } } if (listen(fd, conf.backlog) != 0) { /* XLOG-DOC:SOS:0087:listen_failed * The listen() system call on the just opened and bound socket * failed. */ xlog_printf(xlog_sos, 0x0087, "listen_failed errno=%d errmsg='%s'", errno, strerror(errno)); close(fd); return -1; } return fd; } /***************************************************************************** close_listen_port() *****************************************************************************/ int close_listen_port(const struct sockaddr_in *sin) { char buf[MAXBUF]; int local_socket; int fd; /* XLOG-DOC:INF:0193:close_port * Pproxy has been instructed to listen to a new IP number and port. */ xlog_printf(xlog_inf, 0x0193, "close_port ip='%s' port=%d", print_ip(sin, NULL), ntohs(sin->sin_port)); snprintf(buf, sizeof(buf)-1, "c %s %d", print_ip(sin, NULL), ntohs(sin->sin_port)); local_socket = connect_to_ringd(); if (local_socket < 0) return -1; if (send(local_socket, buf, strlen(buf)+1, 0) != strlen(buf)+1) { /* XLOG-DOC:SOS:0194:port_close_send_failed * Sending a port close request to the ringd process failed. */ xlog_printf(xlog_sos, 0x0194, "port_close_send_failed errno=%d errmsg='%s'", errno, strerror(errno)); close_connection_to_ringd(local_socket); return -1; } { char buf[MAXBUF]; int result = uds_recv(local_socket, NULL, buf, sizeof(buf), &fd); close_connection_to_ringd(local_socket); if (result < 0) { /* XLOG-DOC:SOS:0195:port_close_failed * Closing of the port failed for some reason on the pproxy side. */ xlog_printf(xlog_sos, 0x0195, "port_close_failed errno=%d errmsg='%s'", errno, strerror(errno)); return -1; } } return 0; } /***************************************************************************** print_usage() *****************************************************************************/ void print_usage(const char *prg) { printf("Usage: %s [OPTION] ...\n", prg); printf("POP proxy server.\n"); printf("Options:\n"); printf(" -l, --logfile=FILE set name of logfile\n"); printf(" (default: `%s')\n", PPROXY_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"); } /***************************************************************************** new_session() *****************************************************************************/ void new_session(struct virt_serv *vserv) { struct sockaddr remote; socklen_t remotelen=sizeof(remote); int n; pid_t pid; int clientfd; if (! vserv) return; do { clientfd = accept(vserv->fd, &remote, &remotelen); } while (clientfd < 0 && errno == EINTR); if (clientfd < 0) { /* XLOG-DOC:ERR:0146:accept_failed * The accept () system call failed for some reason. */ xlog_printf(xlog_err, 0x0146, "accept_failed errno=%d errmsg='%s'", errno, strerror(errno)); return; } /* find next free session */ do { current_session = (current_session + 1) % conf.maxsession; } while (sp->session[current_session].state != sstFree); memset(&(sp->session[current_session]), 0, sizeof(sp->session[current_session])); sp->session[current_session].fd_client = clientfd; sp->session[current_session].state = sstNew; sp->session[current_session].statetime = time(NULL); sp->session[current_session].mailcheck_retry = -1; pid = fork(); switch (pid) { case -1: /* error */ /* XLOG-DOC:SOS:0089:fork_failed * Pproxy 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, 0x0089, "fork_failed errno=%d errmsg='%s'", errno, strerror(errno)); close(clientfd); sp->session[current_session].state = sstFree; break; case 0: /* child */ DEBUG0(DG_MAIN, "main", "child started"); /* close all unneeded file descriptors */ for (n=0; n < 255; n++) { if (n == statusfd) continue; /* don't close status fd */ if (n == clientfd) continue; /* don't close new connection */ if (n == logfd) continue; /* don't close logging fd */ close(n); } child_main(&(sp->session[current_session]), current_session, vserv); DEBUG0(DG_MAIN, "main", "child: should never be here"); exit(RCODE_OK); default: /* parent */ close(clientfd); DEBUG1(DG_MAIN, "main", "parent: new process %d started", pid); sp->session[current_session].pid = pid; sp->stat.connections++; sp->used_sessions++; break; } } /***************************************************************************** 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; { char *p; (void) gethostname(conf.sidprefix, sizeof(conf.sidprefix)); conf.sidprefix[sizeof(conf.sidprefix)-1] = '\0'; p = strchr(conf.sidprefix, '.'); if (p) *p = '\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("pproxy - 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 ((logfd = xlog_open(PPROXY_PRG_NAME, conf.logfile, PPROXY_LOG_MODE)) < 0) { fprintf(stderr, "%s: couldn't open logfile '%s': %s\n", prgname, conf.logfile, strerror(errno)); exit(RCODE_ERR); } /* XLOG-DOC:INF:008a:start * PProxy has started and is mostly initialized. */ xlog_printf(xlog_inf, 0x008a, "start version='%s' rundir='%s'", VERSION, conf.rundir); /*************************************************************************** Open pid file now and write pid into it. ***************************************************************************/ write_pidfile(conf.rundir, PPROXY_PID_TEMPL, PPROXY_PID_FILE); /*************************************************************************** Open control socket. ***************************************************************************/ pcontrol_fd = open_ctrl_socket(conf.rundir, PPROXY_SOCK_TEMPL, PPROXY_SOCK_FILE, PPROXY_SOCK_UMASK); /*************************************************************************** Preparing shared mmaped file for status and statistical infos. ***************************************************************************/ sp = prepare_statefile(conf.rundir, PPROXY_STATE_TEMPL, PPROXY_STATE_FILE, sizeof(struct shmem_proxy), &statusfd); (void) strlcpy(sp->magic, "POPULAR", sizeof(sp->magic)); (void) strlcpy(sp->type, "PROXY", sizeof(sp->type)); sp->version = SHMEM_VERSION; sp->starttime = time(NULL); sp->max_virt_serv = MAX_VIRT_SERV; sp->max_backend = MAX_BACKEND; sp->max_sessions = MAX_SESSION; sp->n_sessions = conf.maxsession; /*************************************************************************** Initialize TLS if compiled in. ***************************************************************************/ #ifdef USE_TLS io_tls_main_setup(); #endif /*************************************************************************** OK. Here we are at last: The 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); /* add virtual server fds to fd set only if there are free slots and the load is not too high */ if (sess_ok(sp->used_sessions, conf.maxsession, NULL, NULL) && load_ok(conf.maxlocalload)) fdset_virt_serv(&set); rc = rel_select(&set, 1, 0); if (rc < 0) { /* XLOG-DOC:SOS:008e:select_error * The select () syscall in the main loop returned an error that is * not handled. This should never happen. */ xlog_printf(xlog_sos, 0x008e, "select_error errno=%d errmsg='%s'", errno, strerror(errno)); continue; } while ((sig = signal_next())) { switch (sig) { case SIGQUIT: pproxy_shutdown(SHUTDOWN_SERVER); break; case SIGINT: pproxy_shutdown(SHUTDOWN_CHILDREN); break; case SIGTERM: pproxy_shutdown(SHUTDOWN_CHILDREN | SHUTDOWN_SERVER); break; case SIGHUP: logfd = xlog_reopen(conf.logfile, PPROXY_LOG_MODE); break; } } reapchildren(&free_session); if (delayed_shutdown && ! sp->used_sessions) { /* XLOG-DOC:INF:0130:all_children_gone * All children have died and the server got a delayed shutdown * command earlier. It will shutdown now. */ xlog_printf(xlog_inf, 0x0130, "all_children_gone"); pproxy_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. */ sp->n_sessions = conf.maxsession; if (got_shutdown) pproxy_shutdown(got_shutdown); continue; } /* calls new_session for all virtual servers that have new connections pending */ fd_isset_virt_serv(&set, conf.maxsession - sp->used_sessions); } /* XLOG-DOC:BUG:008f:left_main_loop * Somehow the main loop was terminated. This should never happen. */ xlog_printf(xlog_bug, 0x008f, "left_main_loop"); exit(RCODE_ERR); } /** THE END *****************************************************************/