]>
diplodocus.org Git - mdeliver/blob - mdeliver.c
d6e49e7b80c7e51f0750a411ba5d2538ce7723ad
1 /* mdeliver.c -- Deliver a message to a Maildir.
5 * This program is in the public domain.
7 * mdeliver is a simple, robust MDA. It delivers a single message
8 * from stdin to the user's Maildir and executes (in the background) a
9 * single program with the message filename as the first argument.
10 * Additional arguments may be specified; see main() below for
13 * This program does not conform strictly to POSIX. However, the two
14 * nonstandard interfaces it relies upon (<err.h> and <sysexits.h>)
15 * are widespread and easily enough replaced if necessary.
18 #include <sys/types.h>
31 #ifndef MDELIVER_EXITCODE
33 #define MDELIVER_EXITCODE EX_TEMPFAIL
36 #if !defined(HOST_NAME_MAX)
37 #if defined MAXHOSTNAMELEN
38 #define HOST_NAME_MAX MAXHOSTNAMELEN
40 #define HOST_NAME_MAX 64
44 /* holds 2^128 - 1 in decimal, plus '\0' */
47 /* 1 for '\0', 4 for "tmp/", 2 for the two dots ('.'), HOST_NAME_MAX
48 * for the hostname, and two LEN_ULONGs for time and pid. */
49 #define TMPNAMLEN 1 + 4 + 2 + HOST_NAME_MAX + LEN_ULONG + LEN_ULONG
51 /* Use this macro to perform some operation and saving the value errno
52 * had before that operation.
54 #define save_errno(op) { \
55 int olderrno = errno; \
60 static char ident
[] = "$Id$";
62 /* Write all of buf, even if write(2) is interrupted. */
64 full_write(int fd
, const char *buf
, size_t nbytes
)
68 /* Loop until nbytes of buf have been written. */
70 /* Keep trying until write succeeds without interruption. */
72 r
= write(fd
, buf
+ w
, nbytes
- w
);
73 } while (r
< 0 && errno
== EINTR
);
85 /* Find a filename for the message according to the algorithm
86 * documented in the maildir(5) manual page from qmail
87 * (http://www.qmail.org/qmail-manual-html/man5/maildir.html).
90 get_filename(char *fn
)
93 char host
[HOST_NAME_MAX
+ 1];
100 if (gethostname(host
, sizeof(host
)) != 0) {
101 err(MDELIVER_EXITCODE
, "Failed gethostname");
104 for (try = 0; ; try++) {
107 err(MDELIVER_EXITCODE
, "Failed time()");
110 sprintf(fn
, "tmp/%lu.%lu.%s",
111 (long unsigned)now
, (long unsigned)pid
, host
);
113 if (stat(fn
, &statbuf
) != 0) {
114 if (errno
== ENOENT
) {
115 /* Every failure point in this program relies on errno
116 * describing what went wrong. This is the one
117 * failure we *want*, so it is important to restore
118 * errno to 0 (success) before returning.
126 errx(MDELIVER_EXITCODE
, "Attempted filename exists.");
133 /* Strip the non-RFC2822-compliant 'From ' line some busted-ass
134 * mailers (such as Postfix) add as the first line of input. This bit
135 * of historical gunk originated with Berkeley mbox files, which use
136 * this line to indicate the beginning of a message. Postfix should
137 * only add such a line when it is acting as an MDA and delivering to
138 * a Berkeley mbox; the data written to an external MDA should be the
139 * raw RFC2822 message. >sigh<
141 /* buf is passed only as an (early, evil) space optimization). */
143 strip_from(int fd
, char *buf
, char *fn
)
147 /* Yes, read(2) can be interrupted and read fewer bytes than
148 * requested. However, i'm only asking for five bytes here. I
149 * have tested this program extensively and it handles large
150 * volumes of mail correctly every day on my systems. This holds
151 * up my original opinion that expecting to be able to get five
152 * bytes safely is not an unreasonable demand. If i can't get
153 * five measly bytes, something is probably wrong, so bomb.
155 r
= read(STDIN_FILENO
, buf
, 5);
157 save_errno( unlink(fn
) );
158 err(MDELIVER_EXITCODE
, "Failed read from stdin");
161 if (strncmp(buf
, "From ", 5) == 0) {
162 while ((r
= read(STDIN_FILENO
, buf
, 1)) > 0
163 || (r
== -1 && errno
== EINTR
)) {
164 if (buf
[0] == '\n') {
170 save_errno( unlink(fn
) );
171 err(MDELIVER_EXITCODE
, "Failed read from stdin");
174 if (full_write(fd
, buf
, r
) != r
) {
175 save_errno( unlink(fn
) );
176 err(MDELIVER_EXITCODE
, "Failed write(%s)", fn
);
181 /* Copy the file from INFD to OUTFD in blocks of BUFSIZE bytes (stored
182 * in BUF). BUF must point to a buffer of at least BUFSIZE bytes;
183 * we're not dealing with null-terminated strings here.
185 * Note that OUTFD is closed before returning; this is necessary to
186 * ensure the file was fully written on some filesystems, such as NFS.
189 copy_file(int infd
, int outfd
, char *buf
, size_t bufsize
, char *fn
)
193 while ((r
= read(infd
, buf
, bufsize
)) > 0
194 || (r
== -1 && errno
== EINTR
)) {
195 if (full_write(outfd
, buf
, r
) != r
) {
196 save_errno( unlink(fn
) );
197 err(MDELIVER_EXITCODE
, "Failed write(%s)", fn
);
202 save_errno( unlink(fn
) );
203 err(MDELIVER_EXITCODE
, "Failed read");
206 if (fsync(outfd
) < 0) {
207 save_errno( unlink(fn
) );
208 err(MDELIVER_EXITCODE
, "Failed fsync(%s)", fn
);
211 if (close(outfd
) < 0) {
212 save_errno( unlink(fn
) );
213 err(MDELIVER_EXITCODE
, "Failed close(%s)", fn
);
218 deliver(char *maildir
, char *newfn
)
222 char buf
[BUFSIZ
]; /* Allocated here only as an (early,
223 * evil) space optimization. */
225 if (chdir(maildir
) != 0) {
226 err(MDELIVER_EXITCODE
, "Failed chdir(%s)", maildir
);
231 fd
= open(fn
, O_WRONLY
| O_EXCL
| O_CREAT
, 0600);
233 err(MDELIVER_EXITCODE
, "Failed open(%s)", fn
);
236 /* From this point, we have a tmp file, so if someting fails we
237 * must try to unlink it before bombing. */
239 strip_from(fd
, buf
, fn
);
240 copy_file(STDIN_FILENO
, fd
, buf
, BUFSIZ
, fn
);
243 memcpy(newfn
, "new", 3);
245 tmpfd
= open(newfn
, O_WRONLY
| O_EXCL
| O_CREAT
, 0600);
247 /* XXX: what error message to use? */
248 err(MDELIVER_EXITCODE
, "Failed open(%s)", newfn
);
252 if (rename(fn
, newfn
) != 0) {
253 save_errno( unlink(newfn
) );
254 save_errno( unlink(fn
) );
255 err(MDELIVER_EXITCODE
, "Failed rename(%s, new)", fn
);
259 /* Run a program to process the new message. */
261 run_processor(char *home
, char *newfn
)
264 char command
[PATH_MAX
];
270 if ((pid
= fork()) > 0) {
272 } else if (pid
== 0) {
273 snprintf(command
, sizeof(command
), "%s/%s",
274 home
, ".mdeliver-processor");
275 execl(command
, command
, newfn
, NULL
);
276 if (errno
== ENOENT
) {
277 /* Not having a processor is not an error. */
286 /* If we got here fork or exec failed, so try to write an error
287 * message where later mail processors can find it. We really
288 * can't do anything about it if this fails. */
289 memcpy(newfn
, "err", 3);
291 fd
= open(newfn
, O_WRONLY
| O_EXCL
| O_CREAT
, 0644);
293 errfile
= fdopen(fd
, "w");
295 fprintf(errfile
, "%s:%d\n", fail
, olderrno
);
302 if (fail
[0] == 'e') {
308 main(int argc
, char *argv
[])
311 char newfn
[TMPNAMLEN
];
313 if (argv
[1] && argv
[1][0] == '-' && argv
[1][1] == 'v') {
318 if (!(home
= getenv("HOME"))) {
319 err(MDELIVER_EXITCODE
, "Failed getenv(HOME)");
322 if (chdir(home
) != 0) {
323 err(MDELIVER_EXITCODE
, "Failed chdir(HOME)");
326 deliver("Maildir", newfn
);
328 run_processor(home
, newfn
);
330 /* Always return 0 because deliver exits if it fails, and if it
331 * succeeds we cannot return a failure code to the MTA. */