maildir.c 11 KB

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