From 4b409c7fc9029ba2e279bd689c29f4d46ca1a892 Mon Sep 17 00:00:00 2001 From: epg <> Date: Thu, 30 Mar 2006 01:59:47 +0000 Subject: [PATCH] Support qmail if the QMAIL macro is defined. This entails exiting 111 on failure, not having to worry about dumbass /^From /, and handling RPLINE and DTLINE. Say EX_TEMPFAIL instead of MDELIVER_EXITCODE. s/TMPNAMLEN/LEN_FILENAME/; it's not just used for the tmp file. Random cleanup: - drop some redundant comments - s/statbuf/st/ - s/STDIN_FILENO/0/ - cleanup error messages - other style nits Drop and implement custom err and errx, which doesn't need to be passed an exit code. Implement and use full_sleep to really sleep the specified time. (get_filename): Don't break when gethostname truncates. (strip_from): Don't even compile this for QMAIL. (deliver): Take a new parameter, the subdirectory to deliver to, instead of "new". #ifdef QMAIL, write RPLINE and DTLINE to tmp file before copying stdin. (deliver): Also had a huge problem: it was using the logic i came up with for minc, to open an empty file for the target and then rename onto that. That's fine for mh, which already loses mail this way (once i switch refile to be like minc this will be fine), but since other Maildir MDAs follow the official algorithm, this could clobber mail. Switch to the official algorithm. (run_processor): Use better error message format. (main): If .mdeliver-processor exists, pass deliver "mdb" as the subdirectory and then call run_processor, else pass "new". --- mdeliver.c | 160 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 105 insertions(+), 55 deletions(-) diff --git a/mdeliver.c b/mdeliver.c index d6e49e7..4768c8a 100644 --- a/mdeliver.c +++ b/mdeliver.c @@ -9,28 +9,28 @@ * single program with the message filename as the first argument. * Additional arguments may be specified; see main() below for * details. - * - * This program does not conform strictly to POSIX. However, the two - * nonstandard interfaces it relies upon ( and ) - * are widespread and easily enough replaced if necessary. */ #include #include -#include #include #include #include +#include +#include #include #include #include #include #include -#ifndef MDELIVER_EXITCODE +#ifndef EX_TEMPFAIL +#ifdef QMAIL +#define EX_TEMPFAIL 111 +#else #include -#define MDELIVER_EXITCODE EX_TEMPFAIL +#endif #endif #if !defined(HOST_NAME_MAX) @@ -44,13 +44,10 @@ /* holds 2^128 - 1 in decimal, plus '\0' */ #define LEN_ULONG 40 -/* 1 for '\0', 4 for "tmp/", 2 for the two dots ('.'), HOST_NAME_MAX - * for the hostname, and two LEN_ULONGs for time and pid. */ -#define TMPNAMLEN 1 + 4 + 2 + HOST_NAME_MAX + LEN_ULONG + LEN_ULONG +/* 4 for "tmp/", 2 for the two dots ('.'), HOST_NAME_MAX for the + * hostname, two LEN_ULONGs for time and pid, and 1 for '\0', . */ +#define LEN_FILENAME 4 + 2 + HOST_NAME_MAX + LEN_ULONG + LEN_ULONG + 1 -/* Use this macro to perform some operation and saving the value errno - * had before that operation. - */ #define save_errno(op) { \ int olderrno = errno; \ op; \ @@ -59,6 +56,37 @@ static char ident[] = "$Id$"; +#define err(...) _err(true, __LINE__, __VA_ARGS__) +#define errx(...) _err(false, __LINE__, __VA_ARGS__) + +static void +_err(bool want_strerror, int lineno, char *format, ...) +{ + va_list ap; + + fprintf(stderr, "mdeliver:%d:", lineno); + + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); + + if (want_strerror) { + perror(""); + } else { + fputs("\n", stderr); + } + + exit(EX_TEMPFAIL); +} + +static void +full_sleep(unsigned seconds) +{ + while (seconds > 0) { + seconds = sleep(seconds); + } +} + /* Write all of buf, even if write(2) is interrupted. */ static ssize_t full_write(int fd, const char *buf, size_t nbytes) @@ -93,24 +121,27 @@ get_filename(char *fn) char host[HOST_NAME_MAX + 1]; int try; time_t now; - struct stat statbuf; + struct stat st; pid = getpid(); if (gethostname(host, sizeof(host)) != 0) { - err(MDELIVER_EXITCODE, "Failed gethostname"); + err("gethostname"); } + /* http://www.opengroup.org/onlinepubs/009695399/functions/gethostname.html + * "it is unspecified whether the returned name is null-terminated" */ + host[sizeof(host) - 1] = '\0'; for (try = 0; ; try++) { now = time(NULL); if (now == -1) { - err(MDELIVER_EXITCODE, "Failed time()"); + err("time"); } sprintf(fn, "tmp/%lu.%lu.%s", (long unsigned)now, (long unsigned)pid, host); - if (stat(fn, &statbuf) != 0) { + if (stat(fn, &st) != 0) { if (errno == ENOENT) { /* Every failure point in this program relies on errno * describing what went wrong. This is the one @@ -123,13 +154,14 @@ get_filename(char *fn) } if (try == 2) { - errx(MDELIVER_EXITCODE, "Attempted filename exists."); + errx("Attempted filename exists."); } - sleep(2); + full_sleep(2); } } +#ifndef QMAIL /* Strip the non-RFC2822-compliant 'From ' line some busted-ass * mailers (such as Postfix) add as the first line of input. This bit * of historical gunk originated with Berkeley mbox files, which use @@ -152,14 +184,14 @@ strip_from(int fd, char *buf, char *fn) * bytes safely is not an unreasonable demand. If i can't get * five measly bytes, something is probably wrong, so bomb. */ - r = read(STDIN_FILENO, buf, 5); + r = read(0, buf, 5); if (r != 5) { save_errno( unlink(fn) ); - err(MDELIVER_EXITCODE, "Failed read from stdin"); + err("read(0, 5)"); } if (strncmp(buf, "From ", 5) == 0) { - while ((r = read(STDIN_FILENO, buf, 1)) > 0 + while ((r = read(0, buf, 1)) > 0 || (r == -1 && errno == EINTR)) { if (buf[0] == '\n') { break; @@ -168,15 +200,16 @@ strip_from(int fd, char *buf, char *fn) if (r == -1) { save_errno( unlink(fn) ); - err(MDELIVER_EXITCODE, "Failed read from stdin"); + err("read(0, 1)"); } } else { if (full_write(fd, buf, r) != r) { save_errno( unlink(fn) ); - err(MDELIVER_EXITCODE, "Failed write(%s)", fn); + err("write(%s)", fn); } } } +#endif /* Copy the file from INFD to OUTFD in blocks of BUFSIZE bytes (stored * in BUF). BUF must point to a buffer of at least BUFSIZE bytes; @@ -194,66 +227,80 @@ copy_file(int infd, int outfd, char *buf, size_t bufsize, char *fn) || (r == -1 && errno == EINTR)) { if (full_write(outfd, buf, r) != r) { save_errno( unlink(fn) ); - err(MDELIVER_EXITCODE, "Failed write(%s)", fn); + err("write(%s)", fn); } } if (r == -1) { save_errno( unlink(fn) ); - err(MDELIVER_EXITCODE, "Failed read"); + err("read(%d, %d)", infd, bufsize); } if (fsync(outfd) < 0) { save_errno( unlink(fn) ); - err(MDELIVER_EXITCODE, "Failed fsync(%s)", fn); + err("fsync(%s)", fn); } if (close(outfd) < 0) { save_errno( unlink(fn) ); - err(MDELIVER_EXITCODE, "Failed close(%s)", fn); + err("close(%s)", fn); } } static void -deliver(char *maildir, char *newfn) +deliver(char *maildir, char *sub, char *newfn) { - char fn[TMPNAMLEN]; - int fd, tmpfd; + char fn[LEN_FILENAME]; + int fd; char buf[BUFSIZ]; /* Allocated here only as an (early, * evil) space optimization. */ if (chdir(maildir) != 0) { - err(MDELIVER_EXITCODE, "Failed chdir(%s)", maildir); + err("chdir(%s)", maildir); } get_filename(fn); fd = open(fn, O_WRONLY | O_EXCL | O_CREAT, 0600); if (fd < 0) { - err(MDELIVER_EXITCODE, "Failed open(%s)", fn); + err("open(%s)", fn); } /* From this point, we have a tmp file, so if someting fails we * must try to unlink it before bombing. */ +#ifdef QMAIL + { + char *buf; + size_t len; + + buf = getenv("RPLINE"); + len = strlen(buf); + if (full_write(fd, buf, len) != len) { + save_errno( unlink(fn) ); + err("write(%s)", fn); + } + buf = getenv("DTLINE"); + len = strlen(buf); + if (full_write(fd, buf, len) != len) { + save_errno( unlink(fn) ); + err("write(%s)", fn); + } + } +#else strip_from(fd, buf, fn); - copy_file(STDIN_FILENO, fd, buf, BUFSIZ, fn); +#endif - strcpy(newfn, fn); - memcpy(newfn, "new", 3); + copy_file(0, fd, buf, BUFSIZ, fn); - tmpfd = open(newfn, O_WRONLY | O_EXCL | O_CREAT, 0600); - if (tmpfd < 0) { - /* XXX: what error message to use? */ - err(MDELIVER_EXITCODE, "Failed open(%s)", newfn); - } - close(tmpfd); + strcpy(newfn, fn); + memcpy(newfn, sub, 3); - if (rename(fn, newfn) != 0) { - save_errno( unlink(newfn) ); + if (link(fn, newfn) != 0) { save_errno( unlink(fn) ); - err(MDELIVER_EXITCODE, "Failed rename(%s, new)", fn); + err("link(%s, %s)", fn, newfn); } + save_errno( unlink(fn) ); } /* Run a program to process the new message. */ @@ -292,7 +339,8 @@ run_processor(char *home, char *newfn) if (fd >= 0) { errfile = fdopen(fd, "w"); if (errfile) { - fprintf(errfile, "%s:%d\n", fail, olderrno); + fprintf(errfile, "mdeliver:mdeliver.c:%d:%s: %s\n", + __LINE__, fail, strerror(olderrno)); fflush(errfile); } fsync(fd); @@ -308,26 +356,28 @@ int main(int argc, char *argv[]) { char *home; - char newfn[TMPNAMLEN]; + char newfn[LEN_FILENAME]; + struct stat st; if (argv[1] && argv[1][0] == '-' && argv[1][1] == 'v') { puts(ident); return 0; } - if (!(home = getenv("HOME"))) { - err(MDELIVER_EXITCODE, "Failed getenv(HOME)"); + if ((home = getenv("HOME")) == NULL) { + errx("HOME is not set."); } if (chdir(home) != 0) { - err(MDELIVER_EXITCODE, "Failed chdir(HOME)"); + err("chdir(%s)", home); } - deliver("Maildir", newfn); - - run_processor(home, newfn); + if (stat(".mdeliver-processor", &st) == 0) { + deliver("Maildir", "mdp", newfn); + run_processor(home, newfn); + } else { + deliver("Maildir", "new", newfn); + } - /* Always return 0 because deliver exits if it fails, and if it - * succeeds we cannot return a failure code to the MTA. */ return 0; } -- 2.56.0