mbox.c 17 KB

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