]>
diplodocus.org Git - mdeliver/blob - mdeliver.c
4768c8ab720b94522c3282a100eab32dcc24ec6f
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
14 #include <sys/types.h>
30 #define EX_TEMPFAIL 111
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 /* 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
51 #define save_errno(op) { \
52 int olderrno = errno; \
57 static char ident
[] = "$Id$";
59 #define err(...) _err(true, __LINE__, __VA_ARGS__)
60 #define errx(...) _err(false, __LINE__, __VA_ARGS__)
63 _err(bool want_strerror
, int lineno
, char *format
, ...)
67 fprintf(stderr
, "mdeliver:%d:", lineno
);
70 vfprintf(stderr
, format
, ap
);
83 full_sleep(unsigned seconds
)
86 seconds
= sleep(seconds
);
90 /* Write all of buf, even if write(2) is interrupted. */
92 full_write(int fd
, const char *buf
, size_t nbytes
)
96 /* Loop until nbytes of buf have been written. */
98 /* Keep trying until write succeeds without interruption. */
100 r
= write(fd
, buf
+ w
, nbytes
- w
);
101 } while (r
< 0 && errno
== EINTR
);
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).
118 get_filename(char *fn
)
121 char host
[HOST_NAME_MAX
+ 1];
128 if (gethostname(host
, sizeof(host
)) != 0) {
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';
135 for (try = 0; ; try++) {
141 sprintf(fn
, "tmp/%lu.%lu.%s",
142 (long unsigned)now
, (long unsigned)pid
, host
);
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.
157 errx("Attempted filename exists.");
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<
173 /* buf is passed only as an (early, evil) space optimization). */
175 strip_from(int fd
, char *buf
, char *fn
)
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.
189 save_errno( unlink(fn
) );
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') {
202 save_errno( unlink(fn
) );
206 if (full_write(fd
, buf
, r
) != r
) {
207 save_errno( unlink(fn
) );
208 err("write(%s)", fn
);
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.
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.
222 copy_file(int infd
, int outfd
, char *buf
, size_t bufsize
, char *fn
)
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
);
235 save_errno( unlink(fn
) );
236 err("read(%d, %d)", infd
, bufsize
);
239 if (fsync(outfd
) < 0) {
240 save_errno( unlink(fn
) );
241 err("fsync(%s)", fn
);
244 if (close(outfd
) < 0) {
245 save_errno( unlink(fn
) );
246 err("close(%s)", fn
);
251 deliver(char *maildir
, char *sub
, char *newfn
)
253 char fn
[LEN_FILENAME
];
255 char buf
[BUFSIZ
]; /* Allocated here only as an (early,
256 * evil) space optimization. */
258 if (chdir(maildir
) != 0) {
259 err("chdir(%s)", maildir
);
264 fd
= open(fn
, O_WRONLY
| O_EXCL
| O_CREAT
, 0600);
269 /* From this point, we have a tmp file, so if someting fails we
270 * must try to unlink it before bombing. */
277 buf
= getenv("RPLINE");
279 if (full_write(fd
, buf
, len
) != len
) {
280 save_errno( unlink(fn
) );
281 err("write(%s)", fn
);
283 buf
= getenv("DTLINE");
285 if (full_write(fd
, buf
, len
) != len
) {
286 save_errno( unlink(fn
) );
287 err("write(%s)", fn
);
291 strip_from(fd
, buf
, fn
);
294 copy_file(0, fd
, buf
, BUFSIZ
, fn
);
297 memcpy(newfn
, sub
, 3);
299 if (link(fn
, newfn
) != 0) {
300 save_errno( unlink(fn
) );
301 err("link(%s, %s)", fn
, newfn
);
303 save_errno( unlink(fn
) );
306 /* Run a program to process the new message. */
308 run_processor(char *home
, char *newfn
)
311 char command
[PATH_MAX
];
317 if ((pid
= fork()) > 0) {
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. */
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);
338 fd
= open(newfn
, O_WRONLY
| O_EXCL
| O_CREAT
, 0644);
340 errfile
= fdopen(fd
, "w");
342 fprintf(errfile
, "mdeliver:mdeliver.c:%d:%s: %s\n",
343 __LINE__
, fail
, strerror(olderrno
));
350 if (fail
[0] == 'e') {
356 main(int argc
, char *argv
[])
359 char newfn
[LEN_FILENAME
];
362 if (argv
[1] && argv
[1][0] == '-' && argv
[1][1] == 'v') {
367 if ((home
= getenv("HOME")) == NULL
) {
368 errx("HOME is not set.");
371 if (chdir(home
) != 0) {
372 err("chdir(%s)", home
);
375 if (stat(".mdeliver-processor", &st
) == 0) {
376 deliver("Maildir", "mdp", newfn
);
377 run_processor(home
, newfn
);
379 deliver("Maildir", "new", newfn
);