mbox.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872
  1. /*
  2. * This file is part of the UCB release of Plan 9. It is subject to the license
  3. * terms in the LICENSE file found in the top-level directory of this
  4. * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
  5. * part of the UCB release of Plan 9, including this file, may be copied,
  6. * modified, propagated, or distributed except according to the terms contained
  7. * in the LICENSE file.
  8. */
  9. #include <u.h>
  10. #include <libc.h>
  11. #include <bio.h>
  12. #include <auth.h>
  13. #include "imap4d.h"
  14. static NamedInt flagChars[NFlags] =
  15. {
  16. {"s", MSeen},
  17. {"a", MAnswered},
  18. {"f", MFlagged},
  19. {"D", MDeleted},
  20. {"d", MDraft},
  21. {"r", MRecent},
  22. };
  23. static int fsCtl = -1;
  24. static void boxFlags(Box *box);
  25. static int createImp(Box *box, Qid *qid);
  26. static void fsInit(void);
  27. static void mboxGone(Box *box);
  28. static MbLock *openImp(Box *box, int new);
  29. static int parseImp(Biobuf *b, Box *box);
  30. static int readBox(Box *box);
  31. static uint32_t uidRenumber(Msg *m, uint32_t uid, int force);
  32. static int impFlags(Box *box, Msg *m, char *flags);
  33. /*
  34. * strategy:
  35. * every mailbox file has an associated .imp file
  36. * which maps upas/fs message digests to uids & message flags.
  37. *
  38. * the .imp files are locked by /mail/fs/usename/L.mbox.
  39. * whenever the flags can be modified, the lock file
  40. * should be opened, thereby locking the uid & flag state.
  41. * for example, whenever new uids are assigned to messages,
  42. * and whenever flags are changed internally, the lock file
  43. * should be open and locked. this means the file must be
  44. * opened during store command, and when changing the \seen
  45. * flag for the fetch command.
  46. *
  47. * if no .imp file exists, a null one must be created before
  48. * assigning uids.
  49. *
  50. * the .imp file has the following format
  51. * imp : "imap internal mailbox description\n"
  52. * uidvalidity " " uidnext "\n"
  53. * messageLines
  54. *
  55. * messageLines :
  56. * | messageLines digest " " uid " " flags "\n"
  57. *
  58. * uid, uidnext, and uidvalidity are 32 bit decimal numbers
  59. * printed right justified in a field NUid characters long.
  60. * the 0 uid implies that no uid has been assigned to the message,
  61. * but the flags are valid. note that message lines are in mailbox
  62. * order, except possibly for 0 uid messages.
  63. *
  64. * digest is an ascii hex string NDigest characters long.
  65. *
  66. * flags has a character for each of NFlag flag fields.
  67. * if the flag is clear, it is represented by a "-".
  68. * set flags are represented as a unique single ascii character.
  69. * the currently assigned flags are, in order:
  70. * MSeen s
  71. * MAnswered a
  72. * MFlagged f
  73. * MDeleted D
  74. * MDraft d
  75. */
  76. Box*
  77. openBox(char *name, char *fsname, int writable)
  78. {
  79. Box *box;
  80. MbLock *ml;
  81. int n, new;
  82. if(cistrcmp(name, "inbox") == 0)
  83. if(access("msgs", AEXIST) == 0)
  84. name = "msgs";
  85. else
  86. name = "mbox";
  87. fsInit();
  88. debuglog("imap4d open %s %s\n", name, fsname);
  89. if(fprint(fsCtl, "open '/mail/box/%s/%s' %s", username, name, fsname) < 0){
  90. //ZZZ
  91. char err[ERRMAX];
  92. rerrstr(err, sizeof err);
  93. if(strstr(err, "file does not exist") == nil)
  94. fprint(2,
  95. "imap4d at %lud: upas/fs open %s/%s as %s failed: '%s' %s",
  96. time(nil), username, name, fsname, err,
  97. ctime(time(nil))); /* NB: ctime result ends with \n */
  98. fprint(fsCtl, "close %s", fsname);
  99. return nil;
  100. }
  101. /*
  102. * read box to find all messages
  103. * each one has a directory, and is in numerical order
  104. */
  105. box = MKZ(Box);
  106. box->writable = writable;
  107. n = strlen(name) + 1;
  108. box->name = emalloc(n);
  109. strcpy(box->name, name);
  110. n += STRLEN(".imp");
  111. box->imp = emalloc(n);
  112. snprint(box->imp, n, "%s.imp", name);
  113. n = strlen(fsname) + 1;
  114. box->fs = emalloc(n);
  115. strcpy(box->fs, fsname);
  116. n = STRLEN("/mail/fs/") + strlen(fsname) + 1;
  117. box->fsDir = emalloc(n);
  118. snprint(box->fsDir, n, "/mail/fs/%s", fsname);
  119. box->uidnext = 1;
  120. new = readBox(box);
  121. if(new >= 0){
  122. ml = openImp(box, new);
  123. if(ml != nil){
  124. closeImp(box, ml);
  125. return box;
  126. }
  127. }
  128. closeBox(box, 0);
  129. return nil;
  130. }
  131. /*
  132. * check mailbox
  133. * returns fd of open .imp file if imped.
  134. * otherwise, return value is insignificant
  135. *
  136. * careful: called by idle polling proc
  137. */
  138. MbLock*
  139. checkBox(Box *box, int imped)
  140. {
  141. MbLock *ml;
  142. Dir *d;
  143. int new;
  144. if(box == nil)
  145. return nil;
  146. /*
  147. * if stat fails, mailbox must be gone
  148. */
  149. d = cdDirstat(box->fsDir, ".");
  150. if(d == nil){
  151. mboxGone(box);
  152. return nil;
  153. }
  154. new = 0;
  155. if(box->qid.path != d->qid.path || box->qid.vers != d->qid.vers
  156. || box->mtime != d->mtime){
  157. new = readBox(box);
  158. if(new < 0){
  159. free(d);
  160. return nil;
  161. }
  162. }
  163. free(d);
  164. ml = openImp(box, new);
  165. if(ml == nil)
  166. box->writable = 0;
  167. else if(!imped){
  168. closeImp(box, ml);
  169. ml = nil;
  170. }
  171. return ml;
  172. }
  173. /*
  174. * mailbox is unreachable, so mark all messages expunged
  175. * clean up .imp files as well.
  176. */
  177. static void
  178. mboxGone(Box *box)
  179. {
  180. Msg *m;
  181. if(cdExists(mboxDir, box->name) < 0)
  182. cdRemove(mboxDir, box->imp);
  183. for(m = box->msgs; m != nil; m = m->next)
  184. m->expunged = 1;
  185. box->writable = 0;
  186. }
  187. /*
  188. * read messages in the mailbox
  189. * mark message that no longer exist as expunged
  190. * returns -1 for failure, 0 if no new messages, 1 if new messages.
  191. */
  192. static int
  193. readBox(Box *box)
  194. {
  195. Msg *msgs, *m, *last;
  196. Dir *d;
  197. char *s;
  198. int32_t max, id;
  199. int i, nd, fd, new;
  200. fd = cdOpen(box->fsDir, ".", OREAD);
  201. if(fd < 0){
  202. syslog(0, "mail",
  203. "imap4d at %lud: upas/fs stat of %s/%s aka %s failed: %r",
  204. time(nil), username, box->name, box->fsDir);
  205. mboxGone(box);
  206. return -1;
  207. }
  208. /*
  209. * read box to find all messages
  210. * each one has a directory, and is in numerical order
  211. */
  212. d = dirfstat(fd);
  213. if(d == nil){
  214. close(fd);
  215. return -1;
  216. }
  217. box->mtime = d->mtime;
  218. box->qid = d->qid;
  219. last = nil;
  220. msgs = box->msgs;
  221. max = 0;
  222. new = 0;
  223. free(d);
  224. while((nd = dirread(fd, &d)) > 0){
  225. for(i = 0; i < nd; i++){
  226. s = d[i].name;
  227. id = strtol(s, &s, 10);
  228. if(id <= max || *s != '\0'
  229. || (d[i].mode & DMDIR) != DMDIR)
  230. continue;
  231. max = id;
  232. while(msgs != nil){
  233. last = msgs;
  234. msgs = msgs->next;
  235. if(last->id == id)
  236. goto continueDir;
  237. last->expunged = 1;
  238. }
  239. new = 1;
  240. m = MKZ(Msg);
  241. m->id = id;
  242. m->fsDir = box->fsDir;
  243. m->fs = emalloc(2 * (MsgNameLen + 1));
  244. m->efs = seprint(m->fs, m->fs + (MsgNameLen + 1), "%lud/", id);
  245. m->size = ~0UL&0xFF;
  246. m->lines = ~0UL&0xFF;
  247. m->prev = last;
  248. m->flags = MRecent;
  249. if(!msgInfo(m))
  250. freeMsg(m);
  251. else{
  252. if(last == nil)
  253. box->msgs = m;
  254. else
  255. last->next = m;
  256. last = m;
  257. }
  258. continueDir:;
  259. }
  260. free(d);
  261. }
  262. close(fd);
  263. for(; msgs != nil; msgs = msgs->next)
  264. msgs->expunged = 1;
  265. /*
  266. * make up the imap message sequence numbers
  267. */
  268. id = 1;
  269. for(m = box->msgs; m != nil; m = m->next){
  270. if(m->seq && m->seq != id)
  271. bye("internal error assigning message numbers");
  272. m->seq = id++;
  273. }
  274. box->max = id - 1;
  275. return new;
  276. }
  277. /*
  278. * read in the .imp file, or make one if it doesn't exist.
  279. * make sure all flags and uids are consistent.
  280. * return the mailbox lock.
  281. */
  282. #define IMPMAGIC "imap internal mailbox description\n"
  283. static MbLock*
  284. openImp(Box *box, int new)
  285. {
  286. Qid qid;
  287. Biobuf b;
  288. MbLock *ml;
  289. int fd;
  290. //ZZZZ
  291. int once;
  292. ml = mbLock();
  293. if(ml == nil)
  294. return nil;
  295. fd = cdOpen(mboxDir, box->imp, OREAD);
  296. once = 0;
  297. ZZZhack:
  298. if(fd < 0 || fqid(fd, &qid) < 0){
  299. if(fd < 0){
  300. char buf[ERRMAX];
  301. errstr(buf, sizeof buf);
  302. if(cistrstr(buf, "does not exist") == nil)
  303. fprint(2, "imap4d at %lud: imp open failed: %s\n", time(nil), buf);
  304. if(!once && cistrstr(buf, "locked") != nil){
  305. once = 1;
  306. fprint(2, "imap4d at %lud: imp %s/%s %s locked when it shouldn't be; spinning\n", time(nil), username, box->name, box->imp);
  307. fd = openLocked(mboxDir, box->imp, OREAD);
  308. goto ZZZhack;
  309. }
  310. }
  311. if(fd >= 0)
  312. close(fd);
  313. fd = createImp(box, &qid);
  314. if(fd < 0){
  315. mbUnlock(ml);
  316. return nil;
  317. }
  318. box->dirtyImp = 1;
  319. if(box->uidvalidity == 0)
  320. box->uidvalidity = box->mtime;
  321. box->impQid = qid;
  322. new = 1;
  323. }else if(qid.path != box->impQid.path || qid.vers != box->impQid.vers){
  324. Binit(&b, fd, OREAD);
  325. if(!parseImp(&b, box)){
  326. box->dirtyImp = 1;
  327. if(box->uidvalidity == 0)
  328. box->uidvalidity = box->mtime;
  329. }
  330. Bterm(&b);
  331. box->impQid = qid;
  332. new = 1;
  333. }
  334. if(new)
  335. boxFlags(box);
  336. close(fd);
  337. return ml;
  338. }
  339. /*
  340. * close the .imp file, after writing out any changes
  341. */
  342. void
  343. closeImp(Box *box, MbLock *ml)
  344. {
  345. Msg *m;
  346. Qid qid;
  347. Biobuf b;
  348. char buf[NFlags+1];
  349. int fd;
  350. if(ml == nil)
  351. return;
  352. if(!box->dirtyImp){
  353. mbUnlock(ml);
  354. return;
  355. }
  356. fd = cdCreate(mboxDir, box->imp, OWRITE, 0664);
  357. if(fd < 0){
  358. mbUnlock(ml);
  359. return;
  360. }
  361. Binit(&b, fd, OWRITE);
  362. box->dirtyImp = 0;
  363. Bprint(&b, "%s", IMPMAGIC);
  364. Bprint(&b, "%.*lud %.*lud\n", NUid, box->uidvalidity, NUid, box->uidnext);
  365. for(m = box->msgs; m != nil; m = m->next){
  366. if(m->expunged)
  367. continue;
  368. wrImpFlags(buf, m->flags, strcmp(box->fs, "imap") == 0);
  369. Bprint(&b, "%.*s %.*lud %s\n", NDigest, m->info[IDigest], NUid, m->uid, buf);
  370. }
  371. Bterm(&b);
  372. if(fqid(fd, &qid) >= 0)
  373. box->impQid = qid;
  374. close(fd);
  375. mbUnlock(ml);
  376. }
  377. void
  378. wrImpFlags(char *buf, int flags, int killRecent)
  379. {
  380. int i;
  381. for(i = 0; i < NFlags; i++){
  382. if((flags & flagChars[i].v)
  383. && (flagChars[i].v != MRecent || !killRecent))
  384. buf[i] = flagChars[i].name[0];
  385. else
  386. buf[i] = '-';
  387. }
  388. buf[i] = '\0';
  389. }
  390. int
  391. emptyImp(char *mbox)
  392. {
  393. Dir *d;
  394. int32_t mode;
  395. int fd;
  396. fd = cdCreate(mboxDir, impName(mbox), OWRITE, 0664);
  397. if(fd < 0)
  398. return -1;
  399. d = cdDirstat(mboxDir, mbox);
  400. if(d == nil){
  401. close(fd);
  402. return -1;
  403. }
  404. fprint(fd, "%s%.*lud %.*lud\n", IMPMAGIC, NUid, d->mtime, NUid, 1UL);
  405. mode = d->mode & 0777;
  406. nulldir(d);
  407. d->mode = mode;
  408. dirfwstat(fd, d);
  409. free(d);
  410. return fd;
  411. }
  412. /*
  413. * try to match permissions with mbox
  414. */
  415. static int
  416. createImp(Box *box, Qid *qid)
  417. {
  418. Dir *d;
  419. int32_t mode;
  420. int fd;
  421. fd = cdCreate(mboxDir, box->imp, OREAD, 0664);
  422. if(fd < 0)
  423. return -1;
  424. d = cdDirstat(mboxDir, box->name);
  425. if(d != nil){
  426. mode = d->mode & 0777;
  427. nulldir(d);
  428. d->mode = mode;
  429. dirfwstat(fd, d);
  430. free(d);
  431. }
  432. if(fqid(fd, qid) < 0){
  433. close(fd);
  434. return -1;
  435. }
  436. return fd;
  437. }
  438. /*
  439. * read or re-read a .imp file.
  440. * this is tricky:
  441. * messages can be deleted by another agent
  442. * we might still have a Msg for an expunged message,
  443. * because we haven't told the client yet.
  444. * we can have a Msg without a .imp entry.
  445. * flag information is added at the end of the .imp by copy & append
  446. * there can be duplicate messages (same digests).
  447. *
  448. * look up existing messages based on uid.
  449. * look up new messages based on in order digest matching.
  450. *
  451. * note: in the face of duplicate messages, one of which is deleted,
  452. * two active servers may decide different ones are valid, and so return
  453. * different uids for the messages. this situation will stablize when the servers exit.
  454. */
  455. static int
  456. parseImp(Biobuf *b, Box *box)
  457. {
  458. Msg *m, *mm;
  459. char *s, *t, *toks[3];
  460. uint32_t uid, u;
  461. int match, n;
  462. m = box->msgs;
  463. s = Brdline(b, '\n');
  464. if(s == nil || Blinelen(b) != STRLEN(IMPMAGIC)
  465. || strncmp(s, IMPMAGIC, STRLEN(IMPMAGIC)) != 0)
  466. return 0;
  467. s = Brdline(b, '\n');
  468. if(s == nil || Blinelen(b) != 2*NUid + 2)
  469. return 0;
  470. s[2*NUid + 1] = '\0';
  471. u = strtoul(s, &t, 10);
  472. if(u != box->uidvalidity && box->uidvalidity != 0)
  473. return 0;
  474. box->uidvalidity = u;
  475. if(*t != ' ' || t != s + NUid)
  476. return 0;
  477. t++;
  478. u = strtoul(t, &t, 10);
  479. if(box->uidnext > u)
  480. return 0;
  481. box->uidnext = u;
  482. if(t != s + 2*NUid+1 || box->uidnext == 0)
  483. return 0;
  484. uid = ~0;
  485. while(m != nil){
  486. s = Brdline(b, '\n');
  487. if(s == nil)
  488. break;
  489. n = Blinelen(b) - 1;
  490. if(n != NDigest + NUid + NFlags + 2
  491. || s[NDigest] != ' ' || s[NDigest + NUid + 1] != ' ')
  492. return 0;
  493. toks[0] = s;
  494. s[NDigest] = '\0';
  495. toks[1] = s + NDigest + 1;
  496. s[NDigest + NUid + 1] = '\0';
  497. toks[2] = s + NDigest + NUid + 2;
  498. s[n] = '\0';
  499. t = toks[1];
  500. u = strtoul(t, &t, 10);
  501. if(*t != '\0' || uid != ~0 && (uid >= u && u || u && !uid))
  502. return 0;
  503. uid = u;
  504. /*
  505. * zero uid => added by append or copy, only flags valid
  506. * can only match messages without uids, but this message
  507. * may not be the next one, and may have been deleted.
  508. */
  509. if(!uid){
  510. for(; m != nil && m->uid; m = m->next)
  511. ;
  512. for(mm = m; mm != nil; mm = mm->next){
  513. if(mm->info[IDigest] != nil &&
  514. strcmp(mm->info[IDigest], toks[0]) == 0){
  515. if(!mm->uid)
  516. mm->flags = 0;
  517. if(!impFlags(box, mm, toks[2]))
  518. return 0;
  519. m = mm->next;
  520. break;
  521. }
  522. }
  523. continue;
  524. }
  525. /*
  526. * ignore expunged messages,
  527. * and messages already assigned uids which don't match this uid.
  528. * such messages must have been deleted by another imap server,
  529. * which updated the mailbox and .imp file since we read the mailbox,
  530. * or because upas/fs got confused by consecutive duplicate messages,
  531. * the first of which was deleted by another imap server.
  532. */
  533. for(; m != nil && (m->expunged || m->uid && m->uid < uid); m = m->next)
  534. ;
  535. if(m == nil)
  536. break;
  537. /*
  538. * only check for digest match on the next message,
  539. * since it comes before all other messages, and therefore
  540. * must be in the .imp file if they should be.
  541. */
  542. match = m->info[IDigest] != nil &&
  543. strcmp(m->info[IDigest], toks[0]) == 0;
  544. if(uid && (m->uid == uid || !m->uid && match)){
  545. if(!match)
  546. bye("inconsistent uid");
  547. /*
  548. * wipe out recent flag if some other server saw this new message.
  549. * it will be read from the .imp file if is really should be set,
  550. * ie the message was only seen by a status command.
  551. */
  552. if(!m->uid)
  553. m->flags = 0;
  554. if(!impFlags(box, m, toks[2]))
  555. return 0;
  556. m->uid = uid;
  557. m = m->next;
  558. }
  559. }
  560. return 1;
  561. }
  562. /*
  563. * parse .imp flags
  564. */
  565. static int
  566. impFlags(Box *box, Msg *m, char *flags)
  567. {
  568. int i, f;
  569. f = 0;
  570. for(i = 0; i < NFlags; i++){
  571. if(flags[i] == '-')
  572. continue;
  573. if(flags[i] != flagChars[i].name[0])
  574. return 0;
  575. f |= flagChars[i].v;
  576. }
  577. /*
  578. * recent flags are set until the first time message's box is selected or examined.
  579. * it may be stored in the file as a side effect of a status or subscribe command;
  580. * if so, clear it out.
  581. */
  582. if((f & MRecent) && strcmp(box->fs, "imap") == 0)
  583. box->dirtyImp = 1;
  584. f |= m->flags & MRecent;
  585. /*
  586. * all old messages with changed flags should be reported to the client
  587. */
  588. if(m->uid && m->flags != f){
  589. box->sendFlags = 1;
  590. m->sendFlags = 1;
  591. }
  592. m->flags = f;
  593. return 1;
  594. }
  595. /*
  596. * assign uids to any new messages
  597. * which aren't already in the .imp file.
  598. * sum up totals for flag values.
  599. */
  600. static void
  601. boxFlags(Box *box)
  602. {
  603. Msg *m;
  604. box->recent = 0;
  605. for(m = box->msgs; m != nil; m = m->next){
  606. if(m->uid == 0){
  607. box->dirtyImp = 1;
  608. box->uidnext = uidRenumber(m, box->uidnext, 0);
  609. }
  610. if(m->flags & MRecent)
  611. box->recent++;
  612. }
  613. }
  614. static uint32_t
  615. uidRenumber(Msg *m, uint32_t uid, int force)
  616. {
  617. for(; m != nil; m = m->next){
  618. if(!force && m->uid != 0)
  619. bye("uid renumbering with a valid uid");
  620. m->uid = uid++;
  621. }
  622. return uid;
  623. }
  624. void
  625. closeBox(Box *box, int opened)
  626. {
  627. Msg *m, *next;
  628. /*
  629. * make sure to leave the mailbox directory so upas/fs can close the mailbox
  630. */
  631. myChdir(mboxDir);
  632. if(box->writable){
  633. deleteMsgs(box);
  634. if(expungeMsgs(box, 0))
  635. closeImp(box, checkBox(box, 1));
  636. }
  637. if(fprint(fsCtl, "close %s", box->fs) < 0 && opened)
  638. bye("can't talk to mail server");
  639. for(m = box->msgs; m != nil; m = next){
  640. next = m->next;
  641. freeMsg(m);
  642. }
  643. free(box->name);
  644. free(box->fs);
  645. free(box->fsDir);
  646. free(box->imp);
  647. free(box);
  648. }
  649. int
  650. deleteMsgs(Box *box)
  651. {
  652. Msg *m;
  653. char buf[BufSize], *p, *start;
  654. int ok;
  655. if(!box->writable)
  656. return 0;
  657. /*
  658. * first pass: delete messages; gang the writes together for speed.
  659. */
  660. ok = 1;
  661. start = seprint(buf, buf + sizeof(buf), "delete %s", box->fs);
  662. p = start;
  663. for(m = box->msgs; m != nil; m = m->next){
  664. if((m->flags & MDeleted) && !m->expunged){
  665. m->expunged = 1;
  666. p = seprint(p, buf + sizeof(buf), " %lud", m->id);
  667. if(p + 32 >= buf + sizeof(buf)){
  668. if(write(fsCtl, buf, p - buf) < 0)
  669. bye("can't talk to mail server");
  670. p = start;
  671. }
  672. }
  673. }
  674. if(p != start && write(fsCtl, buf, p - buf) < 0)
  675. bye("can't talk to mail server");
  676. return ok;
  677. }
  678. /*
  679. * second pass: remove the message structure,
  680. * and renumber message sequence numbers.
  681. * update messages counts in mailbox.
  682. * returns true if anything changed.
  683. */
  684. int
  685. expungeMsgs(Box *box, int send)
  686. {
  687. Msg *m, *next, *last;
  688. uint32_t n;
  689. n = 0;
  690. last = nil;
  691. for(m = box->msgs; m != nil; m = next){
  692. m->seq -= n;
  693. next = m->next;
  694. if(m->expunged){
  695. if(send)
  696. Bprint(&bout, "* %lud expunge\r\n", m->seq);
  697. if(m->flags & MRecent)
  698. box->recent--;
  699. n++;
  700. if(last == nil)
  701. box->msgs = next;
  702. else
  703. last->next = next;
  704. freeMsg(m);
  705. }else
  706. last = m;
  707. }
  708. if(n){
  709. box->max -= n;
  710. box->dirtyImp = 1;
  711. }
  712. return n;
  713. }
  714. static void
  715. fsInit(void)
  716. {
  717. if(fsCtl >= 0)
  718. return;
  719. fsCtl = open("/mail/fs/ctl", ORDWR);
  720. if(fsCtl < 0)
  721. bye("can't open mail file system");
  722. if(fprint(fsCtl, "close mbox") < 0)
  723. bye("can't initialize mail file system");
  724. }
  725. static char *stoplist[] =
  726. {
  727. "mbox",
  728. "pipeto",
  729. "forward",
  730. "names",
  731. "pipefrom",
  732. "headers",
  733. "imap.ok",
  734. 0
  735. };
  736. enum {
  737. Maxokbytes = 4096,
  738. Maxfolders = Maxokbytes / 4,
  739. };
  740. static char *folders[Maxfolders];
  741. static char *folderbuff;
  742. static void
  743. readokfolders(void)
  744. {
  745. int fd, nr;
  746. fd = open("imap.ok", OREAD);
  747. if(fd < 0)
  748. return;
  749. folderbuff = malloc(Maxokbytes);
  750. if(folderbuff == nil) {
  751. close(fd);
  752. return;
  753. }
  754. nr = read(fd, folderbuff, Maxokbytes-1); /* once is ok */
  755. close(fd);
  756. if(nr < 0){
  757. free(folderbuff);
  758. folderbuff = nil;
  759. return;
  760. }
  761. folderbuff[nr] = 0;
  762. tokenize(folderbuff, folders, nelem(folders));
  763. }
  764. /*
  765. * reject bad mailboxes based on mailbox name
  766. */
  767. int
  768. okMbox(char *path)
  769. {
  770. char *name;
  771. int i;
  772. if(folderbuff == nil && access("imap.ok", AREAD) == 0)
  773. readokfolders();
  774. name = strrchr(path, '/');
  775. if(name == nil)
  776. name = path;
  777. else
  778. name++;
  779. if(folderbuff != nil){
  780. for(i = 0; i < nelem(folders) && folders[i] != nil; i++)
  781. if(cistrcmp(folders[i], name) == 0)
  782. return 1;
  783. return 0;
  784. }
  785. if(strlen(name) + STRLEN(".imp") >= MboxNameLen)
  786. return 0;
  787. for(i = 0; stoplist[i]; i++)
  788. if(strcmp(name, stoplist[i]) == 0)
  789. return 0;
  790. if(isprefix("L.", name) || isprefix("imap-tmp.", name)
  791. || issuffix(".imp", name)
  792. || strcmp("imap.subscribed", name) == 0
  793. || isdotdot(name) || name[0] == '/')
  794. return 0;
  795. return 1;
  796. }