mbox.c 16 KB

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