PageRenderTime 30ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 0ms

/maildir.c

#
C | 575 lines | 367 code | 88 blank | 120 comment | 122 complexity | 3f2a660c0fbd0ccb12963e680733bd20 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.0
  1. /*
  2. * maildir.c:
  3. * Qmail-style maildir support for tpop3d.
  4. *
  5. * Copyright (c) 2001 Paul Makepeace (realprogrammers.com).
  6. *
  7. * This program is free software; you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation; either version 2, or (at your option)
  10. * any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, write to the Free Software Foundation,
  19. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  20. */
  21. #ifdef HAVE_CONFIG_H
  22. #include "configuration.h"
  23. #endif /* HAVE_CONFIG_H */
  24. #ifdef MBOX_MAILDIR
  25. static const char rcsid[] = "$Id$";
  26. #include <sys/types.h>
  27. #include <dirent.h>
  28. #include <errno.h>
  29. #include <string.h>
  30. #include <stdio.h>
  31. #include <stdlib.h>
  32. #include <syslog.h>
  33. #include <unistd.h>
  34. #include <utime.h>
  35. #include <time.h>
  36. #include <regex.h>
  37. #include <sys/fcntl.h>
  38. #include <sys/stat.h>
  39. #include <sys/time.h>
  40. #include "config.h"
  41. #include "connection.h"
  42. #include "mailbox.h"
  43. #include "util.h"
  44. #include "vector.h"
  45. /*
  46. * Although maildir is a locking-free mailstore, we optionally support the
  47. * exclusive locking of maildirs so that we can implement the RFC1939
  48. * semantics where POP3 sessions are exclusive. To do this we create a lock
  49. * directory called .poplock in the root of the maildir. This is convenient
  50. * because mkdir(2) is atomic, even on NFS.
  51. */
  52. /* MAILDIR_LOCK_LIFETIME
  53. * How long a maildir lock lasts if it is never unlocked. */
  54. #define MAILDIR_LOCK_LIFETIME 1800
  55. /* maildir_lock DIRECTORY
  56. * Attempt to atomically create a .poplock lock directory in DIRECTORY. Returns
  57. * 1 on success or 0 on failure. If such a directory exists and is older than
  58. * MAILDIR_LOCK_LIFETIME, we will update the time in it, claiming the lock
  59. * ourselves. */
  60. static int maildir_lock(const char *dirname) {
  61. char *lockdirname = NULL;
  62. int ret = 0;
  63. lockdirname = xmalloc(strlen(dirname) + sizeof("/.poplock"));
  64. sprintf(lockdirname, "%s/.poplock", dirname);
  65. if (mkdir(lockdirname, 0777) == -1) {
  66. if (errno == EEXIST) {
  67. /*
  68. * Already locked. Now we have a problem, because we can't
  69. * atomically discover the creation time of the directory and
  70. * update it. For the moment, just do this the nonatomic way and
  71. * hope for the best. It's not too serious since we now react
  72. * properly in the case that the message has been deleted by
  73. * another user.
  74. */
  75. struct stat st;
  76. if (stat(lockdirname, &st) == -1)
  77. log_print(LOG_ERR, _("maildir_lock: %s: could not stat .poplock directory: %m"), dirname);
  78. else if (st.st_atime < time(NULL) - MAILDIR_LOCK_LIFETIME) {
  79. /* XXX Race condition here. */
  80. if (utime(lockdirname, NULL) == -1)
  81. log_print(LOG_ERR, _("maildir_lock: %s: could not update access time on .poplock directory: %m"), dirname);
  82. else {
  83. log_print(LOG_WARNING, _("maildir_lock: %s: grabbed stale (age %d:%02d) lock"), dirname, (int)(time(NULL) - st.st_atime) / 60, (int)(time(NULL) - st.st_atime) % 60);
  84. ret = 1;
  85. }
  86. }
  87. } else
  88. log_print(LOG_ERR, _("maildir_lock: %s: could not create .poplock directory: %m"), dirname);
  89. } else
  90. ret = 1;
  91. if (lockdirname)
  92. xfree(lockdirname);
  93. return ret;
  94. }
  95. /* maildir_update_lock DIRECTORY
  96. * Update the access time on DIRECTORY. */
  97. static void maildir_update_lock(const char *dirname) {
  98. static time_t last_updated_lock;
  99. if (last_updated_lock < time(NULL) - 60) {
  100. char *lockdirname = NULL;
  101. lockdirname = xmalloc(strlen(dirname) + sizeof("/.poplock"));
  102. sprintf(lockdirname, "%s/.poplock", dirname);
  103. utime(lockdirname, NULL);
  104. xfree(lockdirname);
  105. last_updated_lock = time(NULL);
  106. }
  107. }
  108. /* maildir_unlock DIRECTORY
  109. * Remove any .poplock lock directory in DIRECTORY. */
  110. static void maildir_unlock(const char *dirname) {
  111. char *lockdirname;
  112. lockdirname = xmalloc(strlen(dirname) + sizeof("/.poplock"));
  113. sprintf(lockdirname, "%s/.poplock", dirname);
  114. rmdir(lockdirname); /* Nothing we can do if this fails. */
  115. xfree(lockdirname);
  116. }
  117. /* maildir_make_indexpoint:
  118. * Make an indexpoint to put in a maildir. */
  119. static void maildir_make_indexpoint(struct indexpoint *m, const char *filename, off_t size, time_t mtime) {
  120. memset(m, 0, sizeof(struct indexpoint));
  121. m->filename = xstrdup(filename);
  122. if (!m->filename) {
  123. return;
  124. }
  125. m->offset = 0; /* not used */
  126. m->length = 0; /* "\n\nFrom " delimiter not used */
  127. m->deleted = 0;
  128. m->msglength = size;
  129. /* In previous versions of tpop3d, the first 16 characters of the file name
  130. * of a maildir message were used to form a unique ID. Unfortunately, this
  131. * is not a good strategy, especially now that time_t's are 10 characters
  132. * long. So now we form an MD5 hash of the file name; obviously, these
  133. * unique IDs are not compatible with the old ones, so optionally you can
  134. * retain the old scheme by replacing the following line with
  135. * strncpy(m->hash, filename+4, sizeof(m->hash));
  136. */
  137. md5_digest(filename + 4, strcspn(filename + 4, ":"), m->hash); /* +4: skip cur/ or new/ subdir; ignore flags at end. */
  138. m->mtime = mtime;
  139. }
  140. /* maildir_build_index MAILDIR SUBDIR TIME
  141. * Build an index of the MAILDIR; SUBDIR is one of cur, tmp or new; TIME is the
  142. * time at which the operation started, used to ignore messages delivered
  143. * during processing. Returns 0 on success, -1 otherwise. */
  144. int maildir_build_index(mailbox M, const char *subdir, time_t T) {
  145. DIR *dir;
  146. struct dirent *d;
  147. if (!M) return -1;
  148. dir = opendir(subdir);
  149. if (!dir) {
  150. log_print(LOG_ERR, "maildir_build_index: opendir(%s/%s): %m", M->name, subdir);
  151. return -1;
  152. }
  153. while ((d = readdir(dir))) {
  154. struct stat st;
  155. char *filename, *seq;
  156. int ret,seql;
  157. if (d->d_name[0] == '.') continue;
  158. filename = xmalloc(strlen(subdir) + strlen(d->d_name) + 2);
  159. sprintf(filename, "%s/%s", subdir, d->d_name);
  160. if (!filename) return -1;
  161. if(config_get_bool("maildir-evaluate-filename")) {
  162. memset(&st, 0, sizeof(st));
  163. st.st_mtime = strtoul(d->d_name, NULL, 10);
  164. if(!(seq = config_get_string("maildir-size-string")))
  165. seq = ",S=";
  166. seql = strlen(seq);
  167. if(seq = strstr(d->d_name, seq))
  168. st.st_size = strtoul(seq + seql, NULL, 10);
  169. if (st.st_size && st.st_mtime)
  170. ret = 0;
  171. else {
  172. ret = stat(filename, &st);
  173. log_print(LOG_DEBUG, "maildir_build_index: Falling back on stat()!");
  174. }
  175. } else {
  176. ret = stat(filename, &st);
  177. }
  178. if (0 == ret) {
  179. struct indexpoint pt;
  180. /* XXX Previously, we ignored messages from the future, since
  181. * that's what qmail-pop3d does. But it's not clear why this is
  182. * useful, so turn the check into a warning. */
  183. if (st.st_mtime > T)
  184. log_print(LOG_WARNING, _("maildir_build_index: %s: mtime is %d seconds in the future; this condition may indicate that you have a clock synchronisation error, especially if you are using NFS-mounted mail directories"), filename, (int)(st.st_mtime - T));
  185. /* These get sorted by mtime later. */
  186. maildir_make_indexpoint(&pt, filename, st.st_size, st.st_mtime);
  187. mailbox_add_indexpoint(M, &pt);
  188. /* Accumulate size of messages. */
  189. M->totalsize += st.st_size;
  190. }
  191. xfree(filename);
  192. }
  193. closedir(dir);
  194. if (d) {
  195. log_print(LOG_ERR, "maildir_build_index: readdir(%s): %m", subdir);
  196. return -1;
  197. }
  198. #ifdef IGNORE_CCLIENT_METADATA
  199. #warning IGNORE_CCLIENT_METADATA not supported with maildir.
  200. #endif /* IGNORE_CCLIENT_METADATA */
  201. return 0;
  202. }
  203. /* maildir_recurse MAILBOX DIRECTORY TIME
  204. * Recurses through IMAP folders to search for messages. Returns 0 on success
  205. * and minor errors, -1 on fatal errors. */
  206. static int maildir_recurse(mailbox M, char *current, time_t time, tokens ignorefolders) {
  207. DIR *dir;
  208. struct dirent *d;
  209. char *folder, *recursefolder;
  210. int foldersl, dirl;
  211. struct stat st;
  212. if (!M) return -1;
  213. dir = opendir(current);
  214. if (!dir) {
  215. /* We ignore subdirectories with errors, therefor we return 0 here. */
  216. log_print(LOG_ERR, "maildir_recurse: opendir(.): %m");
  217. return 0;
  218. }
  219. while ((d = readdir(dir))) {
  220. int i, ignore = 0;
  221. if (d->d_name[0] != '.')
  222. continue;
  223. folder = d->d_name + 1;
  224. if (!*folder || !strcmp(".", folder))
  225. continue;
  226. foldersl = strlen(folder);
  227. for (i = 0; i < ignorefolders->num; i++) {
  228. if (*ignorefolders->toks[i] == '^') {
  229. /* We have a regexp */
  230. regex_t re;
  231. if (regcomp(&re, ignorefolders->toks[i], REG_EXTENDED|REG_NOSUB) == 0) {
  232. if (regexec(&re, folder, (size_t) 0, NULL, 0) == 0) {
  233. ignore = 1;
  234. regfree(&re);
  235. break;
  236. }
  237. regfree(&re);
  238. }
  239. } else if (0 == strcmp(folder, ignorefolders->toks[i])) {
  240. ignore = 1;
  241. break;
  242. }
  243. }
  244. if (ignore)
  245. continue;
  246. dirl = strlen(current) + foldersl + 3;
  247. recursefolder = xmalloc(dirl);
  248. if (!recursefolder)
  249. return -1;
  250. sprintf(recursefolder, "%s/.%s", current, folder);
  251. if(stat(recursefolder, &st) == 0 && S_ISDIR(st.st_mode)) {
  252. if(maildir_recurse(M, recursefolder, time, ignorefolders) != 0) {
  253. xfree(recursefolder);
  254. return -1;
  255. }
  256. recursefolder = xrealloc(recursefolder, dirl + 4);
  257. if (!recursefolder)
  258. return -1;
  259. /* We ignore subdirectories with errors, therefore we don't
  260. * fail on maildir_build_index problems here. */
  261. sprintf(recursefolder, "%s/.%s/new", current, folder);
  262. if (stat(recursefolder, &st) == 0 && S_ISDIR(st.st_mode))
  263. maildir_build_index(M, recursefolder, time);
  264. sprintf(recursefolder, "%s/.%s/cur", current, folder);
  265. if (stat(recursefolder, &st) == 0 && S_ISDIR(st.st_mode))
  266. maildir_build_index(M, recursefolder, time);
  267. }
  268. xfree(recursefolder);
  269. }
  270. closedir(dir);
  271. return 0;
  272. }
  273. /* maildir_sort_callback A B
  274. * qsort(3) callback for ordering messages in a maildir. */
  275. int maildir_sort_callback(const void *a, const void *b) {
  276. const struct indexpoint *A = a, *B = b;
  277. return A->mtime - B->mtime;
  278. }
  279. /* maildir_new DIRECTORY
  280. * Create a mailbox object from the named DIRECTORY. */
  281. mailbox maildir_new(const char *dirname) {
  282. mailbox M, failM = NULL;
  283. struct timeval tv1, tv2;
  284. float f;
  285. int locked = 0;
  286. alloc_struct(_mailbox, M);
  287. M->delete = maildir_delete; /* generic destructor */
  288. M->apply_changes = maildir_apply_changes;
  289. M->sendmessage = maildir_sendmessage;
  290. /* Allocate space for the index. */
  291. M->index = (struct indexpoint*)xcalloc(32, sizeof(struct indexpoint));
  292. M->size = 32;
  293. if (chdir(dirname) == -1) {
  294. if (errno == ENOENT) failM = MBOX_NOENT;
  295. else log_print(LOG_ERR, "maildir_new: chdir(%s): %m", dirname);
  296. goto fail;
  297. } else
  298. M->name = xstrdup(dirname);
  299. /* Optionally, try to lock the maildir. */
  300. if (config_get_bool("maildir-exclusive-lock") && !(locked = maildir_lock(M->name))) {
  301. log_print(LOG_INFO, _("maildir_new: %s: couldn't lock maildir"), dirname);
  302. goto fail;
  303. }
  304. gettimeofday(&tv1, NULL);
  305. /* Build index of maildir. */
  306. if (maildir_build_index(M, "new", tv1.tv_sec) != 0) goto fail;
  307. if (maildir_build_index(M, "cur", tv1.tv_sec) != 0) goto fail;
  308. if (config_get_bool("maildir-recursion")) {
  309. char *ign;
  310. tokens ignorefolders;
  311. if (NULL == (ign = config_get_string("maildir-ignore-folders")))
  312. ign = "Trash Sent";
  313. if (!(ignorefolders = tokens_new(ign, " \t")))
  314. goto fail;
  315. if (maildir_recurse(M, ".", tv1.tv_sec, ignorefolders) != 0) {
  316. tokens_delete(ignorefolders);
  317. goto fail;
  318. }
  319. tokens_delete(ignorefolders);
  320. }
  321. /* Now sort the messages. */
  322. qsort(M->index, M->num, sizeof(struct indexpoint), maildir_sort_callback);
  323. gettimeofday(&tv2, NULL);
  324. f = (float)(tv2.tv_sec - tv1.tv_sec) + 1e-6 * (float)(tv2.tv_usec - tv1.tv_usec);
  325. log_print(LOG_NOTICE, "maildir_new: scanned maildir %s (%d messages) in %0.3fs", dirname, (int)M->num, f);
  326. return M;
  327. fail:
  328. if (M) {
  329. if (M->name) {
  330. if (locked) maildir_unlock(M->name);
  331. xfree(M->name);
  332. }
  333. if (M->index) xfree(M->index);
  334. xfree(M);
  335. }
  336. return failM;
  337. }
  338. /* maildir_delete MAILDIR
  339. * Destructor for MAILDIR; this does nothing maildir-specific unless maildir
  340. * locking is enabled, in which case we must unlock it. */
  341. void maildir_delete(mailbox M) {
  342. if (config_get_bool("maildir-exclusive-lock"))
  343. maildir_unlock(M->name);
  344. mailbox_delete(M);
  345. }
  346. /* maildir_open_message_file MESSAGE
  347. * Return a file descriptor on the file associated with MESSAGE. If it has
  348. * changed name since then, we try to find the file and update MESSAGE. If we
  349. * can't find the MESSAGE, return -1. */
  350. static int open_message_file(struct indexpoint *m) {
  351. int fd;
  352. DIR *d;
  353. struct dirent *de;
  354. size_t msgnamelen;
  355. fd = open(m->filename, O_RDONLY);
  356. if (fd != -1)
  357. return fd;
  358. /*
  359. * Where's that message?
  360. */
  361. /* Possibility 1: message was in new/, and is now in cur/ with a :2,S
  362. * suffix. */
  363. if (strncmp(m->filename, "new/", 4)) {
  364. char *name;
  365. name = xmalloc(strlen(m->filename) + sizeof(":2,S"));
  366. sprintf(name, "cur/%s:2,S", m->filename + 4);
  367. if ((fd = open(name, O_RDONLY)) != -1) {
  368. /* We win! */
  369. xfree(m->filename);
  370. m->filename = name;
  371. return fd;
  372. } else if (errno != ENOENT) {
  373. /* Bad news. */
  374. log_print(LOG_ERR, "maildir_open_message_file: %s: %m", name);
  375. xfree(name);
  376. return -1;
  377. }
  378. }
  379. /* Possibility 2: message is now in cur with some random suffix. This is
  380. * really bad, because we need to rescan the whole maildir. But this
  381. * shouldn't happen very often. */
  382. /* Figure out the name of the message. */
  383. msgnamelen = strcspn(m->filename + 4, ":");
  384. if (!(d = opendir("cur"))) {
  385. log_print(LOG_ERR, "maildir_open_message_file: cur: %m");
  386. return -1;
  387. }
  388. while ((de = readdir(d))) {
  389. /* Compare base name of this message against the new message. */
  390. if (strncmp(de->d_name, m->filename + 4, msgnamelen) == 0
  391. && (de->d_name[msgnamelen] == ':' || de->d_name[msgnamelen] == 0)) {
  392. char *name;
  393. name = xmalloc(strlen(de->d_name) + sizeof("cur/"));
  394. sprintf(name, "cur/%s", de->d_name);
  395. if ((fd = open(name, O_RDONLY)) != -1) {
  396. closedir(d);
  397. xfree(m->filename);
  398. m->filename = name;
  399. return fd;
  400. } else {
  401. /* Either something's gone wrong or the message has just been
  402. * moved or deleted. Just give up at this point. */
  403. closedir(d);
  404. xfree(name);
  405. return -1;
  406. }
  407. }
  408. }
  409. log_print(LOG_ERR, _("maildir_open_message_file: %s: can't find message"), m->filename);
  410. /* Message must have been deleted. */
  411. return -1;
  412. }
  413. /* maildir_sendmessage MAILDIR CONNECTION MSGNUM LINES
  414. * Send a +OK response and the header and the given number of LINES of the body
  415. * of message number MSGNUM from MAILDIR escaping lines which begin . as
  416. * required by RFC1939, or, if it cannot, a -ERR error response. Returns 1 on
  417. * is -1. Sends a +OK / -ERR message in front of the message itself. Note that
  418. * the maildir specification says that messages use only `\n' to indicate EOL,
  419. * though some extended formats don't. We assume that the specification is
  420. * obeyed. It's possible that the message will have moved or been deleted under
  421. * us, in which case we make some effort to find the new version. */
  422. int maildir_sendmessage(const mailbox M, connection c, const int i, int n) {
  423. struct indexpoint *m;
  424. struct stat st;
  425. int fd, status;
  426. size_t real_size;
  427. if (!M || i < 0 || i >= M->num) {
  428. /* Shouldn't happen. */
  429. connection_sendresponse(c, 0, _("Unable to send that message"));
  430. return -1;
  431. }
  432. if (config_get_bool("maildir-exclusive-lock"))
  433. maildir_update_lock(M->name);
  434. m = M->index +i;
  435. if ((fd = open_message_file(m)) == -1) {
  436. connection_sendresponse(c, 0, _("Can't send that message; it may have been deleted by a concurrent session"));
  437. log_print(LOG_ERR, "maildir_sendmessage: unable to send message %d", i + 1);
  438. return -1;
  439. }
  440. /* fstat is cheap after open. Real size is needed in case when S= size doesn't reflect real file size. */
  441. if (fstat(fd, &st) != -1) {
  442. real_size = st.st_size;
  443. if (m->msglength != st.st_size)
  444. log_print(LOG_ERR, _("maildir_sendmessage(%s/%s): inconsistency in mail size: index (%d) vs filesystem (%d)"),
  445. M->name, m->filename, (int)m->msglength, (int)st.st_size);
  446. } else
  447. real_size = m->msglength;
  448. status = connection_sendmessage(c, fd, 0 /* offset */, 0 /* skip */, real_size, n);
  449. close(fd);
  450. return status;
  451. }
  452. /* maildir_apply_changes MAILDIR
  453. * Apply deletions to a maildir. */
  454. int maildir_apply_changes(mailbox M) {
  455. struct indexpoint *m;
  456. int did_deletions = 0;
  457. if (!M) return 1;
  458. for (m = M->index; m < M->index + M->num; ++m) {
  459. if (m->deleted) {
  460. if (unlink(m->filename) == -1)
  461. /* Warn but proceed anyway. */
  462. log_print(LOG_ERR, "maildir_apply_changes: unlink(%s): %m", m->filename);
  463. else
  464. did_deletions = 1;
  465. } else {
  466. /* Mark message read. */
  467. if (strncmp(m->filename, "new/", 4) == 0) {
  468. char *cur;
  469. cur = xmalloc(strlen(m->filename) + 5);
  470. sprintf(cur, "cur/%s:2,S", m->filename + 4); /* Set seen flag */
  471. rename(m->filename, cur); /* doesn't matter if it can't */
  472. xfree(cur);
  473. }
  474. }
  475. }
  476. /* This handles the maildirsize file which appears in Maildir++ mailboxes.
  477. * We delete it; a later delivery by a compliant MDA will recreate it. */
  478. if (did_deletions) {
  479. char *name;
  480. name = xmalloc(strlen(M->name) + sizeof "/maildirsize");
  481. sprintf(name, "%s/maildirsize", M->name);
  482. unlink(name);
  483. xfree(name);
  484. }
  485. return 1;
  486. }
  487. #endif /* MBOX_MAILDIR */