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