maildir.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. /* $Id$
  2. *
  3. * isync - IMAP4 to maildir mailbox synchronizer
  4. * Copyright (C) 2000-2 Michael R. Elkins <me@mutt.org>
  5. *
  6. * This program is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 2 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program; if not, write to the Free Software
  18. * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  19. *
  20. * As a special exception, isync may be linked with the OpenSSL library,
  21. * despite that library's more restrictive license.
  22. */
  23. #include <limits.h>
  24. #include <stdlib.h>
  25. #include <string.h>
  26. #include <dirent.h>
  27. #include <fcntl.h>
  28. #include <stdio.h>
  29. #include <unistd.h>
  30. #include <sys/stat.h>
  31. #include <errno.h>
  32. #include <time.h>
  33. #include "isync.h"
  34. #include "dotlock.h"
  35. /* 2,<flags> */
  36. static void
  37. parse_info (message_t * m, char *s)
  38. {
  39. if (*s == '2' && *(s + 1) == ',')
  40. {
  41. s += 2;
  42. while (*s)
  43. {
  44. if (*s == 'F')
  45. m->flags |= D_FLAGGED;
  46. else if (*s == 'R')
  47. m->flags |= D_ANSWERED;
  48. else if (*s == 'T')
  49. m->flags |= D_DELETED;
  50. else if (*s == 'S')
  51. m->flags |= D_SEEN;
  52. s++;
  53. }
  54. }
  55. }
  56. /*
  57. * There are three possible results of this function:
  58. * >1 uid was already seen
  59. * 0 uid was not yet seen
  60. * -1 unable to read uid because of some other error
  61. */
  62. static int
  63. read_uid (const char *path, const char *file, unsigned int *uid /* out */)
  64. {
  65. char full[_POSIX_PATH_MAX];
  66. int fd;
  67. int ret = -1;
  68. int len;
  69. char buf[64], *ptr;
  70. snprintf (full, sizeof (full), "%s/%s", path, file);
  71. fd = open (full, O_RDONLY);
  72. if (fd == -1)
  73. {
  74. if (errno != ENOENT)
  75. {
  76. perror (full);
  77. return -1;
  78. }
  79. return 0; /* doesn't exist */
  80. }
  81. len = read (fd, buf, sizeof (buf) - 1);
  82. if (len == -1)
  83. perror ("read");
  84. else
  85. {
  86. buf[len] = 0;
  87. errno = 0;
  88. *uid = strtoul (buf, &ptr, 10);
  89. if (errno)
  90. perror ("strtoul");
  91. else if (ptr && *ptr == '\n')
  92. ret = 1;
  93. /* else invalid value */
  94. }
  95. close (fd);
  96. return ret;
  97. }
  98. /*
  99. * open a maildir mailbox.
  100. * if OPEN_FAST is set, we just check to make
  101. * sure its a valid mailbox and don't actually parse it. any IMAP messages
  102. * with the \Recent flag set are guaranteed not to be in the mailbox yet,
  103. * so we can save a lot of time when the user just wants to fetch new messages
  104. * without syncing the flags.
  105. * if OPEN_CREATE is set, we create the mailbox if it doesn't already exist.
  106. */
  107. mailbox_t *
  108. maildir_open (const char *path, int flags)
  109. {
  110. char buf[_POSIX_PATH_MAX];
  111. DIR *d;
  112. struct dirent *e;
  113. message_t **cur;
  114. message_t *p;
  115. mailbox_t *m;
  116. char *s;
  117. int count = 0;
  118. struct stat sb;
  119. const char *subdirs[] = { "cur", "new", "tmp" };
  120. int i;
  121. datum key;
  122. m = calloc (1, sizeof (mailbox_t));
  123. m->lockfd = -1;
  124. /* filename expansion happens here, not in the config parser */
  125. m->path = expand_strdup (path);
  126. if (stat (m->path, &sb))
  127. {
  128. if (errno == ENOENT && (flags & OPEN_CREATE))
  129. {
  130. if (mkdir (m->path, S_IRUSR | S_IWUSR | S_IXUSR))
  131. {
  132. fprintf (stderr, "ERROR: mkdir %s: %s (errno %d)\n",
  133. m->path, strerror (errno), errno);
  134. goto err;
  135. }
  136. for (i = 0; i < 3; i++)
  137. {
  138. snprintf (buf, sizeof (buf), "%s/%s", m->path, subdirs[i]);
  139. if (mkdir (buf, S_IRUSR | S_IWUSR | S_IXUSR))
  140. {
  141. fprintf (stderr, "ERROR: mkdir %s: %s (errno %d)\n",
  142. buf, strerror (errno), errno);
  143. goto err;
  144. }
  145. }
  146. }
  147. else
  148. {
  149. fprintf (stderr, "ERROR: stat %s: %s (errno %d)\n", m->path,
  150. strerror (errno), errno);
  151. goto err;
  152. }
  153. }
  154. else
  155. {
  156. /* check to make sure this looks like a valid maildir box */
  157. for (i = 0; i < 3; i++)
  158. {
  159. snprintf (buf, sizeof (buf), "%s/%s", m->path, subdirs[i]);
  160. if (stat (buf, &sb))
  161. {
  162. fprintf (stderr, "ERROR: stat %s: %s (errno %d)\n", buf,
  163. strerror (errno), errno);
  164. fprintf (stderr,
  165. "ERROR: %s does not appear to be a valid maildir style mailbox\n",
  166. m->path);
  167. goto err;
  168. }
  169. }
  170. }
  171. /*
  172. * we need a mutex on the maildir because of the state files that isync
  173. * uses.
  174. */
  175. snprintf (buf, sizeof (buf), "%s/isynclock", m->path);
  176. if (dotlock_lock (buf, &m->lockfd))
  177. goto err;
  178. /* check for the uidvalidity value */
  179. i = read_uid (m->path, "isyncuidvalidity", &m->uidvalidity);
  180. if (i == -1)
  181. goto err;
  182. else if (i > 0)
  183. m->uidseen = 1;
  184. /* load the current maxuid */
  185. if (read_uid (m->path, "isyncmaxuid", &m->maxuid) == -1)
  186. goto err;
  187. if (flags & OPEN_FAST)
  188. return m;
  189. snprintf (buf, sizeof (buf), "%s/isyncuidmap", m->path);
  190. m->db = dbm_open (buf, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
  191. if (m->db == NULL)
  192. {
  193. fputs ("ERROR: unable to open UID db\n", stderr);
  194. goto err;
  195. }
  196. cur = &m->msgs;
  197. for (; count < 2; count++)
  198. {
  199. /* read the msgs from the new subdir */
  200. snprintf (buf, sizeof (buf), "%s/%s", m->path,
  201. (count == 0) ? "new" : "cur");
  202. d = opendir (buf);
  203. if (!d)
  204. {
  205. perror ("opendir");
  206. goto err;
  207. }
  208. while ((e = readdir (d)))
  209. {
  210. if (*e->d_name == '.')
  211. continue; /* skip dot-files */
  212. *cur = calloc (1, sizeof (message_t));
  213. p = *cur;
  214. p->file = strdup (e->d_name);
  215. p->uid = -1;
  216. p->flags = 0;
  217. p->new = (count == 0);
  218. /* determine the UID for this message. The basename (sans
  219. * flags) is used as the key in the db
  220. */
  221. key.dptr = p->file;
  222. s = strchr (key.dptr, ':');
  223. key.dsize = s ? (size_t) (s - key.dptr) : strlen (key.dptr);
  224. key = dbm_fetch (m->db, key);
  225. if (key.dptr)
  226. {
  227. p->uid = *(int *) key.dptr;
  228. if (p->uid > m->maxuid)
  229. m->maxuid = p->uid;
  230. }
  231. else /* XXX remove. every locally generated message triggers this */
  232. puts ("Warning, no UID for message");
  233. if (s)
  234. parse_info (p, s + 1);
  235. if (p->flags & D_DELETED)
  236. m->deleted++;
  237. cur = &p->next;
  238. }
  239. closedir (d);
  240. }
  241. return m;
  242. err:
  243. if (m->db)
  244. dbm_close (m->db);
  245. dotlock_unlock (&m->lockfd);
  246. free (m->path);
  247. free (m);
  248. return NULL;
  249. }
  250. /* permanently remove messages from a maildir mailbox. if `dead' is nonzero,
  251. * we only remove the messags marked dead.
  252. */
  253. int
  254. maildir_expunge (mailbox_t * mbox, int dead)
  255. {
  256. message_t **cur = &mbox->msgs;
  257. message_t *tmp;
  258. char *s;
  259. datum key;
  260. char path[_POSIX_PATH_MAX];
  261. while (*cur)
  262. {
  263. if ((dead == 0 && (*cur)->flags & D_DELETED) ||
  264. (dead && (*cur)->dead))
  265. {
  266. tmp = *cur;
  267. snprintf (path, sizeof (path), "%s/%s/%s",
  268. mbox->path, tmp->new ? "new" : "cur", tmp->file);
  269. if (unlink (path))
  270. perror (path);
  271. /* remove the message from the UID map */
  272. key.dptr = tmp->file;
  273. s = strchr (key.dptr, ':');
  274. key.dsize = s ? (size_t) (s - key.dptr) : strlen (key.dptr);
  275. dbm_delete (mbox->db, key);
  276. *cur = (*cur)->next;
  277. free (tmp->file);
  278. free (tmp);
  279. }
  280. else
  281. cur = &(*cur)->next;
  282. }
  283. return 0;
  284. }
  285. int
  286. maildir_update_maxuid (mailbox_t * mbox)
  287. {
  288. int fd;
  289. char buf[64];
  290. size_t len;
  291. char path[_POSIX_PATH_MAX];
  292. int ret = 0;
  293. snprintf (path, sizeof (path), "%s/isyncmaxuid", mbox->path);
  294. fd = open (path, O_WRONLY | O_CREAT, 0600);
  295. if (fd == -1)
  296. {
  297. perror ("open");
  298. return -1;
  299. }
  300. /* write out the file */
  301. snprintf (buf, sizeof (buf), "%u\n", mbox->maxuid);
  302. len = write (fd, buf, strlen (buf));
  303. if (len == (size_t) - 1)
  304. {
  305. perror ("write");
  306. ret = -1;
  307. }
  308. if (close (fd))
  309. ret = -1;
  310. return ret;
  311. }
  312. #define _24_HOURS (3600 * 24)
  313. static void
  314. maildir_clean_tmp (const char *mbox)
  315. {
  316. char path[_POSIX_PATH_MAX];
  317. DIR *dirp;
  318. struct dirent *entry;
  319. struct stat st;
  320. time_t now;
  321. snprintf (path, sizeof (path), "%s/tmp", mbox);
  322. dirp = opendir (path);
  323. if (dirp == NULL)
  324. {
  325. fprintf (stderr, "maildir_clean_tmp: opendir: %s: %s (errno %d)\n",
  326. path, strerror (errno), errno);
  327. return;
  328. }
  329. /* assuming this scan will take less than a second, we only need to
  330. * check the time once before the following loop.
  331. */
  332. time (&now);
  333. while ((entry = readdir (dirp)))
  334. {
  335. snprintf (path, sizeof (path), "%s/tmp/%s", mbox, entry->d_name);
  336. if (stat (path, &st))
  337. fprintf (stderr, "maildir_clean_tmp: stat: %s: %s (errno %d)\n",
  338. path, strerror (errno), errno);
  339. else if (S_ISREG (st.st_mode) && now - st.st_ctime >= _24_HOURS)
  340. {
  341. /* this should happen infrequently enough that it won't be
  342. * bothersome to the user to display when it occurs.
  343. */
  344. printf ("Warning: removing stale file %s\n", path);
  345. if (unlink (path))
  346. fprintf (stderr,
  347. "maildir_clean_tmp: unlink: %s: %s (errno %d)\n",
  348. path, strerror (errno), errno);
  349. }
  350. }
  351. closedir(dirp);
  352. }
  353. void
  354. maildir_close (mailbox_t * mbox)
  355. {
  356. if (mbox->db)
  357. dbm_close (mbox->db);
  358. /* release the mutex on the mailbox */
  359. dotlock_unlock (&mbox->lockfd);
  360. /* per the maildir(5) specification, delivery agents are supposed to
  361. * set a 24-hour timer on items placed in the `tmp' directory.
  362. */
  363. maildir_clean_tmp (mbox->path);
  364. free (mbox->path);
  365. free_message (mbox->msgs);
  366. memset (mbox, 0xff, sizeof (mailbox_t));
  367. free (mbox);
  368. }
  369. int
  370. maildir_set_uidvalidity (mailbox_t * mbox, unsigned int uidvalidity)
  371. {
  372. char path[_POSIX_PATH_MAX];
  373. char buf[16];
  374. int fd;
  375. int ret;
  376. snprintf (path, sizeof (path), "%s/isyncuidvalidity", mbox->path);
  377. fd = open (path, O_WRONLY | O_CREAT | O_EXCL, 0600);
  378. if (fd == -1)
  379. {
  380. perror ("open");
  381. return -1;
  382. }
  383. snprintf (buf, sizeof (buf), "%u\n", uidvalidity);
  384. ret = write (fd, buf, strlen (buf));
  385. if (ret == -1)
  386. perror ("write");
  387. else if ((size_t) ret != strlen (buf))
  388. ret = -1;
  389. else
  390. ret = 0;
  391. if (close (fd))
  392. {
  393. perror ("close");
  394. ret = -1;
  395. }
  396. if (ret)
  397. if (unlink (path))
  398. perror ("unlink");
  399. return (ret);
  400. }