tmaildir.c - mixmaster - mixmaster 3.0 patched for libressl
HTML git clone git://parazyd.org/mixmaster.git
DIR Log
DIR Files
DIR Refs
DIR README
---
tmaildir.c (8242B)
---
1 /* Mixmaster version 3.0 -- (C) 1999 - 2006 Anonymizer Inc. and others.
2
3 Mixmaster may be redistributed and modified under certain conditions.
4 This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF
5 ANY KIND, either express or implied. See the file COPYRIGHT for
6 details.
7
8 Maildir support routines
9 $Id: $ */
10
11
12 /* Maildir support for Mixmaster 3 - see
13 http://www.qmail.org/man/man5/maildir.html and
14 http://cr.yp.to/proto/maildir.html
15
16 Added by and (C) 2001 Doobee R. Tzeck
17 drt@un.bewaff.net - http://c0re.jp/
18
19 To test it try:
20 $ gcc maildir.c -DUNITTEST -o test_maildir
21 $ ./test_maildir
22 this should print a single line saying "OK"
23 */
24
25 #include "mix3.h"
26
27 #ifdef WIN32
28 #include <io.h>
29 #include <direct.h>
30 #include <process.h>
31 #define S_IWUSR _S_IWRITE
32 #define S_IRUSR _S_IREAD
33 #else /* end of WIN32 */
34 #include <unistd.h>
35 #endif /* else not WIN32 */
36 #include <fcntl.h>
37 #include <time.h>
38 #include <string.h>
39 #include <sys/stat.h>
40 #include <sys/types.h>
41 #include <errno.h>
42 #include <stdarg.h>
43 #include <assert.h>
44
45 #if defined(S_IFDIR) && !defined(S_ISDIR)
46 #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
47 #endif /* defined(S_IFDIR) && !defined(S_ISDIR) */
48
49 #ifndef SHORTNAMES
50
51 static unsigned long namecounter = 0;
52
53 int checkDirectory(char *dir, char *append, int create) {
54 char tmp[PATHMAX];
55 struct stat buf;
56 int err;
57
58 tmp[0] = '\0';
59 strcatn(tmp, dir, PATHMAX);
60 if (append)
61 strcatn(tmp, append, PATHMAX);
62
63 err = stat(tmp, &buf);
64 if (err == -1) {
65 if (create) {
66 #ifndef POSIX
67 err = mkdir(tmp);
68 #else /* end of not POSIX */
69 err = mkdir(tmp, S_IRWXU);
70 #endif /* else if POSIX */
71 if (err == 0)
72 errlog(NOTICE, "Creating directory %s.\n", tmp);
73 } else
74 err = 1;
75 } else if (!S_ISDIR(buf.st_mode))
76 err = -1;
77
78 return err;
79 }
80
81 /* Write "message" to "maildir", retunr 0 on success, -1 on failure */
82 #define MAX_BASENAME 113 /* actual length should be smaller than 111 bytes */
83 #define MAX_SUBNAME 123 /* actual length should be smaller than 115 bytes */
84 int maildirWrite(char *maildir, BUFFER *message, int create) {
85 int fd;
86 int count;
87 int returnValue;
88 char hostname[64];
89 struct stat statbuf;
90 char basename[MAX_BASENAME];
91 char tmpname[MAX_SUBNAME];
92 char newname[MAX_SUBNAME];
93 int messagesize;
94 char olddirectory[PATHMAX] = "";
95 char normalizedmaildir[PATHMAX];
96
97 /* Declare a handler for SIGALRM so we can time out. */
98 /* set_handler(SIGALRM, alarm_handler); */
99 /* alarm(86400); */
100
101 hostname[0] = '\0';
102 gethostname(hostname, 63);
103 hostname[63] = '\0';
104
105 mixfile(normalizedmaildir, maildir);
106 if ((checkDirectory(normalizedmaildir, NULL, create) != 0) ||
107 (checkDirectory(normalizedmaildir, "tmp", create) != 0) ||
108 (checkDirectory(normalizedmaildir, "cur", create) != 0) ||
109 (checkDirectory(normalizedmaildir, "new", create) != 0)) {
110 returnValue = -1;
111 goto realend;
112 }
113
114 messagesize = message->length;
115
116 /* Step 1: chdir to maildir (and save current dir) */
117 if (getcwd(olddirectory, PATHMAX) == NULL) {
118 returnValue = -1;
119 goto realend;
120 }
121 olddirectory[PATHMAX-1] = '\0';
122 if(chdir(normalizedmaildir) != 0) {
123 returnValue = -1;
124 goto functionExit;
125 }
126
127 /* Step 2: Stat the temporary file. Wait for ENOENT as a response. */
128 for (count = 0;; count++) {
129 tmpname[0] = '\0';
130 newname[0] = '\0';
131 snprintf(basename, MAX_BASENAME, "%lu.%u_%lu.%s,S=%u",
132 time(NULL), getpid(), namecounter++, hostname, messagesize);
133 basename[MAX_BASENAME-1] = '\0';
134 strcatn(tmpname, "tmp" DIRSEPSTR, MAX_SUBNAME);
135 strcatn(tmpname, basename, MAX_SUBNAME);
136 strcatn(newname, "new" DIRSEPSTR, MAX_SUBNAME);
137 strcatn(newname, basename, MAX_SUBNAME);
138
139 if (stat(tmpname, &statbuf) == 0)
140 errno = EEXIST;
141 else if (errno == ENOENT) {
142 /* Step 4: create the file (at least try) */
143 fd = open(tmpname, O_WRONLY|O_CREAT|O_EXCL, S_IWUSR|S_IRUSR);
144 if (fd >= 0)
145 break; /* we managed to open the file */
146 }
147
148 if (count > 5) {
149 /* Too many retries - give up */
150 errlog(ERRORMSG, "Can't create message in %s\n", maildir);
151 returnValue = -1;
152 goto functionExit;
153 }
154
155 /* Step 3: sleep and retry */
156 sleep(2);
157 }
158
159 /* Step 5: write file */
160 if(write(fd, message->data, message->length) != message->length) {
161 returnValue = -1;
162 goto functionExit;
163 }
164
165 /* on NFS this could fail */
166 #ifndef WIN32
167 if((fsync(fd) != 0) || (close(fd) != 0)) {
168 #else /* end of not WIN32 */
169 if((_commit(fd) != 0) || (close(fd) != 0)) {
170 #endif /* else if WIN32 */
171 returnValue = -1;
172 goto functionExit;
173 }
174
175 /* Step 6: move message to 'cur' */
176 #ifdef POSIX
177 for (count = 0;; count++) {
178 if(link(tmpname, newname) != 0) {
179 if (errno == EXDEV || errno == EPERM) {
180 /* We probably are on coda or some other filesystem that does not allow
181 * hardlinks. rename() the file instead of link() and unlink()
182 * I know, It's evil (PP).
183 */
184 if (rename(tmpname, newname) != 0) {
185 returnValue = -1;
186 goto functionExit;
187 };
188 break;
189 } else if (errno != EEXIST) {
190 returnValue = -1;
191 goto functionExit;
192 }
193 } else {
194 /* We successfully linked the message in new/. Now let's get
195 * rid of our tmp/ entry
196 */
197 if(unlink(tmpname) != 0) {
198 /* unlinking failed */
199 returnValue = -1;
200 goto functionExit;
201 }
202 break;
203 }
204
205 if (count > 5) {
206 /* Too many retries - give up */
207 errlog(ERRORMSG, "Can't move message to %s/new/\n", maildir);
208 returnValue = -1;
209 goto functionExit;
210 }
211
212 sleep(2);
213 newname[0] = '\0';
214 snprintf(basename, MAX_BASENAME, "%lu.%u_%lu.%s,S=%u",
215 time(NULL), getpid(), namecounter++, hostname, messagesize);
216 basename[MAX_BASENAME-1] = '\0';
217 strcatn(newname, "new" DIRSEPSTR, MAX_SUBNAME);
218 strcatn(newname, basename, MAX_SUBNAME);
219 }
220 #else /* end of POSIX */
221 /* On non POSIX systems we simply use rename(). Let's hope DJB
222 * never finds out
223 */
224 if (rename(tmpname, newname) != 0) {
225 returnValue = -1;
226 goto functionExit;
227 };
228 #endif /* else if not POSIX */
229
230 returnValue = 0;
231
232 functionExit:
233 /* return to original directory */
234 assert(olddirectory[0] != '\0');
235 if(chdir(olddirectory) != 0)
236 returnValue = -1;
237
238 realend:
239
240 return returnValue;
241 }
242
243 #else /* end of SHORTNAMES */
244 int maildirWrite(char *maildir, BUFFER *message, int create) {
245 {
246 errlog(ERRORMSG, "Maildir delivery does not work with SHORTNAMES.\n");
247 return -1;
248 }
249 #endif /* else if not SHORTNAMES */
250
251
252 #ifdef UNITTEST
253
254 #ifdef NDEBUG
255 #undef NDEBUG
256 #endif /* NDEBUG */
257
258 #include <dirent.h>
259
260 /* mock-up of errlog for unittest */
261 void errlog(int type, char *fmt,...)
262 {
263 va_list ap;
264
265 va_start(ap, fmt);
266 vfprintf(stderr, fmt, ap);
267 va_end(ap);
268 }
269
270 /* main for unittest */
271 int main()
272 {
273 int i, count = 23;
274 int fd;
275 DIR *d;
276 struct dirent *de;
277 BUFFER message;
278 char text[] = "From: nobody@un.bewaff.net\nTo: hackers@c0re.jp\nSubject: testing\n\nthis is just a test\n";
279 char buf[1024];
280
281 /* create buffer with test data */
282 message.data = text;
283 message.length = strlen(text);
284
285 /* write <count> messages to maildir */
286 for(i = 0; i < count; i++)
287 assert(maildirWrite("Maildir.test_maildir", message, 1) == 0);
288
289 /* read them back */
290 assert((d = opendir("Maildir.test_maildir/new")) != NULL);
291 for (i = 0; i < count + 2; i++)
292 {
293 de = readdir(d);
294 if(de->d_name[0] != '.')
295 {
296 buf[0] = '\0';
297 strcat(buf, "Maildir.test_maildir/new/");
298 strcat(buf, de->d_name);
299 fd = open(buf, O_RDONLY);
300 assert(unlink(buf) == 0);
301 assert(read(fd, buf, strlen(text)) == strlen(text));
302 buf[strlen(text)] = '\0';
303 /* check if they match the original message */
304 assert(strcmp(text, buf) == 0);
305 close(fd);
306 }
307 }
308
309 /* no files left in directory? */
310 assert(readdir(d) == NULL);
311
312 /* delete maildir */
313 assert(rmdir("Maildir.test_maildir/tmp") == 0);
314 assert(rmdir("Maildir.test_maildir/new") == 0);
315 assert(rmdir("Maildir.test_maildir/cur") == 0);
316 assert(rmdir("Maildir.test_maildir") == 0);
317
318 /* check if writing to a non existant maildir yilds an error */
319 assert(maildirWrite("Maildir.test_maildir", &message, 0) == -1);
320
321 puts("OK");
322 }
323 #endif /* UNITTEST */