/*****************************************************************************
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 <jochen@remote.org>
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 <config.h>
#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 *****************************************************************/
syntax highlighted by Code2HTML, v. 0.9.1