* 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)
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
}
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
* 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;
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;
|| (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. */
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);
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;
}