]> diplodocus.org Git - mdeliver/blob - mdeliver.c
54fa5da6f29a165cbca55c278960d153548d6839
[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
8 #include <sys/types.h>
9 #include <sys/stat.h>
10
11 #include <err.h>
12 #include <errno.h>
13 #include <fcntl.h>
14 #include <limits.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <sysexits.h>
19 #include <time.h>
20 #include <unistd.h>
21
22 #if !defined(HOST_NAME_MAX)
23 #if defined MAXHOSTNAMELEN
24 #define HOST_NAME_MAX MAXHOSTNAMELEN
25 #else
26 #define HOST_NAME_MAX 64
27 #endif
28 #endif
29
30 /* holds 2^128 - 1 in decimal, plus '\0' */
31 #define LEN_ULONG 40
32
33 /* 1 for '\0', 4 for "tmp/", HOST_NAME_MAX for the hostname, and two
34 * LEN_ULONGs for time and pid. */
35 #define TMPNAMLEN 1 + 4 + HOST_NAME_MAX + LEN_ULONG + LEN_ULONG
36
37 /* Write all of buf, even if write(2) is interrupted. */
38 static ssize_t
39 full_write(int fd, const char *buf, size_t nbytes)
40 {
41 ssize_t r, w = 0;
42
43 /* Loop until nbytes of buf have been written. */
44 while (w < nbytes) {
45 /* Keep trying until write succeeds without interruption. */
46 do {
47 r = write(fd, buf + w, nbytes - w);
48 } while (r < 0 && errno == EINTR);
49
50 if (r < 0) {
51 return r;
52 }
53
54 w += r;
55 }
56
57 return w;
58 }
59
60 static void
61 deliver(char *maildir, char *newfn)
62 {
63 char fn[TMPNAMLEN], host[HOST_NAME_MAX + 1];
64 char *buildfn; /* used to build up the fn */
65 time_t now;
66 struct tm *t;
67 pid_t pid;
68 struct stat statbuf;
69 int try;
70 int fd, r;
71 char buf[BUFSIZ];
72
73 if (chdir(maildir) != 0) {
74 err(EX_TEMPFAIL, "Failed chdir(%s)", maildir);
75 }
76
77 pid = getpid();
78 gethostname(host, sizeof(host));
79
80 for (try = 0; ; try++) {
81 now = time(NULL);
82 t = gmtime(&now);
83
84 buildfn = fn + sprintf(fn, "tmp/");
85 buildfn += strftime(buildfn, LEN_ULONG + 1, "%s.", t);
86 buildfn += snprintf(buildfn, LEN_ULONG + 1, "%d.", pid);
87 buildfn += sprintf(buildfn, "%s", host);
88
89 if (stat(fn, &statbuf) != 0) {
90 if (errno == ENOENT) {
91 break;
92 }
93 }
94
95 if (try == 2) {
96 errx(EX_TEMPFAIL, "Attempted filename exists.");
97 }
98
99 sleep(2);
100 }
101
102 fd = open(fn, O_WRONLY | O_EXCL | O_CREAT, 0644);
103 if (fd < 0) {
104 err(EX_TEMPFAIL, "Failed open(%s)", fn);
105 }
106
107 /* From this point, we have a tmp file, so if someting fails we
108 * must try to unlink it before bombing. */
109
110 while ((r = read(STDIN_FILENO, buf, BUFSIZ)) > 0) {
111 if (full_write(fd, buf, r) != r) {
112 unlink(fn);
113 err(EX_TEMPFAIL, "Failed write(%s)", fn);
114 }
115 }
116
117 if (fsync(fd) < 0) {
118 unlink(fn);
119 err(EX_TEMPFAIL, "Failed fsync(%s)", fn);
120 }
121 if (close(fd) < 0) {
122 unlink(fn);
123 err(EX_TEMPFAIL, "Failed close(%s)", fn);
124 }
125
126 strcpy(newfn, fn);
127 memcpy(newfn, "new", 3);
128
129 if (rename(fn, newfn) != 0) {
130 unlink(fn);
131 err(EX_TEMPFAIL, "Failed rename(%s, new)", fn);
132 }
133 }
134
135 int
136 main(int argc, char *argv[])
137 {
138 char newfn[TMPNAMLEN];
139 pid_t pid;
140 FILE *errfile;
141 char *fail;
142 int fd;
143
144 deliver("Maildir", newfn);
145
146 if (argv[1]) {
147 if ((pid = fork()) > 0) {
148 return 0;
149 } else if (pid == 0) {
150 argv[0] = argv[1];
151 argv[1] = newfn;
152 execv(argv[0], argv);
153 fail = "exec";
154 } else {
155 fail = "fork";
156 }
157
158 /* If we got here fork or exec failed, so try to write an
159 * error message where later mail processors can find it. We
160 * really can't do anything about it if this fails. */
161 memcpy(newfn, "err", 3);
162 fd = open(newfn, O_WRONLY | O_EXCL | O_CREAT, 0644);
163 errfile = fdopen(fd, "w");
164 if (errfile) {
165 fprintf(errfile, "%s:%d\n", fail, errno);
166 }
167 }
168
169 /* Always return 0 because deliver exits if it fails, and if it
170 * succeeds we cannot return a failure code to the MTA. */
171 return 0;
172 }