maildir.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 */