/*****************************************************************************
POPular -- A POP3 server and proxy for large mail systems
$Id: pcheckd.c,v 1.35 2003/11/22 21:27:22 sqrt Exp $
http://www.remote.org/jochen/mail/popular/
******************************************************************************
Copyright (C) 1999-2002 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 "daemon.h"
static char mbdir[MAXBUF];
static char maxsession_file[MAXBUF];
static int load_is_ok;
static long num_requests;
static long num_mailchecks;
static long num_mailchecks_old;
static long num_empty;
static long num_not_empty;
static long num_load_too_high;
static long num_maxsession_reached;
int debug;
int child_pid[MAX_PCHECKD_CHILDREN];
/*****************************************************************************
handle_mailcheck()
*****************************************************************************/
int
handle_mailcheck(const char *mailbox, char *out)
{
DIR *d;
int n, len, i;
char dirname[MAXLEN_DIRNAME];
if (mailbox[0] == '/') {
/* XLOG-DOC:ADM:0072:invalid_mailbox
* The mailbox name that the client sent is invalid. The name is not
* allowed to start with a '/' */
xlog_printf(xlog_adm, 0x0072, "invalid_mailbox mailbox='%s'", mailbox);
return strlcpy(out, "-ERR no mailbox", MAXLEN_MAILCHECK);
}
for (i=0; mailbox[i]; i++) {
if ( !(isalnum((int)(mailbox[i])) ||
mailbox[i] == '.' ||
mailbox[i] == '_' ||
mailbox[i] == '-' ||
mailbox[i] == '+' ||
mailbox[i] == '/' ||
mailbox[i] == '%' ||
mailbox[i] == '='
) ||
(mailbox[i] == '.' && mailbox[i+1] == '.')
) {
/* XLOG-DOC:ADM:0154:bad_mailbox
* The mailbox name that the proxy sent to the pserv has funny
* chars in it. Only the following chars are allowed: a-z, A-Z, 0-9,
* '.', '_', '-', '+', '/', '=', '%'. Two adjacent dots ("..") are not
* allowed. */
xlog_printf(xlog_adm, 0x0154, "bad_mailbox mailbox='%s'", mailbox);
return strlcpy(out, "-ERR no mailbox", MAXLEN_MAILCHECK);
}
}
(void) strlcpy(dirname, mbdir, sizeof(dirname));
(void) strlcat(dirname, "/", sizeof(dirname));
(void) strlcat(dirname, mailbox, sizeof(dirname));
len = strlcat(dirname, "/new", sizeof(dirname));
d = opendir(dirname);
if (! d) {
/* XLOG-DOC:ERR:0073:opendir_failed
* The 'new' directory for the given mailbox can't be opened. This
* probably means that the mailbox hasn't been created yet. */
xlog_printf(xlog_err, 0x0073, "opendir_failed dir='%s' errno=%d errmsg='%s'", dirname, errno, strerror(errno));
return strlcpy(out, "-ERR no mailbox", MAXLEN_MAILCHECK);
}
n=0;
while (readdir(d)) {
n++;
if (n > 2) {
closedir(d);
num_not_empty++;
return strlcpy(out, "+OK 2 new mail", MAXLEN_MAILCHECK);
}
}
closedir(d);
(void) strlcpy(dirname+len-3, "cur", 4);
d = opendir(dirname);
if (! d) {
/* XLOG-DOC:ADM:0074:opendir_failed
* The 'cur' directory for the given mailbox can't be opened. */
xlog_printf(xlog_adm, 0x0074, "opendir_failed dir='%s' errno=%d errmsg='%s'", dirname, errno, strerror(errno));
return strlcpy(out, "-ERR no mailbox", MAXLEN_MAILCHECK);
}
n=0;
while (readdir(d)) {
n++;
if (n > 2) {
closedir(d);
num_not_empty++;
return strlcpy(out, "+OK 1 mail", MAXLEN_MAILCHECK);
}
}
closedir(d);
num_empty++;
return strlcpy(out, "+OK 0 no mail", MAXLEN_MAILCHECK);
}
/*****************************************************************************
handle_loadcheck()
*****************************************************************************/
int
handle_loadcheck(const char *in, char *out)
{
long load = get_load();
if (load < 0) {
(void) strlcpy(out, "-ERR not available", MAXLEN_MAILCHECK);
} else {
snprintf(out, MAXLEN_MAILCHECK, "+OK %ld", load);
}
return strlen(out);
}
/*****************************************************************************
handle_request()
*****************************************************************************/
int
handle_request(const char *in, char *out)
{
num_requests++;
if (in[0] == '*') {
if (in[1] == 'L') {
return handle_loadcheck(in+2, out);
} else if (in[1] == 'M') {
if (load_is_ok) {
struct stat sbuf;
int sr = stat(maxsession_file, &sbuf);
if (sr == -1) {
num_mailchecks++;
return handle_mailcheck(in+2, out);
} else {
num_maxsession_reached++;
return strlcpy(out, "+OK 4 maxsession reached", MAXLEN_MAILCHECK);
}
} else {
num_load_too_high++;
return strlcpy(out, "+OK 3 load too high", MAXLEN_MAILCHECK);
}
} else {
/* XLOG-DOC:ADM:0075:unknown_request
* The client sent an unknown request. Allowed are 'L' for load check
* and 'M' for mailbox check. */
xlog_printf(xlog_adm, 0x0075, "unknown_request request='%s'", in);
(void) strlcpy(out, "-ERR unknown request", MAXLEN_MAILCHECK);
return strlen(out);
}
} else { /* compatibility for old versions */
num_mailchecks_old++;
return handle_mailcheck(in, out);
}
}
/*****************************************************************************
pcheckd_reapchildren()
Reap all deceased children, logging the event only if they died an
unnatural death.
*****************************************************************************/
void
pcheckd_reapchildren()
{
pid_t pid;
int status, i;
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
if (WIFEXITED(status) && WEXITSTATUS(status)) {
/* XLOG-DOC:ADM:0156:child_died
* A child of pcheckd died with non-zero exit code. See the log entries
* of the child for more details about what happened. */
xlog_printf(xlog_adm, 0x0156, "child_died pid=%d status=%d", pid, WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
/* XLOG-DOC:ADM:0157:child_died_signal
* A child of pcheckd died because of a signal. This can either mean
* that there is a bug in the program of the system has big problems
* (like running out of memory). */
xlog_printf(xlog_sos, 0x0157, "child_died_signal pid=%d signal=%d", pid, WTERMSIG(status));
}
DEBUG1(DG_MAIN, "reapchildren", "child died pid=%d", pid);
for (i=0; i<MAX_PCHECKD_CHILDREN; i++) {
if (child_pid[i] == pid) {
child_pid[i] = 0;
break;
}
}
}
}
/*****************************************************************************
print_usage()
*****************************************************************************/
void
print_usage(const char *prg)
{
printf("Usage: %s [OPTION] ...\n", prg);
printf("Daemon that can be asked to check for mails in mailboxes.\n");
printf("Options:\n");
printf(" -l, --logfile=FILE set logfile name (default: `%s')\n", PCHECKD_LOG_FILE);
printf(" -m, --mailboxdir=DIR set mailbox directory (default: `%s')\n", POPDIR);
printf(" -p, --port=PORT set UDP port to listen on (default: %d)\n", DEFAULT_PORT_CHECK);
printf(" -e, --emptyload=LOAD if load is higher than this, all mailboxes\n");
printf(" are empty (0=off, default: 0)\n");
printf(" -r, --rundir=DIR set name of directory where pidfile is\n");
printf(" saved (default: `%s')\n", RUN_DIR);
printf(" -f, --fork=NUM number of children to fork\n");
printf(" (default: no fork, max: %d)\n", MAX_PCHECKD_CHILDREN);
printf(" -d, --debug enable debugging messages\n");
printf(" -h, --host=HOST host name or IP number to bind to\n");
printf(" (default: INADDR_ANY)\n");
printf(" --nodaemon don't start as daemon\n");
printf(" --help print this help message\n");
printf(" --version print version information\n");
printf("\n");
}
/*****************************************************************************
main()
*****************************************************************************/
int
main(int argc, char *argv[])
{
int s;
int c, i, daemon=1, checkport=DEFAULT_PORT_CHECK;
int emptyload=0;
char host[80] = "";
const int one = 1;
struct sockaddr_in sa;
struct sockaddr remote;
unsigned int remotelen = sizeof(remote);
char inmsg[MAXLEN_MAILCHECK+1];
char outmsg[MAXLEN_MAILCHECK+1];
char *logfile = PCHECKD_LOG_FILE;
char rundir[MAXBUF] = RUN_DIR;
char *prgname;
int num_fork=0;
int parent=1;
static struct option long_options[] = {
{ "help", no_argument, 0, 0 },
{ "version", no_argument, 0, 0 },
{ "debug", no_argument, 0, 'd' },
{ "nodaemon", no_argument, 0, 0 },
{ "rundir", required_argument, 0, 'r' },
{ "port", required_argument, 0, 'p' },
{ "logfile", required_argument, 0, 'l' },
{ "mailboxdir", required_argument, 0, 'm' },
{ "emptyload", required_argument, 0, 'e' },
{ "fork", required_argument, 0, 'f' },
{ "host", required_argument, 0, 'h' },
{ NULL, 0, 0, 0 }
};
prgname = argv[0]; /* program name for error messages */
strlcpy(mbdir, POPDIR, sizeof(mbdir));
debug = 0;
for (i=0; i<MAX_PCHECKD_CHILDREN; i++) child_pid[i] = 0;
/***************************************************************************
Parse command line options.
***************************************************************************/
while (1) {
int option_index = 0;
c = getopt_long(argc, argv, "l:m:p:e:r:f:h:d", long_options, &option_index);
if (c == -1) break;
switch (c) {
case 0:
if (! strcmp(long_options[option_index].name, "version")) {
printf("pcheckd - 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 if (! strcmp(long_options[option_index].name, "nodaemon")) {
daemon = 0;
} else {
fprintf(stderr, "%s: unknown option: %s\n", prgname, long_options[option_index].name);
exit(RCODE_INTERNAL);
}
break;
case 'l': /* log file */
logfile = optarg;
break;
case 'm': /* mailboxdir */
strlcpy(mbdir, optarg, sizeof(mbdir));
break;
case 'd': /* debug */
debug = DG_ALL;
break;
case 'p': /* UDP port */
checkport = get_port(optarg);
if (checkport < 0) {
fprintf(stderr, "%s: port must be valid UDP port.\n", prgname);
exit(RCODE_CMDLINE);
}
break;
case 'f': /* fork */
num_fork = get_int(optarg, 0, MAX_PCHECKD_CHILDREN, -1, -1, -1);
if (num_fork < 0) {
fprintf(stderr, "%s: fork argument must be between 0 and %d.\n", prgname, MAX_PCHECKD_CHILDREN);
exit(RCODE_CMDLINE);
}
break;
case 'r': /* rundir */
strlcpy(rundir, optarg, sizeof(rundir));
break;
case 'h': /* host or IP number */
strlcpy(host, optarg, sizeof(host));
break;
case 'e': /* emptyload */
emptyload = get_int(optarg, 0, 9999, -1, -1, -1);
if (emptyload < 0) {
fprintf(stderr, "%s: emptyload must be 0 (=off) or between 1 and 9999.\n", prgname);
exit(RCODE_CMDLINE);
}
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);
}
if (access(mbdir, R_OK) != 0) {
fprintf(stderr, "%s: access to `%s' failed: %s\n", prgname, mbdir, strerror(errno));
exit(RCODE_ERR);
}
strlcpy(maxsession_file, rundir, sizeof(maxsession_file));
strlcat(maxsession_file, "/", sizeof(maxsession_file));
strlcat(maxsession_file, PSERV_MAX_FILE, sizeof(maxsession_file));
/***************************************************************************
Open socket and bind to port.
***************************************************************************/
if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
fprintf(stderr, "%s: socket error: %s\n", prgname, strerror(errno));
exit(RCODE_ERR);
}
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)) != 0) {
fprintf(stderr, "%s: setsockopt failed: %s\n", prgname, strerror(errno));
exit(RCODE_ERR);
}
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(checkport);
if (host[0]) {
struct hostent *hostent;
hostent = gethostbyname(host);
if (hostent == NULL || (hostent->h_addr_list)[0] == NULL) {
fprintf(stderr, "%s: unkown host or IP: %s\n", prgname, host);
exit(RCODE_ERR);
}
memcpy(&(sa.sin_addr.s_addr), hostent->h_addr_list[0], sizeof(sa.sin_addr.s_addr));
} else {
sa.sin_addr.s_addr = htonl(INADDR_ANY);
}
if (bind(s, (struct sockaddr *) &sa, sizeof(sa)) < 0) {
fprintf(stderr, "%s: bind failed: %s\n", prgname, strerror(errno));
exit(RCODE_ERR);
}
/***************************************************************************
Daemonize unless --nodaemon option was given.
***************************************************************************/
if (daemon) daemonize(prgname);
/***************************************************************************
Open log file.
***************************************************************************/
if (xlog_open(PCHECKD_PRG_NAME, logfile, PCHECKD_LOG_MODE) < 0) {
fprintf(stderr, "%s: couldn't open logfile '%s': %s\n", prgname, logfile, strerror(errno));
exit(RCODE_ERR);
}
/* XLOG-DOC:INF:0070:start
* Pcheckd has been started and partially initialized. */
xlog_printf(xlog_inf, 0x0070, "start version='%s' port=%d mailboxdir='%s' emptyload=%d", VERSION, checkport, mbdir, emptyload);
/***************************************************************************
Open pid file.
***************************************************************************/
write_pidfile(rundir, PCHECKD_PID_TEMPL, PCHECKD_PID_FILE);
/***************************************************************************
Setup signal handling.
***************************************************************************/
signal_init(1);
if (num_fork) {
int i;
for (i=0; i<num_fork; i++) {
int pid = fork();
if (pid < 0) {
/* XLOG-DOC:SOS:0155:fork_failed
* The fork () system call in pcheckd failed. */
xlog_printf(xlog_sos, 0x0155, "fork_failed errno=%d errmsg='%s'", errno, strerror(errno));
}
if (pid == 0) {
parent = 0;
break;
}
child_pid[i] = pid;
}
}
/***************************************************************************
Event loop.
***************************************************************************/
load_is_ok = 1;
while (1) {
int n, sig, r;
fd_set readset;
FD_ZERO(&readset);
FD_SET(s, &readset);
r = rel_select(&readset, 1, 0);
load_is_ok = load_ok(emptyload);
if (r < 0) {
/* XLOG-DOC:SOS:0077:select_error
* The select () syscall in the main loop returned an error that is
* not handled. This should never happen. */
xlog_printf(xlog_sos, 0x0077, "select_error errno=%d errmsg='%s'", errno, strerror(errno));
continue;
}
while ((sig = signal_next())) {
switch (sig) {
case SIGCHLD:
pcheckd_reapchildren();
break;
case SIGQUIT: /* fallthrough */
case SIGINT: /* fallthrough */
case SIGTERM: /* fallthrough */
/* XLOG-DOC:INF:0078:shutdown
* pcheckd received a QUIT, INT, TER, or PWR signal and is
* shutting down. */
xlog_printf(xlog_inf, 0x0078, "shutdown");
if (parent) {
unlink_in_rundir(rundir, PCHECKD_PID_TEMPL, PCHECKD_PID_FILE);
for (i=0; i<MAX_PCHECKD_CHILDREN; i++) {
if (child_pid[i] > 0) kill(child_pid[i], SIGTERM);
}
}
exit(RCODE_OK);
case SIGHUP:
/* XLOG-DOC:INF:0144:stat
* Statistics from pcheckd: 'requests' is the number of all
* requests that pcheckd got, 'mailchecks' is the number of
* new format mailcheck requests, 'mailchecks_old' the number
* of old format mailcheck requests, 'empty' the number of
* requests finding an empty mailbox, 'not_empty' the number of
* requests finding a not empty mailbox, 'load_too_high'
* the number of requests answered with that message, and
* 'maxsession_reached' the number of messages send while the
* maximum number of pserv sessions was used. The
* statistics are logged and cleared on SIGHUP. */
xlog_printf(xlog_inf, 0x0144, "stat requests=%ld mailchecks=%ld mailchecks_old=%ld empty=%ld not_empty=%ld load_too_high=%ld maxsession_reached=%ld", num_requests, num_mailchecks, num_mailchecks_old, num_empty, num_not_empty, num_load_too_high, num_maxsession_reached);
num_requests = 0;
num_mailchecks = 0;
num_mailchecks_old = 0;
num_empty = 0;
num_not_empty = 0;
num_load_too_high = 0;
num_maxsession_reached = 0;
xlog_reopen(logfile, PCHECKD_LOG_MODE);
break;
case SIGUSR1:
/* XLOG-DOC:INF:0079:debug_disabled
* pcheckd received a SIGUSR1 and disabled debuglogging. */
xlog_printf(xlog_inf, 0x0079, "debug_disabled");
debug=0;
break;
case SIGUSR2:
/* XLOG-DOC:INF:007a:debug_enabled
* pcheckd received a SIGUSR2 and enabled debuglogging. */
xlog_printf(xlog_inf, 0x007a, "debug_enabled");
debug=1;
break;
default: /* ignore other signals */
break;
}
}
if (r == 0) continue;
n = recvfrom(s, inmsg, MAXLEN_MAILCHECK, 0, &remote, &remotelen);
if (n >= 0) {
inmsg[n] = '\0';
if (n>0 && inmsg[n-1] == '\n') inmsg[--n] = '\0';
if (n>0 && inmsg[n-1] == '\r') inmsg[--n] = '\0';
DEBUG1(DG_MAIN, "main", "recv_msg msg='%s'", inmsg);
if ((n = handle_request(inmsg, outmsg)) > 0) {
DEBUG1(DG_MAIN, "main", "send_msg msg='%s'", outmsg);
(void) strlcat(outmsg, "\n", sizeof(outmsg));
if (sendto(s, outmsg, n+1, 0, &remote, remotelen) != n+1) {
/* XLOG-DOC:ERR:0076:sendto_failed
* Sending the answer of the requested check back to the client
* failed. */
xlog_printf(xlog_err, 0x0076, "sendto_failed errno=%d errmsg='%s'", errno, strerror(errno));
}
}
}
}
}
/** THE END *****************************************************************/
syntax highlighted by Code2HTML, v. 0.9.1