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