maildir.c 10 KB

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