/***************************************************************************** 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 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" 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 *****************************************************************/