]> diplodocus.org Git - mdeliver/commitdiff
Support qmail if the QMAIL macro is defined. This entails exiting 111
authorepg <>
Thu, 30 Mar 2006 01:59:47 +0000 (01:59 +0000)
committerepg <>
Thu, 30 Mar 2006 01:59:47 +0000 (01:59 +0000)
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 <err.h> 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

index d6e49e7..4768c8a 100644 (file)
@@ -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 (<err.h> and <sysexits.h>)
- * are widespread and easily enough replaced if necessary.
  */
 
 #include <sys/types.h>
 #include <sys/stat.h>
 
-#include <err.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <limits.h>
+#include <stdarg.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
 #include <unistd.h>
 
-#ifndef MDELIVER_EXITCODE
+#ifndef EX_TEMPFAIL
+#ifdef QMAIL
+#define EX_TEMPFAIL 111
+#else
 #include <sysexits.h>
-#define MDELIVER_EXITCODE EX_TEMPFAIL
+#endif
 #endif
 
 #if !defined(HOST_NAME_MAX)
 /* 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;                                                                 \
 
 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;
 }