]> diplodocus.org Git - mdeliver/blob - mdeliver.c
4768c8ab720b94522c3282a100eab32dcc24ec6f
[mdeliver] / mdeliver.c
1 /* mdeliver.c -- Deliver a message to a Maildir.
2 *
3 * $Id$
4 *
5 * This program is in the public domain.
6 *
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
11 * details.
12 */
13
14 #include <sys/types.h>
15 #include <sys/stat.h>
16
17 #include <errno.h>
18 #include <fcntl.h>
19 #include <limits.h>
20 #include <stdarg.h>
21 #include <stdbool.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <time.h>
26 #include <unistd.h>
27
28 #ifndef EX_TEMPFAIL
29 #ifdef QMAIL
30 #define EX_TEMPFAIL 111
31 #else
32 #include <sysexits.h>
33 #endif
34 #endif
35
36 #if !defined(HOST_NAME_MAX)
37 #if defined MAXHOSTNAMELEN
38 #define HOST_NAME_MAX MAXHOSTNAMELEN
39 #else
40 #define HOST_NAME_MAX 64
41 #endif
42 #endif
43
44 /* holds 2^128 - 1 in decimal, plus '\0' */
45 #define LEN_ULONG 40
46
47 /* 4 for "tmp/", 2 for the two dots ('.'), HOST_NAME_MAX for the
48 * hostname, two LEN_ULONGs for time and pid, and 1 for '\0', . */
49 #define LEN_FILENAME 4 + 2 + HOST_NAME_MAX + LEN_ULONG + LEN_ULONG + 1
50
51 #define save_errno(op) { \
52 int olderrno = errno; \
53 op; \
54 errno = olderrno; \
55 }
56
57 static char ident[] = "$Id$";
58
59 #define err(...) _err(true, __LINE__, __VA_ARGS__)
60 #define errx(...) _err(false, __LINE__, __VA_ARGS__)
61
62 static void
63 _err(bool want_strerror, int lineno, char *format, ...)
64 {
65 va_list ap;
66
67 fprintf(stderr, "mdeliver:%d:", lineno);
68
69 va_start(ap, format);
70 vfprintf(stderr, format, ap);
71 va_end(ap);
72
73 if (want_strerror) {
74 perror("");
75 } else {
76 fputs("\n", stderr);
77 }
78
79 exit(EX_TEMPFAIL);
80 }
81
82 static void
83 full_sleep(unsigned seconds)
84 {
85 while (seconds > 0) {
86 seconds = sleep(seconds);
87 }
88 }
89
90 /* Write all of buf, even if write(2) is interrupted. */
91 static ssize_t
92 full_write(int fd, const char *buf, size_t nbytes)
93 {
94 ssize_t r, w = 0;
95
96 /* Loop until nbytes of buf have been written. */
97 while (w < nbytes) {
98 /* Keep trying until write succeeds without interruption. */
99 do {
100 r = write(fd, buf + w, nbytes - w);
101 } while (r < 0 && errno == EINTR);
102
103 if (r < 0) {
104 return r;
105 }
106
107 w += r;
108 }
109
110 return w;
111 }
112
113 /* Find a filename for the message according to the algorithm
114 * documented in the maildir(5) manual page from qmail
115 * (http://www.qmail.org/qmail-manual-html/man5/maildir.html).
116 */
117 static void
118 get_filename(char *fn)
119 {
120 pid_t pid;
121 char host[HOST_NAME_MAX + 1];
122 int try;
123 time_t now;
124 struct stat st;
125
126 pid = getpid();
127
128 if (gethostname(host, sizeof(host)) != 0) {
129 err("gethostname");
130 }
131 /* http://www.opengroup.org/onlinepubs/009695399/functions/gethostname.html
132 * "it is unspecified whether the returned name is null-terminated" */
133 host[sizeof(host) - 1] = '\0';
134
135 for (try = 0; ; try++) {
136 now = time(NULL);
137 if (now == -1) {
138 err("time");
139 }
140
141 sprintf(fn, "tmp/%lu.%lu.%s",
142 (long unsigned)now, (long unsigned)pid, host);
143
144 if (stat(fn, &st) != 0) {
145 if (errno == ENOENT) {
146 /* Every failure point in this program relies on errno
147 * describing what went wrong. This is the one
148 * failure we *want*, so it is important to restore
149 * errno to 0 (success) before returning.
150 */
151 errno = 0;
152 break;
153 }
154 }
155
156 if (try == 2) {
157 errx("Attempted filename exists.");
158 }
159
160 full_sleep(2);
161 }
162 }
163
164 #ifndef QMAIL
165 /* Strip the non-RFC2822-compliant 'From ' line some busted-ass
166 * mailers (such as Postfix) add as the first line of input. This bit
167 * of historical gunk originated with Berkeley mbox files, which use
168 * this line to indicate the beginning of a message. Postfix should
169 * only add such a line when it is acting as an MDA and delivering to
170 * a Berkeley mbox; the data written to an external MDA should be the
171 * raw RFC2822 message. >sigh<
172 */
173 /* buf is passed only as an (early, evil) space optimization). */
174 static void
175 strip_from(int fd, char *buf, char *fn)
176 {
177 ssize_t r;
178
179 /* Yes, read(2) can be interrupted and read fewer bytes than
180 * requested. However, i'm only asking for five bytes here. I
181 * have tested this program extensively and it handles large
182 * volumes of mail correctly every day on my systems. This holds
183 * up my original opinion that expecting to be able to get five
184 * bytes safely is not an unreasonable demand. If i can't get
185 * five measly bytes, something is probably wrong, so bomb.
186 */
187 r = read(0, buf, 5);
188 if (r != 5) {
189 save_errno( unlink(fn) );
190 err("read(0, 5)");
191 }
192
193 if (strncmp(buf, "From ", 5) == 0) {
194 while ((r = read(0, buf, 1)) > 0
195 || (r == -1 && errno == EINTR)) {
196 if (buf[0] == '\n') {
197 break;
198 }
199 }
200
201 if (r == -1) {
202 save_errno( unlink(fn) );
203 err("read(0, 1)");
204 }
205 } else {
206 if (full_write(fd, buf, r) != r) {
207 save_errno( unlink(fn) );
208 err("write(%s)", fn);
209 }
210 }
211 }
212 #endif
213
214 /* Copy the file from INFD to OUTFD in blocks of BUFSIZE bytes (stored
215 * in BUF). BUF must point to a buffer of at least BUFSIZE bytes;
216 * we're not dealing with null-terminated strings here.
217 *
218 * Note that OUTFD is closed before returning; this is necessary to
219 * ensure the file was fully written on some filesystems, such as NFS.
220 */
221 static void
222 copy_file(int infd, int outfd, char *buf, size_t bufsize, char *fn)
223 {
224 ssize_t r;
225
226 while ((r = read(infd, buf, bufsize)) > 0
227 || (r == -1 && errno == EINTR)) {
228 if (full_write(outfd, buf, r) != r) {
229 save_errno( unlink(fn) );
230 err("write(%s)", fn);
231 }
232 }
233
234 if (r == -1) {
235 save_errno( unlink(fn) );
236 err("read(%d, %d)", infd, bufsize);
237 }
238
239 if (fsync(outfd) < 0) {
240 save_errno( unlink(fn) );
241 err("fsync(%s)", fn);
242 }
243
244 if (close(outfd) < 0) {
245 save_errno( unlink(fn) );
246 err("close(%s)", fn);
247 }
248 }
249
250 static void
251 deliver(char *maildir, char *sub, char *newfn)
252 {
253 char fn[LEN_FILENAME];
254 int fd;
255 char buf[BUFSIZ]; /* Allocated here only as an (early,
256 * evil) space optimization. */
257
258 if (chdir(maildir) != 0) {
259 err("chdir(%s)", maildir);
260 }
261
262 get_filename(fn);
263
264 fd = open(fn, O_WRONLY | O_EXCL | O_CREAT, 0600);
265 if (fd < 0) {
266 err("open(%s)", fn);
267 }
268
269 /* From this point, we have a tmp file, so if someting fails we
270 * must try to unlink it before bombing. */
271
272 #ifdef QMAIL
273 {
274 char *buf;
275 size_t len;
276
277 buf = getenv("RPLINE");
278 len = strlen(buf);
279 if (full_write(fd, buf, len) != len) {
280 save_errno( unlink(fn) );
281 err("write(%s)", fn);
282 }
283 buf = getenv("DTLINE");
284 len = strlen(buf);
285 if (full_write(fd, buf, len) != len) {
286 save_errno( unlink(fn) );
287 err("write(%s)", fn);
288 }
289 }
290 #else
291 strip_from(fd, buf, fn);
292 #endif
293
294 copy_file(0, fd, buf, BUFSIZ, fn);
295
296 strcpy(newfn, fn);
297 memcpy(newfn, sub, 3);
298
299 if (link(fn, newfn) != 0) {
300 save_errno( unlink(fn) );
301 err("link(%s, %s)", fn, newfn);
302 }
303 save_errno( unlink(fn) );
304 }
305
306 /* Run a program to process the new message. */
307 static void
308 run_processor(char *home, char *newfn)
309 {
310 pid_t pid;
311 char command[PATH_MAX];
312 FILE *errfile;
313 char *fail;
314 int olderrno;
315 int fd;
316
317 if ((pid = fork()) > 0) {
318 return;
319 } else if (pid == 0) {
320 snprintf(command, sizeof(command), "%s/%s",
321 home, ".mdeliver-processor");
322 execl(command, command, newfn, NULL);
323 if (errno == ENOENT) {
324 /* Not having a processor is not an error. */
325 _exit(0);
326 }
327 fail = "exec";
328 } else {
329 fail = "fork";
330 }
331 olderrno = errno;
332
333 /* If we got here fork or exec failed, so try to write an error
334 * message where later mail processors can find it. We really
335 * can't do anything about it if this fails. */
336 memcpy(newfn, "err", 3);
337 mkdir("err", 0700);
338 fd = open(newfn, O_WRONLY | O_EXCL | O_CREAT, 0644);
339 if (fd >= 0) {
340 errfile = fdopen(fd, "w");
341 if (errfile) {
342 fprintf(errfile, "mdeliver:mdeliver.c:%d:%s: %s\n",
343 __LINE__, fail, strerror(olderrno));
344 fflush(errfile);
345 }
346 fsync(fd);
347 close(fd);
348 }
349
350 if (fail[0] == 'e') {
351 _exit(0);
352 }
353 }
354
355 int
356 main(int argc, char *argv[])
357 {
358 char *home;
359 char newfn[LEN_FILENAME];
360 struct stat st;
361
362 if (argv[1] && argv[1][0] == '-' && argv[1][1] == 'v') {
363 puts(ident);
364 return 0;
365 }
366
367 if ((home = getenv("HOME")) == NULL) {
368 errx("HOME is not set.");
369 }
370
371 if (chdir(home) != 0) {
372 err("chdir(%s)", home);
373 }
374
375 if (stat(".mdeliver-processor", &st) == 0) {
376 deliver("Maildir", "mdp", newfn);
377 run_processor(home, newfn);
378 } else {
379 deliver("Maildir", "new", newfn);
380 }
381
382 return 0;
383 }