/*****************************************************************************
POPular -- A POP3 server and proxy for large mail systems
$Id: pdeliver.c,v 1.17 2001/04/30 12:20:34 sqrt Exp $
http://www.remote.org/jochen/mail/popular/
******************************************************************************
Copyright (C) 1999-2001 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"
int debug;
/*****************************************************************************
get_dir_size()
Get size of a directory.
*****************************************************************************/
int
get_dir_size(const char *name, int *num)
{
DIR *dir;
struct dirent *de;
struct stat sb;
int sum=0;
char *p, *pp, filename[MAXLEN_MAILFILE+1];
dir = opendir(name);
if (!dir) {
/* XLOG-DOC:ADM:0056:opendir_failed
* The opendir() system call failed while trying to determine the
* mailbox size. */
xlog_printf(xlog_adm, 0x0056, "opendir_failed dir='%s' errno=%d errmsg='%s'", name, errno, strerror(errno));
exit(RCODE_ERR);
}
while ((de = readdir(dir))) {
int size = 0;
if (de->d_name[0] == '.') continue; /* don't count dot files */
/* try to get message size from file name. The format must be as
follows: The name must end in a _ (underscore) and the file size. */
if ((p = strrchr(de->d_name, '_'))) {
long l=strtol(p+1, &pp, 10);
if (! *pp) size = (int)l;
}
if (size == 0) {
(void) strlcpy(filename, name, sizeof(filename));
(void) strlcat(filename, "/", sizeof(filename));
(void) strlcat(filename, de->d_name, sizeof(filename));
if (stat(filename, &sb) < 0) {
/* XLOG-DOC:ADM:0053:stat_failed
* The stat() of a mail message file failed. A size of 0 will be
* used. The quota can't be calculated accurately. */
xlog_printf(xlog_adm, 0x0053, "stat_failed file='%s' errno=%d errmsg='%s'", filename, errno, strerror(errno));
} else {
size = sb.st_size;
}
}
sum += size;
(*num)++;
DEBUG3(DG_MAIN, "get_dir_size", "sum=%d size=%d num=%d", sum, size, *num);
}
closedir(dir);
return sum;
}
/*****************************************************************************
check_quota()
*****************************************************************************/
void
check_quota(int size, int count)
{
int s, c=0;
if ((size == 0) && (count == 0)) return;
size *= 1024*1024;
s = get_dir_size("cur", &c) + get_dir_size("new", &c);
if ((size > 0) && (s >= size)) {
/* XLOG-DOC:ERR:0057:quota_exceeded
* The quota on a mailbox was exceeded. No mail will be delivered. */
xlog_printf(xlog_err, 0x0057, "quota_exceeded size=%d quota=%d", s, size);
exit(RCODE_QUOTA);
}
if ((count > 0) && (c >= count)) {
/* XLOG-DOC:ERR:0058:quota_exceeded
* The quota on a mailbox was exceeded. No mail will be delivered. */
xlog_printf(xlog_err, 0x0058, "quota_exceeded count=%d quota=%d", c, count);
exit(RCODE_QUOTA);
}
DEBUG2(DG_MAIN, "check_quota", "quota_info count=%d size=%d", c, s);
return;
}
/*****************************************************************************
write_mail()
returns size (including CRLF but without . stuffing)
*****************************************************************************/
int
write_mail(FILE *in, FILE *out, mail_format_t format)
{
char buf[1024+1];
int n=0, l, contline=0;
while (fgets(buf, sizeof(buf)-1, in)) {
if ((format == mfNET) && (buf[0] == '.') &&
(buf[1] == '\r') &&
(buf[2] == '\n') &&
(buf[3] == 0)) break;
l = strlen(buf);
if (buf[l-1] == '\n') {
contline = 0;
} else {
if ((format == mfNET) && (contline == 0) && (buf[0] == '.')) {
if (fputs(buf+1, out) < 0) return -1;
n += l-1;
} else {
if (fputs(buf, out) < 0) return -1;
n += l;
}
contline = 1;
continue;
}
switch (format) {
case mfLF:
buf[l-1] = '\r';
buf[l] = '\n';
buf[l+1] = '\0';
if (fputs(buf, out) < 0) return -1;
n += l+1;
break;
case mfCRLF:
if (fputs(buf, out) < 0) return -1;
n += l;
break;
case mfNET:
if (buf[0] == '.') {
if (fputs(buf+1, out) < 0) return -1;
n += l-1;
} else {
if (fputs(buf, out) < 0) return -1;
n += l;
}
break;
default:
return -1;
}
}
return n;
}
/*****************************************************************************
print_usage()
*****************************************************************************/
void
print_usage(char *prg)
{
printf("Usage: %s [OPTION] ... [MAILBOX]\n", prg);
printf("Deliver mail from stdin into a mailbox.\n");
printf("Options:\n");
printf(" -f, --format=FORMAT format of mail data (default: `LF')\n");
printf(" -i, --id=ID id for logging\n");
printf(" -l, --logfile=FILE name of log file (default: `%s')\n", PDELIVER_LOG_FILE);
printf(" -m, --mailboxdir=DIR set mailbox directory (default: `%s')\n", POPDIR);
printf(" -q, --quota=QUOTA max size of mailbox in MB (default: no limit)\n");
printf(" -Q, --maxnum=NUM max number of mails in mailbox (default: no limit)\n");
printf(" -d, --debug enable debug logging\n");
printf(" --help print this help message\n");
printf(" --version print version information\n");
printf("\n");
printf("Formats:\n");
printf(" LF line ends are LF (UNIX format)\n");
printf(" CRLF line ends are CRLF\n");
printf(" NET line ends are CRLF and leading dots (.) are quoted\n");
printf(" (SMTP/POP wire format)\n");
printf("\n");
}
/*****************************************************************************
main
*****************************************************************************/
int
main(int argc, char *argv[])
{
int fd, c, size;
int quota_count, quota_size;
char *logfile, *mbdir, *mailbox, *id;
mail_format_t format;
char tmpbuf[MAXLEN_MAILFILE+1];
char newbuf[MAXLEN_MAILFILE+1];
FILE *tmpfile;
char *prgname = argv[0];
static struct option long_options[] = {
{ "help", no_argument, NULL, 0 },
{ "version", no_argument, NULL, 0 },
{ "format", required_argument, NULL, 'f' },
{ "id", required_argument, NULL, 'i' },
{ "logfile", required_argument, NULL, 'l' },
{ "mailboxdir", required_argument, NULL, 'm' },
{ "quota", required_argument, NULL, 'q' },
{ "maxnum", required_argument, NULL, 'Q' },
{ "debug", no_argument, NULL, 'd' },
{ NULL, 0, NULL, 0 }
};
/***************************************************************************
Defaults for parameters
***************************************************************************/
mbdir = POPDIR;
logfile = PDELIVER_LOG_FILE;
id = NULL;
format = mfLF;
quota_size = 0;
quota_count = 0;
debug = 0;
/***************************************************************************
Parse command line options
***************************************************************************/
while (1) {
int option_index = 0;
c = getopt_long(argc, argv, "f:i:l:m:q:Q:d", long_options, &option_index);
if (c == -1) break;
switch (c) {
case 0:
if (! strcmp(long_options[option_index].name, "version")) {
printf("pdeliver - 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 'q': /* quota mailbox size */
quota_size = get_int(optarg, 0, 10000, -1, -2, 0);
if (quota_size < 0) {
fprintf(stderr, "%s: quota must be between 1 an 10000 MByte or 0 for unlimited.\n", prgname);
exit(RCODE_CMDLINE);
}
break;
case 'Q': /* quota mail number */
quota_count = get_int(optarg, 0, 10000, -1, -2, 0);
if (quota_count < 0) {
fprintf(stderr, "%s: max number of mails must be between 1 an 10000 or 0 for unlimited.\n", prgname);
exit(RCODE_CMDLINE);
}
break;
case 'f': /* format */
if (!strcmp(optarg, "CRLF")) format = mfCRLF;
else if (!strcmp(optarg, "LF")) format = mfLF;
else if (!strcmp(optarg, "NET")) format = mfNET;
else {
fprintf(stderr, "%s: unknown format: `%s'\n", prgname, optarg);
exit(RCODE_CMDLINE);
}
break;
case 'i': /* id */
id = optarg;
break;
case 'l': /* logfile */
logfile = optarg;
break;
case 'm':
mbdir = optarg;
break;
case 'd':
debug = DG_ALL;
break;
default:
fprintf(stderr, "Try `%s --help' for more information.\n", prgname);
exit(RCODE_CMDLINE);
}
}
if (optind == argc-1) {
mailbox = argv[optind];
} else if (optind < argc) {
fprintf(stderr, "%s: extra arguments.\nTry `%s --help' for more information.\n", prgname, prgname);
exit(RCODE_CMDLINE);
} else {
fprintf(stderr, "%s: mailbox argument missing.\nTry `%s --help' for more information.\n", prgname, prgname);
exit(RCODE_CMDLINE);
}
/***************************************************************************
Start logging.
***************************************************************************/
xlog_open(PDELIVER_PRG_NAME, logfile, PDELIVER_LOG_MODE);
if (id) xlog_set_id(id);
/***************************************************************************
Prepare mailbox directory and check quota.
***************************************************************************/
if (chdir(mbdir) != 0) {
/* XLOG-DOC:SOS:0059:mailboxdir_error
* Pdeliver can't change into the mailbox directory. */
xlog_printf(xlog_sos, 0x0059, "mailboxdir_error dir='%s' errno=%d errmsg='%s'", mbdir, errno, strerror(errno));
exit(RCODE_ERR);
}
if (! mailbox_prepdir(mailbox)) {
exit(RCODE_ERR);
}
check_quota(quota_size, quota_count);
/***************************************************************************
Create filename for tmp file, open it and write mail into it.
***************************************************************************/
snprintf(tmpbuf, sizeof(tmpbuf), "tmp/%lu.%d", time(NULL), getpid());
tmpbuf[sizeof(tmpbuf)-1] = 0;
if ((fd = open(tmpbuf, O_WRONLY|O_CREAT|O_EXCL, MAILFILEMODE)) < 0) {
/* XLOG-DOC:ADM:005a:open_tmpfile_failed
* Opening the tmp file for a new mail message failed. */
xlog_printf(xlog_adm, 0x005a, "open_tmpfile_failed file='%s' errno=%d errmsg='%s'", tmpbuf, errno, strerror(errno));
exit(RCODE_ERR);
}
if ((tmpfile = fdopen(fd, "w")) == NULL) {
/* XLOG-DOC:ADM:005b:fdopen_failed
* fdopen() on new tmp file for mail message failed. */
xlog_printf(xlog_adm, 0x005b, "fdopen_failed errno=%d errmsg='%s'", errno, strerror(errno));
exit(RCODE_ERR);
}
if ((size = write_mail(stdin, tmpfile, format)) < 0) {
/* XLOG-DOC:ADM:005c:write_mail_failed
* A write to the tmp file for a new mail message failed. */
xlog_printf(xlog_adm, 0x005c, "write_mail_failed errno=%d errmsg='%s'", errno, strerror(errno));
exit(RCODE_ERR);
}
if (fclose(tmpfile) != 0) {
/* XLOG-DOC:ADM:005d:close_failed
* The close on the tmp file for a new mail message failed. */
xlog_printf(xlog_adm, 0x005d, "close_failed errno=%d errmsg='%s'", errno, strerror(errno));
exit(RCODE_ERR);
}
/***************************************************************************
Create filename for new file and rename mail file.
***************************************************************************/
#ifdef STORE_MAIL_SIZE_IN_NAME
snprintf(newbuf, sizeof(newbuf), "new/%s_%d", tmpbuf+4, size);
#else
strlcpy(newbuf, tmpbuf, sizeof(newbuf));
newbuf[0] = 'n'; newbuf[1] = 'e'; newbuf[2] = 'w';
#endif
if (rename(tmpbuf, newbuf) != 0) {
/* XLOG-DOC:ADM:0054:rename_failed
* After the mail message has been written to the 'tmp' directory, it
* can't be renamed to the 'new' directory. */
xlog_printf(xlog_adm, 0x0054, "rename_failed filename='%s' errno=%d errmsg='%s'", tmpbuf, errno, strerror(errno));
exit(RCODE_ERR);
}
/* XLOG-DOC:INF:0055:delivered
* A mail has been delivered successfully. */
xlog_printf(xlog_inf, 0x0055, "delivered box='%s' size=%d", mailbox, size);
exit(RCODE_OK);
}
/** THE END *****************************************************************/
syntax highlighted by Code2HTML, v. 0.9.1