mixmaster

mixmaster 3.0 patched for libressl
git clone git://parazyd.org/mixmaster.git
Log | Files | Refs | README

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