maildir.c 11 KB

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