imap4d.c 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087
  1. #include <u.h>
  2. #include <libc.h>
  3. #include <auth.h>
  4. #include <bio.h>
  5. #include "imap4d.h"
  6. /*
  7. * these should be in libraries
  8. */
  9. char *csquery(char *attr, char *val, char *rattr);
  10. /*
  11. * /lib/rfc/rfc2060 imap4rev1
  12. * /lib/rfc/rfc2683 is implementation advice
  13. * /lib/rfc/rfc2342 is namespace capability
  14. * /lib/rfc/rfc2222 is security protocols
  15. * /lib/rfc/rfc1731 is security protocols
  16. * /lib/rfc/rfc2221 is LOGIN-REFERRALS
  17. * /lib/rfc/rfc2193 is MAILBOX-REFERRALS
  18. * /lib/rfc/rfc2177 is IDLE capability
  19. * /lib/rfc/rfc2195 is CRAM-MD5 authentication
  20. * /lib/rfc/rfc2088 is LITERAL+ capability
  21. * /lib/rfc/rfc1760 is S/Key authentication
  22. *
  23. * outlook uses "Secure Password Authentication" aka ntlm authentication
  24. *
  25. * capabilities from nslocum
  26. * CAPABILITY IMAP4 IMAP4REV1 NAMESPACE IDLE SCAN SORT MAILBOX-REFERRALS LOGIN-REFERRALS AUTH=LOGIN THREAD=ORDEREDSUBJECT
  27. */
  28. typedef struct ParseCmd ParseCmd;
  29. enum
  30. {
  31. UlongMax = 4294967295,
  32. };
  33. struct ParseCmd
  34. {
  35. char *name;
  36. void (*f)(char *tg, char *cmd);
  37. };
  38. static void appendCmd(char *tg, char *cmd);
  39. static void authenticateCmd(char *tg, char *cmd);
  40. static void capabilityCmd(char *tg, char *cmd);
  41. static void closeCmd(char *tg, char *cmd);
  42. static void copyCmd(char *tg, char *cmd);
  43. static void createCmd(char *tg, char *cmd);
  44. static void deleteCmd(char *tg, char *cmd);
  45. static void expungeCmd(char *tg, char *cmd);
  46. static void fetchCmd(char *tg, char *cmd);
  47. static void idleCmd(char *tg, char *cmd);
  48. static void listCmd(char *tg, char *cmd);
  49. static void loginCmd(char *tg, char *cmd);
  50. static void logoutCmd(char *tg, char *cmd);
  51. static void namespaceCmd(char *tg, char *cmd);
  52. static void noopCmd(char *tg, char *cmd);
  53. static void renameCmd(char *tg, char *cmd);
  54. static void searchCmd(char *tg, char *cmd);
  55. static void selectCmd(char *tg, char *cmd);
  56. static void statusCmd(char *tg, char *cmd);
  57. static void storeCmd(char *tg, char *cmd);
  58. static void subscribeCmd(char *tg, char *cmd);
  59. static void uidCmd(char *tg, char *cmd);
  60. static void unsubscribeCmd(char *tg, char *cmd);
  61. static void copyUCmd(char *tg, char *cmd, int uids);
  62. static void fetchUCmd(char *tg, char *cmd, int uids);
  63. static void searchUCmd(char *tg, char *cmd, int uids);
  64. static void storeUCmd(char *tg, char *cmd, int uids);
  65. static void imap4(int);
  66. static void status(int expungeable, int uids);
  67. static void cleaner(void);
  68. static void check(void);
  69. static int catcher(void*, char*);
  70. static Search *searchKey(int first);
  71. static Search *searchKeys(int first, Search *tail);
  72. static char *astring(void);
  73. static char *atomString(char *disallowed, char *initial);
  74. static char *atom(void);
  75. static void badsyn(void);
  76. static void clearcmd(void);
  77. static char *command(void);
  78. static void crnl(void);
  79. static Fetch *fetchAtt(char *s, Fetch *f);
  80. static Fetch *fetchWhat(void);
  81. static int flagList(void);
  82. static int flags(void);
  83. static int getc(void);
  84. static char *listmbox(void);
  85. static char *literal(void);
  86. static ulong litlen(void);
  87. static MsgSet *msgSet(void);
  88. static void mustBe(int c);
  89. static ulong number(int nonzero);
  90. static int peekc(void);
  91. static char *quoted(void);
  92. static void sectText(Fetch *f, int mimeOk);
  93. static ulong seqNo(void);
  94. static Store *storeWhat(void);
  95. static char *tag(void);
  96. static void ungetc(void);
  97. static ParseCmd SNonAuthed[] =
  98. {
  99. {"capability", capabilityCmd},
  100. {"logout", logoutCmd},
  101. {"x-exit", logoutCmd},
  102. {"noop", noopCmd},
  103. {"login", loginCmd},
  104. {"authenticate", authenticateCmd},
  105. nil
  106. };
  107. static ParseCmd SAuthed[] =
  108. {
  109. {"capability", capabilityCmd},
  110. {"logout", logoutCmd},
  111. {"x-exit", logoutCmd},
  112. {"noop", noopCmd},
  113. {"append", appendCmd},
  114. {"create", createCmd},
  115. {"delete", deleteCmd},
  116. {"examine", selectCmd},
  117. {"select", selectCmd},
  118. {"idle", idleCmd},
  119. {"list", listCmd},
  120. {"lsub", listCmd},
  121. {"namespace", namespaceCmd},
  122. {"rename", renameCmd},
  123. {"status", statusCmd},
  124. {"subscribe", subscribeCmd},
  125. {"unsubscribe", unsubscribeCmd},
  126. nil
  127. };
  128. static ParseCmd SSelected[] =
  129. {
  130. {"capability", capabilityCmd},
  131. {"logout", logoutCmd},
  132. {"x-exit", logoutCmd},
  133. {"noop", noopCmd},
  134. {"append", appendCmd},
  135. {"create", createCmd},
  136. {"delete", deleteCmd},
  137. {"examine", selectCmd},
  138. {"select", selectCmd},
  139. {"idle", idleCmd},
  140. {"list", listCmd},
  141. {"lsub", listCmd},
  142. {"namespace", namespaceCmd},
  143. {"rename", renameCmd},
  144. {"status", statusCmd},
  145. {"subscribe", subscribeCmd},
  146. {"unsubscribe", unsubscribeCmd},
  147. {"check", noopCmd},
  148. {"close", closeCmd},
  149. {"copy", copyCmd},
  150. {"expunge", expungeCmd},
  151. {"fetch", fetchCmd},
  152. {"search", searchCmd},
  153. {"store", storeCmd},
  154. {"uid", uidCmd},
  155. nil
  156. };
  157. static char *atomStop = "(){%*\"\\";
  158. static Chalstate *chal;
  159. static int chaled;
  160. static ParseCmd *imapState;
  161. static jmp_buf parseJmp;
  162. static char *parseMsg;
  163. static int allowPass;
  164. static int allowCR;
  165. static int exiting;
  166. static QLock imaplock;
  167. static int idlepid = -1;
  168. Biobuf bout;
  169. Biobuf bin;
  170. char username[UserNameLen];
  171. char mboxDir[MboxNameLen];
  172. char *servername;
  173. char *site;
  174. char *remote;
  175. Box *selected;
  176. Bin *parseBin;
  177. int debug;
  178. void
  179. main(int argc, char *argv[])
  180. {
  181. char *s, *t;
  182. int preauth, n;
  183. Binit(&bin, 0, OREAD);
  184. Binit(&bout, 1, OWRITE);
  185. preauth = 0;
  186. allowPass = 0;
  187. allowCR = 0;
  188. ARGBEGIN{
  189. case 'a':
  190. preauth = 1;
  191. break;
  192. case 'd':
  193. site = ARGF();
  194. break;
  195. case 'c':
  196. allowCR = 1;
  197. break;
  198. case 'p':
  199. allowPass = 1;
  200. break;
  201. case 'r':
  202. remote = ARGF();
  203. break;
  204. case 's':
  205. servername = ARGF();
  206. break;
  207. case 'v':
  208. debug = 1;
  209. debuglog("imap4d debugging enabled\n");
  210. break;
  211. default:
  212. fprint(2, "usage: ip/imap4d [-acpv] [-d site] [-r remotehost] [-s servername]\n");
  213. bye("usage");
  214. break;
  215. }ARGEND
  216. if(allowPass && allowCR){
  217. fprint(2, "%s: -c and -p are mutually exclusive\n", argv0);
  218. bye("usage");
  219. }
  220. if(preauth)
  221. setupuser(nil);
  222. if(servername == nil){
  223. servername = csquery("sys", sysname(), "dom");
  224. if(servername == nil)
  225. servername = sysname();
  226. if(servername == nil){
  227. fprint(2, "ip/imap4d can't find server name: %r\n");
  228. bye("can't find system name");
  229. }
  230. }
  231. if(site == nil){
  232. t = getenv("site");
  233. if(t == nil)
  234. site = servername;
  235. else{
  236. n = strlen(t);
  237. s = strchr(servername, '.');
  238. if(s == nil)
  239. s = servername;
  240. else
  241. s++;
  242. n += strlen(s) + 2;
  243. site = emalloc(n);
  244. snprint(site, n, "%s.%s", t, s);
  245. }
  246. }
  247. rfork(RFNOTEG|RFREND);
  248. atnotify(catcher, 1);
  249. qlock(&imaplock);
  250. atexit(cleaner);
  251. imap4(preauth);
  252. }
  253. static void
  254. imap4(int preauth)
  255. {
  256. char *volatile tg;
  257. char *volatile cmd;
  258. ParseCmd *st;
  259. if(preauth){
  260. Bprint(&bout, "* preauth %s IMAP4rev1 server ready user %s authenticated\r\n", servername, username);
  261. imapState = SAuthed;
  262. }else{
  263. Bprint(&bout, "* OK %s IMAP4rev1 server ready\r\n", servername);
  264. imapState = SNonAuthed;
  265. }
  266. if(Bflush(&bout) < 0)
  267. writeErr();
  268. chaled = 0;
  269. tg = nil;
  270. cmd = nil;
  271. if(setjmp(parseJmp)){
  272. if(tg == nil)
  273. Bprint(&bout, "* bad empty command line: %s\r\n", parseMsg);
  274. else if(cmd == nil)
  275. Bprint(&bout, "%s BAD no command: %s\r\n", tg, parseMsg);
  276. else
  277. Bprint(&bout, "%s BAD %s %s\r\n", tg, cmd, parseMsg);
  278. clearcmd();
  279. if(Bflush(&bout) < 0)
  280. writeErr();
  281. binfree(&parseBin);
  282. }
  283. for(;;){
  284. if(mbLocked())
  285. bye("internal error: mailbox lock held");
  286. tg = nil;
  287. cmd = nil;
  288. tg = tag();
  289. mustBe(' ');
  290. cmd = atom();
  291. /*
  292. * note: outlook express is broken: it requires echoing the
  293. * command as part of matching response
  294. */
  295. for(st = imapState; st->name != nil; st++){
  296. if(cistrcmp(cmd, st->name) == 0){
  297. (*st->f)(tg, cmd);
  298. break;
  299. }
  300. }
  301. if(st->name == nil){
  302. clearcmd();
  303. Bprint(&bout, "%s BAD %s illegal command\r\n", tg, cmd);
  304. }
  305. if(Bflush(&bout) < 0)
  306. writeErr();
  307. binfree(&parseBin);
  308. }
  309. }
  310. void
  311. bye(char *fmt, ...)
  312. {
  313. va_list arg;
  314. va_start(arg, fmt);
  315. Bprint(&bout, "* bye ");
  316. Bvprint(&bout, fmt, arg);
  317. Bprint(&bout, "\r\n");
  318. Bflush(&bout);
  319. exits("rob2");
  320. exits(0);
  321. }
  322. void
  323. parseErr(char *msg)
  324. {
  325. parseMsg = msg;
  326. longjmp(parseJmp, 1);
  327. }
  328. /*
  329. * an error occured while writing to the client
  330. */
  331. void
  332. writeErr(void)
  333. {
  334. cleaner();
  335. _exits("connection closed");
  336. }
  337. static int
  338. catcher(void *v, char *msg)
  339. {
  340. USED(v);
  341. if(strstr(msg, "closed pipe") != nil)
  342. return 1;
  343. return 0;
  344. }
  345. /*
  346. * wipes out the idleCmd backgroung process if it is around.
  347. * this can only be called if the current proc has qlocked imaplock.
  348. * it must be the last piece of imap4d code executed.
  349. */
  350. static void
  351. cleaner(void)
  352. {
  353. int i;
  354. if(idlepid < 0)
  355. return;
  356. exiting = 1;
  357. close(0);
  358. close(1);
  359. close(2);
  360. /*
  361. * the other proc is either stuck in a read, a sleep,
  362. * or is trying to lock imap4lock.
  363. * get him out of it so he can exit cleanly
  364. */
  365. qunlock(&imaplock);
  366. for(i = 0; i < 4; i++)
  367. postnote(PNGROUP, getpid(), "die");
  368. }
  369. /*
  370. * send any pending status updates to the client
  371. * careful: shouldn't exit, because called by idle polling proc
  372. *
  373. * can't always send pending info
  374. * in particular, can't send expunge info
  375. * in response to a fetch, store, or search command.
  376. *
  377. * rfc2060 5.2: server must send mailbox size updates
  378. * rfc2060 5.2: server may send flag updates
  379. * rfc2060 5.5: servers prohibited from sending expunge while fetch, store, search in progress
  380. * rfc2060 7: in selected state, server checks mailbox for new messages as part of every command
  381. * sends untagged EXISTS and RECENT respsonses reflecting new size of the mailbox
  382. * should also send appropriate untagged FETCH and EXPUNGE messages if another agent
  383. * changes the state of any message flags or expunges any messages
  384. * rfc2060 7.4.1 expunge server response must not be sent when no command is in progress,
  385. * nor while responding to a fetch, stort, or search command (uid versions are ok)
  386. * command only "in progress" after entirely parsed.
  387. *
  388. * strategy for third party deletion of messages or of a mailbox
  389. *
  390. * deletion of a selected mailbox => act like all message are expunged
  391. * not strictly allowed by rfc2180, but close to method 3.2.
  392. *
  393. * renaming same as deletion
  394. *
  395. * copy
  396. * reject iff a deleted message is in the request
  397. *
  398. * search, store, fetch operations on expunged messages
  399. * ignore the expunged messages
  400. * return tagged no if referenced
  401. */
  402. static void
  403. status(int expungeable, int uids)
  404. {
  405. int tell;
  406. if(!selected)
  407. return;
  408. tell = 0;
  409. if(expungeable)
  410. tell = expungeMsgs(selected, 1);
  411. if(selected->sendFlags)
  412. sendFlags(selected, uids);
  413. if(tell || selected->toldMax != selected->max){
  414. Bprint(&bout, "* %lud exists\r\n", selected->max);
  415. selected->toldMax = selected->max;
  416. }
  417. if(tell || selected->toldRecent != selected->recent){
  418. Bprint(&bout, "* %lud recent\r\n", selected->recent);
  419. selected->toldRecent = selected->recent;
  420. }
  421. if(tell)
  422. closeImp(selected, checkBox(selected, 1));
  423. }
  424. /*
  425. * careful: can't exit, because called by idle polling proc
  426. */
  427. static void
  428. check(void)
  429. {
  430. if(!selected)
  431. return;
  432. checkBox(selected, 0);
  433. status(1, 0);
  434. }
  435. static void
  436. appendCmd(char *tg, char *cmd)
  437. {
  438. char *mbox, head[128];
  439. ulong t, n, now;
  440. int flags, ok;
  441. mustBe(' ');
  442. mbox = astring();
  443. mustBe(' ');
  444. flags = 0;
  445. if(peekc() == '('){
  446. flags = flagList();
  447. mustBe(' ');
  448. }
  449. now = time(nil);
  450. if(peekc() == '"'){
  451. t = imap4DateTime(quoted());
  452. if(t == ~0)
  453. parseErr("illegal date format");
  454. mustBe(' ');
  455. if(t > now)
  456. t = now;
  457. }else
  458. t = now;
  459. n = litlen();
  460. mbox = mboxName(mbox);
  461. if(mbox == nil || !okMbox(mbox)){
  462. check();
  463. Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
  464. return;
  465. }
  466. if(!cdExists(mboxDir, mbox)){
  467. check();
  468. Bprint(&bout, "%s NO [TRYCREATE] %s mailbox does not exist\r\n", tg, cmd);
  469. return;
  470. }
  471. snprint(head, sizeof(head), "From %s %s", username, ctime(t));
  472. ok = appendSave(mbox, flags, head, &bin, n);
  473. crnl();
  474. check();
  475. if(ok)
  476. Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
  477. else
  478. Bprint(&bout, "%s NO %s message save failed\r\n", tg, cmd);
  479. }
  480. static void
  481. authenticateCmd(char *tg, char *cmd)
  482. {
  483. char *s, *t;
  484. mustBe(' ');
  485. s = atom();
  486. crnl();
  487. auth_freechal(chal);
  488. chal = nil;
  489. if(cistrcmp(s, "cram-md5") == 0){
  490. t = cramauth();
  491. if(t == nil){
  492. Bprint(&bout, "%s OK %s\r\n", tg, cmd);
  493. imapState = SAuthed;
  494. }else
  495. Bprint(&bout, "%s NO %s failed %s\r\n", tg, cmd, t);
  496. }else
  497. Bprint(&bout, "%s NO %s unsupported authentication protocol\r\n", tg, cmd);
  498. }
  499. static void
  500. capabilityCmd(char *tg, char *cmd)
  501. {
  502. crnl();
  503. check();
  504. // nslocum's capabilities
  505. // Bprint(&bout, "* CAPABILITY IMAP4 IMAP4REV1 NAMESPACE IDLE SCAN SORT MAILBOX-REFERRALS LOGIN-REFERRALS AUTH=LOGIN THREAD=ORDEREDSUBJECT\r\n");
  506. Bprint(&bout, "* CAPABILITY IMAP4REV1 IDLE NAMESPACE AUTH=CRAM-MD5\r\n");
  507. Bprint(&bout, "%s OK %s\r\n", tg, cmd);
  508. }
  509. static void
  510. closeCmd(char *tg, char *cmd)
  511. {
  512. crnl();
  513. imapState = SAuthed;
  514. closeBox(selected, 1);
  515. selected = nil;
  516. Bprint(&bout, "%s OK %s mailbox closed, now in authenticated state\r\n", tg, cmd);
  517. }
  518. /*
  519. * note: message id's are before any pending expunges
  520. */
  521. static void
  522. copyCmd(char *tg, char *cmd)
  523. {
  524. copyUCmd(tg, cmd, 0);
  525. }
  526. static void
  527. copyUCmd(char *tg, char *cmd, int uids)
  528. {
  529. MsgSet *ms;
  530. char *uid, *mbox;
  531. ulong max;
  532. int ok;
  533. mustBe(' ');
  534. ms = msgSet();
  535. mustBe(' ');
  536. mbox = astring();
  537. crnl();
  538. uid = "";
  539. if(uids)
  540. uid = "uid ";
  541. mbox = mboxName(mbox);
  542. if(mbox == nil || !okMbox(mbox)){
  543. status(1, uids);
  544. Bprint(&bout, "%s NO %s%s bad mailbox\r\n", tg, uid, cmd);
  545. return;
  546. }
  547. if(!cdExists(mboxDir, mbox)){
  548. check();
  549. Bprint(&bout, "%s NO [TRYCREATE] %s mailbox does not exist\r\n", tg, cmd);
  550. return;
  551. }
  552. max = selected->max;
  553. checkBox(selected, 0);
  554. ok = forMsgs(selected, ms, max, uids, copyCheck, nil);
  555. if(ok)
  556. ok = forMsgs(selected, ms, max, uids, copySave, mbox);
  557. status(1, uids);
  558. if(ok)
  559. Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
  560. else
  561. Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd);
  562. }
  563. static void
  564. createCmd(char *tg, char *cmd)
  565. {
  566. char *mbox, *m;
  567. int fd, slash;
  568. mustBe(' ');
  569. mbox = astring();
  570. crnl();
  571. check();
  572. m = strchr(mbox, '\0');
  573. slash = m != mbox && m[-1] == '/';
  574. mbox = mboxName(mbox);
  575. if(mbox == nil || !okMbox(mbox)){
  576. Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
  577. return;
  578. }
  579. if(cistrcmp(mbox, "inbox") == 0){
  580. Bprint(&bout, "%s NO %s cannot remotely create INBOX\r\n", tg, cmd);
  581. return;
  582. }
  583. if(access(mbox, AEXIST) >= 0){
  584. Bprint(&bout, "%s NO %s mailbox already exists\r\n", tg, cmd);
  585. return;
  586. }
  587. fd = createBox(mbox, slash);
  588. close(fd);
  589. if(fd < 0)
  590. Bprint(&bout, "%s NO %s cannot create mailbox %s\r\n", tg, cmd, mbox);
  591. else
  592. Bprint(&bout, "%s OK %s %s completed\r\n", tg, mbox, cmd);
  593. }
  594. static void
  595. deleteCmd(char *tg, char *cmd)
  596. {
  597. char *mbox, *imp;
  598. mustBe(' ');
  599. mbox = astring();
  600. crnl();
  601. check();
  602. mbox = mboxName(mbox);
  603. if(mbox == nil || !okMbox(mbox)){
  604. Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
  605. return;
  606. }
  607. imp = impName(mbox);
  608. if(cistrcmp(mbox, "inbox") == 0
  609. || imp != nil && cdRemove(mboxDir, imp) < 0 && cdExists(mboxDir, imp)
  610. || cdRemove(mboxDir, mbox) < 0)
  611. Bprint(&bout, "%s NO %s cannot delete mailbox %s\r\n", tg, cmd, mbox);
  612. else
  613. Bprint(&bout, "%s OK %s %s completed\r\n", tg, mbox, cmd);
  614. }
  615. static void
  616. expungeCmd(char *tg, char *cmd)
  617. {
  618. int ok;
  619. crnl();
  620. ok = deleteMsgs(selected);
  621. check();
  622. if(ok)
  623. Bprint(&bout, "%s OK %s messages erased\r\n", tg, cmd);
  624. else
  625. Bprint(&bout, "%s NO %s some messages not expunged\r\n", tg, cmd);
  626. }
  627. static void
  628. fetchCmd(char *tg, char *cmd)
  629. {
  630. fetchUCmd(tg, cmd, 0);
  631. }
  632. static void
  633. fetchUCmd(char *tg, char *cmd, int uids)
  634. {
  635. Fetch *f;
  636. MsgSet *ms;
  637. MbLock *ml;
  638. char *uid;
  639. ulong max;
  640. int ok;
  641. mustBe(' ');
  642. ms = msgSet();
  643. mustBe(' ');
  644. f = fetchWhat();
  645. crnl();
  646. uid = "";
  647. if(uids)
  648. uid = "uid ";
  649. max = selected->max;
  650. ml = checkBox(selected, 1);
  651. if(ml != nil)
  652. forMsgs(selected, ms, max, uids, fetchSeen, f);
  653. closeImp(selected, ml);
  654. ok = ml != nil && forMsgs(selected, ms, max, uids, fetchMsg, f);
  655. status(uids, uids);
  656. if(ok)
  657. Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
  658. else
  659. Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd);
  660. }
  661. static void
  662. idleCmd(char *tg, char *cmd)
  663. {
  664. int c, pid;
  665. crnl();
  666. Bprint(&bout, "+ idling, waiting for done\r\n");
  667. if(Bflush(&bout) < 0)
  668. writeErr();
  669. if(idlepid < 0){
  670. pid = rfork(RFPROC|RFMEM|RFNOWAIT);
  671. if(pid == 0){
  672. for(;;){
  673. qlock(&imaplock);
  674. if(exiting)
  675. break;
  676. /*
  677. * parent may have changed curDir, but it doesn't change our .
  678. */
  679. resetCurDir();
  680. check();
  681. if(Bflush(&bout) < 0)
  682. writeErr();
  683. qunlock(&imaplock);
  684. sleep(15*1000);
  685. enableForwarding();
  686. }
  687. _exits("rob3");
  688. _exits(0);
  689. }
  690. idlepid = pid;
  691. }
  692. qunlock(&imaplock);
  693. /*
  694. * clear out the next line, which is supposed to contain (case-insensitive)
  695. * done\n
  696. * this is special code since it has to dance with the idle polling proc
  697. * and handle exiting correctly.
  698. */
  699. for(;;){
  700. c = getc();
  701. if(c < 0){
  702. qlock(&imaplock);
  703. if(!exiting)
  704. cleaner();
  705. _exits("rob4");
  706. _exits(0);
  707. }
  708. if(c == '\n')
  709. break;
  710. }
  711. qlock(&imaplock);
  712. if(exiting)
  713. {_exits("rob5");
  714. _exits(0);
  715. }
  716. /*
  717. * child may have changed curDir, but it doesn't change our .
  718. */
  719. resetCurDir();
  720. check();
  721. Bprint(&bout, "%s OK %s terminated\r\n", tg, cmd);
  722. }
  723. static void
  724. listCmd(char *tg, char *cmd)
  725. {
  726. char *s, *t, *ss, *ref, *mbox;
  727. int n;
  728. mustBe(' ');
  729. s = astring();
  730. mustBe(' ');
  731. t = listmbox();
  732. crnl();
  733. check();
  734. ref = mutf7str(s);
  735. mbox = mutf7str(t);
  736. if(ref == nil || mbox == nil){
  737. Bprint(&bout, "%s BAD %s mailbox name not in modified utf-7\r\n", tg, cmd);
  738. return;
  739. }
  740. /*
  741. * special request for hierarchy delimiter and root name
  742. * root name appears to be name up to and including any delimiter,
  743. * or the empty string, if there is no delimiter.
  744. *
  745. * this must change if the # namespace convention is supported.
  746. */
  747. if(*mbox == '\0'){
  748. s = strchr(ref, '/');
  749. if(s == nil)
  750. ref = "";
  751. else
  752. s[1] = '\0';
  753. Bprint(&bout, "* %s (\\Noselect) \"/\" \"%s\"\r\n", cmd, ref);
  754. Bprint(&bout, "%s OK %s\r\n", tg, cmd);
  755. return;
  756. }
  757. /*
  758. * massage the listing name:
  759. * clean up the components individually,
  760. * then rip off componenets from the ref to
  761. * take care of leading ..'s in the mbox.
  762. *
  763. * the cleanup can wipe out * followed by a ..
  764. * tough luck if such a stupid pattern is given.
  765. */
  766. cleanname(mbox);
  767. if(strcmp(mbox, ".") == 0)
  768. *mbox = '\0';
  769. if(mbox[0] == '/')
  770. *ref = '\0';
  771. else if(*ref != '\0'){
  772. cleanname(ref);
  773. if(strcmp(ref, ".") == 0)
  774. *ref = '\0';
  775. }else
  776. *ref = '\0';
  777. while(*ref && isdotdot(mbox)){
  778. s = strrchr(ref, '/');
  779. if(s == nil)
  780. s = ref;
  781. if(isdotdot(s))
  782. break;
  783. *s = '\0';
  784. mbox += 2;
  785. if(*mbox == '/')
  786. mbox++;
  787. }
  788. if(*ref == '\0'){
  789. s = mbox;
  790. ss = s;
  791. }else{
  792. n = strlen(ref) + strlen(mbox) + 2;
  793. t = binalloc(&parseBin, n, 0);
  794. if(t == nil)
  795. parseErr("out of memory");
  796. snprint(t, n, "%s/%s", ref, mbox);
  797. s = t;
  798. ss = s + strlen(ref);
  799. }
  800. /*
  801. * only allow activity in /mail/box
  802. */
  803. if(s[0] == '/' || isdotdot(s)){
  804. Bprint(&bout, "%s NO illegal mailbox pattern\r\n", tg);
  805. return;
  806. }
  807. if(cistrcmp(cmd, "lsub") == 0)
  808. lsubBoxes(cmd, s, ss);
  809. else
  810. listBoxes(cmd, s, ss);
  811. Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
  812. }
  813. static char*
  814. passCR(char*u, char*p)
  815. {
  816. static char Ebadch[] = "can't get challenge";
  817. static char nchall[64];
  818. static char response[64];
  819. static Chalstate *ch = nil;
  820. AuthInfo *ai;
  821. again:
  822. if (ch == nil){
  823. if(!(ch = auth_challenge("proto=p9cr role=server user=%q", u)))
  824. return Ebadch;
  825. snprint(nchall, 64, " encrypt challenge: %s", ch->chal);
  826. return nchall;
  827. } else {
  828. strncpy(response, p, 64);
  829. ch->resp = response;
  830. ch->nresp = strlen(response);
  831. ai = auth_response(ch);
  832. auth_freechal(ch);
  833. ch = nil;
  834. if (ai == nil)
  835. goto again;
  836. setupuser(ai);
  837. return nil;
  838. }
  839. }
  840. static void
  841. loginCmd(char *tg, char *cmd)
  842. {
  843. char *s, *t;
  844. AuthInfo *ai;
  845. char*r;
  846. mustBe(' ');
  847. s = astring(); /* uid */
  848. mustBe(' ');
  849. t = astring(); /* password */
  850. crnl();
  851. if(allowCR){
  852. if ((r = passCR(s, t)) == nil){
  853. Bprint(&bout, "%s OK %s succeeded\r\n", tg, cmd);
  854. imapState = SAuthed;
  855. } else {
  856. Bprint(&bout, "* NO [ALERT] %s\r\n", r);
  857. Bprint(&bout, "%s NO %s succeeded\r\n", tg, cmd);
  858. }
  859. return;
  860. }
  861. else if(allowPass){
  862. if(ai = passLogin(s, t)){
  863. setupuser(ai);
  864. Bprint(&bout, "%s OK %s succeeded\r\n", tg, cmd);
  865. imapState = SAuthed;
  866. }else
  867. Bprint(&bout, "%s NO %s failed check\r\n", tg, cmd);
  868. return;
  869. }
  870. Bprint(&bout, "%s NO %s plaintext passwords disallowed\r\n", tg, cmd);
  871. }
  872. /*
  873. * logout or x-exit, which doesn't expunge the mailbox
  874. */
  875. static void
  876. logoutCmd(char *tg, char *cmd)
  877. {
  878. crnl();
  879. if(cmd[0] != 'x' && selected){
  880. closeBox(selected, 1);
  881. selected = nil;
  882. }
  883. Bprint(&bout, "* bye\r\n");
  884. Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
  885. exits("rob6");
  886. exits(0);
  887. }
  888. static void
  889. namespaceCmd(char *tg, char *cmd)
  890. {
  891. crnl();
  892. check();
  893. /*
  894. * personal, other users, shared namespaces
  895. * send back nil or descriptions of (prefix heirarchy-delim) for each case
  896. */
  897. Bprint(&bout, "* namespace ((\"\" \"/\")) nil nil\r\n");
  898. Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
  899. }
  900. static void
  901. noopCmd(char *tg, char *cmd)
  902. {
  903. crnl();
  904. check();
  905. Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
  906. enableForwarding();
  907. }
  908. /*
  909. * this is only a partial implementation
  910. * should copy files to other directories,
  911. * and copy & truncate inbox
  912. */
  913. static void
  914. renameCmd(char *tg, char *cmd)
  915. {
  916. char *from, *to;
  917. int ok;
  918. mustBe(' ');
  919. from = astring();
  920. mustBe(' ');
  921. to = astring();
  922. crnl();
  923. check();
  924. to = mboxName(to);
  925. if(to == nil || !okMbox(to) || cistrcmp(to, "inbox") == 0){
  926. Bprint(&bout, "%s NO %s bad mailbox destination name\r\n", tg, cmd);
  927. return;
  928. }
  929. if(access(to, AEXIST) >= 0){
  930. Bprint(&bout, "%s NO %s mailbox already exists\r\n", tg, cmd);
  931. return;
  932. }
  933. from = mboxName(from);
  934. if(from == nil || !okMbox(from)){
  935. Bprint(&bout, "%s NO %s bad mailbox destination name\r\n", tg, cmd);
  936. return;
  937. }
  938. if(cistrcmp(from, "inbox") == 0)
  939. ok = copyBox(from, to, 0);
  940. else
  941. ok = moveBox(from, to);
  942. if(ok)
  943. Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
  944. else
  945. Bprint(&bout, "%s NO %s failed\r\n", tg, cmd);
  946. }
  947. static void
  948. searchCmd(char *tg, char *cmd)
  949. {
  950. searchUCmd(tg, cmd, 0);
  951. }
  952. static void
  953. searchUCmd(char *tg, char *cmd, int uids)
  954. {
  955. Search rock;
  956. Msg *m;
  957. char *uid;
  958. ulong id;
  959. mustBe(' ');
  960. rock.next = nil;
  961. searchKeys(1, &rock);
  962. crnl();
  963. uid = "";
  964. if(uids)
  965. uid = "uid ";
  966. if(rock.next != nil && rock.next->key == SKCharset){
  967. if(cistrstr(rock.next->s, "utf-8") != 0
  968. && cistrcmp(rock.next->s, "us-ascii") != 0){
  969. Bprint(&bout, "%s NO [BADCHARSET] (\"US-ASCII\" \"UTF-8\") %s%s failed\r\n", tg, uid, cmd);
  970. checkBox(selected, 0);
  971. status(uids, uids);
  972. return;
  973. }
  974. rock.next = rock.next->next;
  975. }
  976. Bprint(&bout, "* search");
  977. for(m = selected->msgs; m != nil; m = m->next)
  978. m->matched = searchMsg(m, rock.next);
  979. for(m = selected->msgs; m != nil; m = m->next){
  980. if(m->matched){
  981. if(uids)
  982. id = m->uid;
  983. else
  984. id = m->seq;
  985. Bprint(&bout, " %lud", id);
  986. }
  987. }
  988. Bprint(&bout, "\r\n");
  989. checkBox(selected, 0);
  990. status(uids, uids);
  991. Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
  992. }
  993. static void
  994. selectCmd(char *tg, char *cmd)
  995. {
  996. Msg *m;
  997. char *s, *mbox;
  998. mustBe(' ');
  999. mbox = astring();
  1000. crnl();
  1001. if(selected){
  1002. imapState = SAuthed;
  1003. closeBox(selected, 1);
  1004. selected = nil;
  1005. }
  1006. mbox = mboxName(mbox);
  1007. if(mbox == nil || !okMbox(mbox)){
  1008. Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
  1009. return;
  1010. }
  1011. selected = openBox(mbox, "imap", cistrcmp(cmd, "select") == 0);
  1012. if(selected == nil){
  1013. Bprint(&bout, "%s NO %s can't open mailbox %s: %r\r\n", tg, cmd, mbox);
  1014. return;
  1015. }
  1016. imapState = SSelected;
  1017. Bprint(&bout, "* FLAGS (\\Seen \\Answered \\Flagged \\Deleted \\Draft)\r\n");
  1018. Bprint(&bout, "* %lud exists\r\n", selected->max);
  1019. selected->toldMax = selected->max;
  1020. Bprint(&bout, "* %lud recent\r\n", selected->recent);
  1021. selected->toldRecent = selected->recent;
  1022. for(m = selected->msgs; m != nil; m = m->next){
  1023. if(!m->expunged && (m->flags & MSeen) != MSeen){
  1024. Bprint(&bout, "* OK [UNSEEN %ld]\r\n", m->seq);
  1025. break;
  1026. }
  1027. }
  1028. Bprint(&bout, "* OK [PERMANENTFLAGS (\\Seen \\Answered \\Flagged \\Draft \\Deleted)]\r\n");
  1029. Bprint(&bout, "* OK [UIDNEXT %ld]\r\n", selected->uidnext);
  1030. Bprint(&bout, "* OK [UIDVALIDITY %ld]\r\n", selected->uidvalidity);
  1031. s = "READ-ONLY";
  1032. if(selected->writable)
  1033. s = "READ-WRITE";
  1034. Bprint(&bout, "%s OK [%s] %s %s completed\r\n", tg, s, cmd, mbox);
  1035. }
  1036. static NamedInt statusItems[] =
  1037. {
  1038. {"MESSAGES", SMessages},
  1039. {"RECENT", SRecent},
  1040. {"UIDNEXT", SUidNext},
  1041. {"UIDVALIDITY", SUidValidity},
  1042. {"UNSEEN", SUnseen},
  1043. {nil, 0}
  1044. };
  1045. static void
  1046. statusCmd(char *tg, char *cmd)
  1047. {
  1048. Box *box;
  1049. Msg *m;
  1050. char *s, *mbox;
  1051. ulong v;
  1052. int si, i;
  1053. mustBe(' ');
  1054. mbox = astring();
  1055. mustBe(' ');
  1056. mustBe('(');
  1057. si = 0;
  1058. for(;;){
  1059. s = atom();
  1060. i = mapInt(statusItems, s);
  1061. if(i == 0)
  1062. parseErr("illegal status item");
  1063. si |= i;
  1064. if(peekc() == ')')
  1065. break;
  1066. mustBe(' ');
  1067. }
  1068. mustBe(')');
  1069. crnl();
  1070. mbox = mboxName(mbox);
  1071. if(mbox == nil || !okMbox(mbox)){
  1072. check();
  1073. Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
  1074. return;
  1075. }
  1076. box = openBox(mbox, "status", 1);
  1077. if(box == nil){
  1078. check();
  1079. Bprint(&bout, "%s NO [TRYCREATE] %s can't open mailbox %s: %r\r\n", tg, cmd, mbox);
  1080. return;
  1081. }
  1082. Bprint(&bout, "* STATUS (");
  1083. s = "";
  1084. for(i = 0; statusItems[i].name != nil; i++){
  1085. if(si & statusItems[i].v){
  1086. v = 0;
  1087. switch(statusItems[i].v){
  1088. case SMessages:
  1089. v = box->max;
  1090. break;
  1091. case SRecent:
  1092. v = box->recent;
  1093. break;
  1094. case SUidNext:
  1095. v = box->uidnext;
  1096. break;
  1097. case SUidValidity:
  1098. v = box->uidvalidity;
  1099. break;
  1100. case SUnseen:
  1101. v = 0;
  1102. for(m = box->msgs; m != nil; m = m->next)
  1103. if((m->flags & MSeen) != MSeen)
  1104. v++;
  1105. break;
  1106. default:
  1107. Bprint(&bout, ")");
  1108. bye("internal error: status item not implemented");
  1109. break;
  1110. }
  1111. Bprint(&bout, "%s%s %lud", s, statusItems[i].name, v);
  1112. s = " ";
  1113. }
  1114. }
  1115. Bprint(&bout, ")\r\n");
  1116. closeBox(box, 1);
  1117. check();
  1118. Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
  1119. }
  1120. static void
  1121. storeCmd(char *tg, char *cmd)
  1122. {
  1123. storeUCmd(tg, cmd, 0);
  1124. }
  1125. static void
  1126. storeUCmd(char *tg, char *cmd, int uids)
  1127. {
  1128. Store *st;
  1129. MsgSet *ms;
  1130. MbLock *ml;
  1131. char *uid;
  1132. ulong max;
  1133. int ok;
  1134. mustBe(' ');
  1135. ms = msgSet();
  1136. mustBe(' ');
  1137. st = storeWhat();
  1138. crnl();
  1139. uid = "";
  1140. if(uids)
  1141. uid = "uid ";
  1142. max = selected->max;
  1143. ml = checkBox(selected, 1);
  1144. ok = ml != nil && forMsgs(selected, ms, max, uids, storeMsg, st);
  1145. closeImp(selected, ml);
  1146. status(uids, uids);
  1147. if(ok)
  1148. Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
  1149. else
  1150. Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd);
  1151. }
  1152. /*
  1153. * minimal implementation of subscribe
  1154. * all folders are automatically subscribed,
  1155. * and can't be unsubscribed
  1156. */
  1157. static void
  1158. subscribeCmd(char *tg, char *cmd)
  1159. {
  1160. Box *box;
  1161. char *mbox;
  1162. int ok;
  1163. mustBe(' ');
  1164. mbox = astring();
  1165. crnl();
  1166. check();
  1167. mbox = mboxName(mbox);
  1168. ok = 0;
  1169. if(mbox != nil && okMbox(mbox)){
  1170. box = openBox(mbox, "subscribe", 0);
  1171. if(box != nil){
  1172. ok = subscribe(mbox, 's');
  1173. closeBox(box, 1);
  1174. }
  1175. }
  1176. if(!ok)
  1177. Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
  1178. else
  1179. Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
  1180. }
  1181. static void
  1182. uidCmd(char *tg, char *cmd)
  1183. {
  1184. char *sub;
  1185. mustBe(' ');
  1186. sub = atom();
  1187. if(cistrcmp(sub, "copy") == 0)
  1188. copyUCmd(tg, sub, 1);
  1189. else if(cistrcmp(sub, "fetch") == 0)
  1190. fetchUCmd(tg, sub, 1);
  1191. else if(cistrcmp(sub, "search") == 0)
  1192. searchUCmd(tg, sub, 1);
  1193. else if(cistrcmp(sub, "store") == 0)
  1194. storeUCmd(tg, sub, 1);
  1195. else{
  1196. clearcmd();
  1197. Bprint(&bout, "%s BAD %s illegal uid command %s\r\n", tg, cmd, sub);
  1198. }
  1199. }
  1200. static void
  1201. unsubscribeCmd(char *tg, char *cmd)
  1202. {
  1203. char *mbox;
  1204. mustBe(' ');
  1205. mbox = astring();
  1206. crnl();
  1207. check();
  1208. mbox = mboxName(mbox);
  1209. if(mbox == nil || !okMbox(mbox) || !subscribe(mbox, 'u'))
  1210. Bprint(&bout, "%s NO %s can't unsubscribe\r\n", tg, cmd);
  1211. else
  1212. Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
  1213. }
  1214. static void
  1215. badsyn(void)
  1216. {
  1217. parseErr("bad syntax");
  1218. }
  1219. static void
  1220. clearcmd(void)
  1221. {
  1222. int c;
  1223. for(;;){
  1224. c = getc();
  1225. if(c < 0)
  1226. bye("end of input");
  1227. if(c == '\n')
  1228. return;
  1229. }
  1230. }
  1231. static void
  1232. crnl(void)
  1233. {
  1234. int c;
  1235. c = getc();
  1236. if(c == '\n')
  1237. return;
  1238. if(c != '\r' || getc() != '\n')
  1239. badsyn();
  1240. }
  1241. static void
  1242. mustBe(int c)
  1243. {
  1244. if(getc() != c){
  1245. ungetc();
  1246. badsyn();
  1247. }
  1248. }
  1249. /*
  1250. * flaglist : '(' ')' | '(' flags ')'
  1251. */
  1252. static int
  1253. flagList(void)
  1254. {
  1255. int f;
  1256. mustBe('(');
  1257. f = 0;
  1258. if(peekc() != ')')
  1259. f = flags();
  1260. mustBe(')');
  1261. return f;
  1262. }
  1263. /*
  1264. * flags : flag | flags ' ' flag
  1265. * flag : '\' atom | atom
  1266. */
  1267. static int
  1268. flags(void)
  1269. {
  1270. int ff, flags;
  1271. char *s;
  1272. int c;
  1273. flags = 0;
  1274. for(;;){
  1275. c = peekc();
  1276. if(c == '\\'){
  1277. mustBe('\\');
  1278. s = atomString(atomStop, "\\");
  1279. }else if(strchr(atomStop, c) != nil)
  1280. s = atom();
  1281. else
  1282. break;
  1283. ff = mapFlag(s);
  1284. if(ff == 0)
  1285. parseErr("flag not supported");
  1286. flags |= ff;
  1287. if(peekc() != ' ')
  1288. break;
  1289. mustBe(' ');
  1290. }
  1291. if(flags == 0)
  1292. parseErr("no flags given");
  1293. return flags;
  1294. }
  1295. /*
  1296. * storeWhat : osign 'FLAGS' ' ' storeflags
  1297. * | osign 'FLAGS.SILENT' ' ' storeflags
  1298. * osign :
  1299. * | '+' | '-'
  1300. * storeflags : flagList | flags
  1301. */
  1302. static Store*
  1303. storeWhat(void)
  1304. {
  1305. int f;
  1306. char *s;
  1307. int c, w;
  1308. c = peekc();
  1309. if(c == '+' || c == '-')
  1310. mustBe(c);
  1311. else
  1312. c = 0;
  1313. s = atom();
  1314. w = 0;
  1315. if(cistrcmp(s, "flags") == 0)
  1316. w = STFlags;
  1317. else if(cistrcmp(s, "flags.silent") == 0)
  1318. w = STFlagsSilent;
  1319. else
  1320. parseErr("illegal store attribute");
  1321. mustBe(' ');
  1322. if(peekc() == '(')
  1323. f = flagList();
  1324. else
  1325. f = flags();
  1326. return mkStore(c, w, f);
  1327. }
  1328. /*
  1329. * fetchWhat : "ALL" | "FULL" | "FAST" | fetchAtt | '(' fetchAtts ')'
  1330. * fetchAtts : fetchAtt | fetchAtts ' ' fetchAtt
  1331. */
  1332. static char *fetchAtom = "(){}%*\"\\[]";
  1333. static Fetch*
  1334. fetchWhat(void)
  1335. {
  1336. Fetch *f;
  1337. char *s;
  1338. if(peekc() == '('){
  1339. getc();
  1340. f = nil;
  1341. for(;;){
  1342. s = atomString(fetchAtom, "");
  1343. f = fetchAtt(s, f);
  1344. if(peekc() == ')')
  1345. break;
  1346. mustBe(' ');
  1347. }
  1348. getc();
  1349. return revFetch(f);
  1350. }
  1351. s = atomString(fetchAtom, "");
  1352. if(cistrcmp(s, "all") == 0)
  1353. f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, mkFetch(FEnvelope, nil))));
  1354. else if(cistrcmp(s, "fast") == 0)
  1355. f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, nil)));
  1356. else if(cistrcmp(s, "full") == 0)
  1357. f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, mkFetch(FEnvelope, mkFetch(FBody, nil)))));
  1358. else
  1359. f = fetchAtt(s, nil);
  1360. return f;
  1361. }
  1362. /*
  1363. * fetchAtt : "ENVELOPE" | "FLAGS" | "INTERNALDATE"
  1364. * | "RFC822" | "RFC822.HEADER" | "RFC822.SIZE" | "RFC822.TEXT"
  1365. * | "BODYSTRUCTURE"
  1366. * | "UID"
  1367. * | "BODY"
  1368. * | "BODY" bodysubs
  1369. * | "BODY.PEEK" bodysubs
  1370. * bodysubs : sect
  1371. * | sect '<' number '.' nz-number '>'
  1372. * sect : '[' sectSpec ']'
  1373. * sectSpec : sectMsgText
  1374. * | sectPart
  1375. * | sectPart '.' sectText
  1376. * sectPart : nz-number
  1377. * | sectPart '.' nz-number
  1378. */
  1379. static Fetch*
  1380. fetchAtt(char *s, Fetch *f)
  1381. {
  1382. NList *sect;
  1383. int c;
  1384. if(cistrcmp(s, "envelope") == 0)
  1385. return mkFetch(FEnvelope, f);
  1386. if(cistrcmp(s, "flags") == 0)
  1387. return mkFetch(FFlags, f);
  1388. if(cistrcmp(s, "internaldate") == 0)
  1389. return mkFetch(FInternalDate, f);
  1390. if(cistrcmp(s, "RFC822") == 0)
  1391. return mkFetch(FRfc822, f);
  1392. if(cistrcmp(s, "RFC822.header") == 0)
  1393. return mkFetch(FRfc822Head, f);
  1394. if(cistrcmp(s, "RFC822.size") == 0)
  1395. return mkFetch(FRfc822Size, f);
  1396. if(cistrcmp(s, "RFC822.text") == 0)
  1397. return mkFetch(FRfc822Text, f);
  1398. if(cistrcmp(s, "bodystructure") == 0)
  1399. return mkFetch(FBodyStruct, f);
  1400. if(cistrcmp(s, "uid") == 0)
  1401. return mkFetch(FUid, f);
  1402. if(cistrcmp(s, "body") == 0){
  1403. if(peekc() != '[')
  1404. return mkFetch(FBody, f);
  1405. f = mkFetch(FBodySect, f);
  1406. }else if(cistrcmp(s, "body.peek") == 0)
  1407. f = mkFetch(FBodyPeek, f);
  1408. else
  1409. parseErr("illegal fetch attribute");
  1410. mustBe('[');
  1411. c = peekc();
  1412. if(c >= '1' && c <= '9'){
  1413. sect = mkNList(number(1), nil);
  1414. while(peekc() == '.'){
  1415. getc();
  1416. c = peekc();
  1417. if(c >= '1' && c <= '9'){
  1418. sect = mkNList(number(1), sect);
  1419. }else{
  1420. break;
  1421. }
  1422. }
  1423. f->sect = revNList(sect);
  1424. }
  1425. if(peekc() != ']')
  1426. sectText(f, f->sect != nil);
  1427. mustBe(']');
  1428. if(peekc() != '<')
  1429. return f;
  1430. f->partial = 1;
  1431. mustBe('<');
  1432. f->start = number(0);
  1433. mustBe('.');
  1434. f->size = number(1);
  1435. mustBe('>');
  1436. return f;
  1437. }
  1438. /*
  1439. * sectText : sectMsgText | "MIME"
  1440. * sectMsgText : "HEADER"
  1441. * | "TEXT"
  1442. * | "HEADER.FIELDS" ' ' hdrList
  1443. * | "HEADER.FIELDS.NOT" ' ' hdrList
  1444. * hdrList : '(' hdrs ')'
  1445. * hdrs: : astring
  1446. * | hdrs ' ' astring
  1447. */
  1448. static void
  1449. sectText(Fetch *f, int mimeOk)
  1450. {
  1451. SList *h;
  1452. char *s;
  1453. s = atomString(fetchAtom, "");
  1454. if(cistrcmp(s, "header") == 0){
  1455. f->part = FPHead;
  1456. return;
  1457. }
  1458. if(cistrcmp(s, "text") == 0){
  1459. f->part = FPText;
  1460. return;
  1461. }
  1462. if(mimeOk && cistrcmp(s, "mime") == 0){
  1463. f->part = FPMime;
  1464. return;
  1465. }
  1466. if(cistrcmp(s, "header.fields") == 0)
  1467. f->part = FPHeadFields;
  1468. else if(cistrcmp(s, "header.fields.not") == 0)
  1469. f->part = FPHeadFieldsNot;
  1470. else
  1471. parseErr("illegal fetch section text");
  1472. mustBe(' ');
  1473. mustBe('(');
  1474. h = nil;
  1475. for(;;){
  1476. h = mkSList(astring(), h);
  1477. if(peekc() == ')')
  1478. break;
  1479. mustBe(' ');
  1480. }
  1481. mustBe(')');
  1482. f->hdrs = revSList(h);
  1483. }
  1484. /*
  1485. * searchWhat : "CHARSET" ' ' astring searchkeys | searchkeys
  1486. * searchkeys : searchkey | searchkeys ' ' searchkey
  1487. * searchkey : "ALL" | "ANSWERED" | "DELETED" | "FLAGGED" | "NEW" | "OLD" | "RECENT"
  1488. * | "SEEN" | "UNANSWERED" | "UNDELETED" | "UNFLAGGED" | "DRAFT" | "UNDRAFT"
  1489. * | astrkey ' ' astring
  1490. * | datekey ' ' date
  1491. * | "KEYWORD" ' ' flag | "UNKEYWORD" flag
  1492. * | "LARGER" ' ' number | "SMALLER" ' ' number
  1493. * | "HEADER" astring ' ' astring
  1494. * | set | "UID" ' ' set
  1495. * | "NOT" ' ' searchkey
  1496. * | "OR" ' ' searchkey ' ' searchkey
  1497. * | '(' searchkeys ')'
  1498. * astrkey : "BCC" | "BODY" | "CC" | "FROM" | "SUBJECT" | "TEXT" | "TO"
  1499. * datekey : "BEFORE" | "ON" | "SINCE" | "SENTBEFORE" | "SENTON" | "SENTSINCE"
  1500. */
  1501. static NamedInt searchMap[] =
  1502. {
  1503. {"ALL", SKAll},
  1504. {"ANSWERED", SKAnswered},
  1505. {"DELETED", SKDeleted},
  1506. {"FLAGGED", SKFlagged},
  1507. {"NEW", SKNew},
  1508. {"OLD", SKOld},
  1509. {"RECENT", SKRecent},
  1510. {"SEEN", SKSeen},
  1511. {"UNANSWERED", SKUnanswered},
  1512. {"UNDELETED", SKUndeleted},
  1513. {"UNFLAGGED", SKUnflagged},
  1514. {"DRAFT", SKDraft},
  1515. {"UNDRAFT", SKUndraft},
  1516. {"UNSEEN", SKUnseen},
  1517. {nil, 0}
  1518. };
  1519. static NamedInt searchMapStr[] =
  1520. {
  1521. {"CHARSET", SKCharset},
  1522. {"BCC", SKBcc},
  1523. {"BODY", SKBody},
  1524. {"CC", SKCc},
  1525. {"FROM", SKFrom},
  1526. {"SUBJECT", SKSubject},
  1527. {"TEXT", SKText},
  1528. {"TO", SKTo},
  1529. {nil, 0}
  1530. };
  1531. static NamedInt searchMapDate[] =
  1532. {
  1533. {"BEFORE", SKBefore},
  1534. {"ON", SKOn},
  1535. {"SINCE", SKSince},
  1536. {"SENTBEFORE", SKSentBefore},
  1537. {"SENTON", SKSentOn},
  1538. {"SENTSINCE", SKSentSince},
  1539. {nil, 0}
  1540. };
  1541. static NamedInt searchMapFlag[] =
  1542. {
  1543. {"KEYWORD", SKKeyword},
  1544. {"UNKEYWORD", SKUnkeyword},
  1545. {nil, 0}
  1546. };
  1547. static NamedInt searchMapNum[] =
  1548. {
  1549. {"SMALLER", SKSmaller},
  1550. {"LARGER", SKLarger},
  1551. {nil, 0}
  1552. };
  1553. static Search*
  1554. searchKeys(int first, Search *tail)
  1555. {
  1556. Search *s;
  1557. for(;;){
  1558. if(peekc() == '('){
  1559. getc();
  1560. tail = searchKeys(0, tail);
  1561. mustBe(')');
  1562. }else{
  1563. s = searchKey(first);
  1564. tail->next = s;
  1565. tail = s;
  1566. }
  1567. first = 0;
  1568. if(peekc() != ' ')
  1569. break;
  1570. getc();
  1571. }
  1572. return tail;
  1573. }
  1574. static Search*
  1575. searchKey(int first)
  1576. {
  1577. Search *sr, rock;
  1578. Tm tm;
  1579. char *a;
  1580. int i, c;
  1581. sr = binalloc(&parseBin, sizeof(Search), 1);
  1582. if(sr == nil)
  1583. parseErr("out of memory");
  1584. c = peekc();
  1585. if(c >= '0' && c <= '9'){
  1586. sr->key = SKSet;
  1587. sr->set = msgSet();
  1588. return sr;
  1589. }
  1590. a = atom();
  1591. if(i = mapInt(searchMap, a))
  1592. sr->key = i;
  1593. else if(i = mapInt(searchMapStr, a)){
  1594. if(!first && i == SKCharset)
  1595. parseErr("illegal search key");
  1596. sr->key = i;
  1597. mustBe(' ');
  1598. sr->s = astring();
  1599. }else if(i = mapInt(searchMapDate, a)){
  1600. sr->key = i;
  1601. mustBe(' ');
  1602. c = peekc();
  1603. if(c == '"')
  1604. getc();
  1605. a = atom();
  1606. if(!imap4Date(&tm, a))
  1607. parseErr("bad date format");
  1608. sr->year = tm.year;
  1609. sr->mon = tm.mon;
  1610. sr->mday = tm.mday;
  1611. if(c == '"')
  1612. mustBe('"');
  1613. }else if(i = mapInt(searchMapFlag, a)){
  1614. sr->key = i;
  1615. mustBe(' ');
  1616. c = peekc();
  1617. if(c == '\\'){
  1618. mustBe('\\');
  1619. a = atomString(atomStop, "\\");
  1620. }else
  1621. a = atom();
  1622. i = mapFlag(a);
  1623. if(i == 0)
  1624. parseErr("flag not supported");
  1625. sr->num = i;
  1626. }else if(i = mapInt(searchMapNum, a)){
  1627. sr->key = i;
  1628. mustBe(' ');
  1629. sr->num = number(0);
  1630. }else if(cistrcmp(a, "HEADER") == 0){
  1631. sr->key = SKHeader;
  1632. mustBe(' ');
  1633. sr->hdr = astring();
  1634. mustBe(' ');
  1635. sr->s = astring();
  1636. }else if(cistrcmp(a, "UID") == 0){
  1637. sr->key = SKUid;
  1638. mustBe(' ');
  1639. sr->set = msgSet();
  1640. }else if(cistrcmp(a, "NOT") == 0){
  1641. sr->key = SKNot;
  1642. mustBe(' ');
  1643. rock.next = nil;
  1644. searchKeys(0, &rock);
  1645. sr->left = rock.next;
  1646. }else if(cistrcmp(a, "OR") == 0){
  1647. sr->key = SKOr;
  1648. mustBe(' ');
  1649. rock.next = nil;
  1650. searchKeys(0, &rock);
  1651. sr->left = rock.next;
  1652. mustBe(' ');
  1653. rock.next = nil;
  1654. searchKeys(0, &rock);
  1655. sr->right = rock.next;
  1656. }else
  1657. parseErr("illegal search key");
  1658. return sr;
  1659. }
  1660. /*
  1661. * set : seqno
  1662. * | seqno ':' seqno
  1663. * | set ',' set
  1664. * seqno: nz-number
  1665. * | '*'
  1666. *
  1667. */
  1668. static MsgSet*
  1669. msgSet(void)
  1670. {
  1671. MsgSet head, *last, *ms;
  1672. ulong from, to;
  1673. last = &head;
  1674. head.next = nil;
  1675. for(;;){
  1676. from = seqNo();
  1677. to = from;
  1678. if(peekc() == ':'){
  1679. getc();
  1680. to = seqNo();
  1681. }
  1682. ms = binalloc(&parseBin, sizeof(MsgSet), 0);
  1683. if(ms == nil)
  1684. parseErr("out of memory");
  1685. ms->from = from;
  1686. ms->to = to;
  1687. ms->next = nil;
  1688. last->next = ms;
  1689. last = ms;
  1690. if(peekc() != ',')
  1691. break;
  1692. getc();
  1693. }
  1694. return head.next;
  1695. }
  1696. static ulong
  1697. seqNo(void)
  1698. {
  1699. if(peekc() == '*'){
  1700. getc();
  1701. return ~0UL;
  1702. }
  1703. return number(1);
  1704. }
  1705. /*
  1706. * 7 bit, non-ctl chars, no (){%*"\
  1707. * NIL is special case for nstring or parenlist
  1708. */
  1709. static char *
  1710. atom(void)
  1711. {
  1712. return atomString(atomStop, "");
  1713. }
  1714. /*
  1715. * like an atom, but no +
  1716. */
  1717. static char *
  1718. tag(void)
  1719. {
  1720. return atomString("+(){%*\"\\", "");
  1721. }
  1722. /*
  1723. * string or atom allowing %*
  1724. */
  1725. static char *
  1726. listmbox(void)
  1727. {
  1728. int c;
  1729. c = peekc();
  1730. if(c == '{')
  1731. return literal();
  1732. if(c == '"')
  1733. return quoted();
  1734. return atomString("(){\"\\", "");
  1735. }
  1736. /*
  1737. * string or atom
  1738. */
  1739. static char *
  1740. astring(void)
  1741. {
  1742. int c;
  1743. c = peekc();
  1744. if(c == '{')
  1745. return literal();
  1746. if(c == '"')
  1747. return quoted();
  1748. return atom();
  1749. }
  1750. /*
  1751. * 7 bit, non-ctl chars, none from exception list
  1752. */
  1753. static char *
  1754. atomString(char *disallowed, char *initial)
  1755. {
  1756. char *s;
  1757. int c, ns, as;
  1758. ns = strlen(initial);
  1759. s = binalloc(&parseBin, ns + StrAlloc, 0);
  1760. if(s == nil)
  1761. parseErr("out of memory");
  1762. strcpy(s, initial);
  1763. as = ns + StrAlloc;
  1764. for(;;){
  1765. c = getc();
  1766. if(c <= ' ' || c >= 0x7f || strchr(disallowed, c) != nil){
  1767. ungetc();
  1768. break;
  1769. }
  1770. s[ns++] = c;
  1771. if(ns >= as){
  1772. s = bingrow(&parseBin, s, as, as + StrAlloc, 0);
  1773. if(s == nil)
  1774. parseErr("out of memory");
  1775. as += StrAlloc;
  1776. }
  1777. }
  1778. if(ns == 0)
  1779. badsyn();
  1780. s[ns] = '\0';
  1781. return s;
  1782. }
  1783. /*
  1784. * quoted: '"' chars* '"'
  1785. * chars: 1-128 except \r and \n
  1786. */
  1787. static char *
  1788. quoted(void)
  1789. {
  1790. char *s;
  1791. int c, ns, as;
  1792. mustBe('"');
  1793. s = binalloc(&parseBin, StrAlloc, 0);
  1794. if(s == nil)
  1795. parseErr("out of memory");
  1796. as = StrAlloc;
  1797. ns = 0;
  1798. for(;;){
  1799. c = getc();
  1800. if(c == '"')
  1801. break;
  1802. if(c < 1 || c > 0x7f || c == '\r' || c == '\n')
  1803. badsyn();
  1804. if(c == '\\'){
  1805. c = getc();
  1806. if(c != '\\' && c != '"')
  1807. badsyn();
  1808. }
  1809. s[ns++] = c;
  1810. if(ns >= as){
  1811. s = bingrow(&parseBin, s, as, as + StrAlloc, 0);
  1812. if(s == nil)
  1813. parseErr("out of memory");
  1814. as += StrAlloc;
  1815. }
  1816. }
  1817. s[ns] = '\0';
  1818. return s;
  1819. }
  1820. /*
  1821. * litlen: {number}\r\n
  1822. */
  1823. static ulong
  1824. litlen(void)
  1825. {
  1826. ulong v;
  1827. mustBe('{');
  1828. v = number(0);
  1829. mustBe('}');
  1830. crnl();
  1831. return v;
  1832. }
  1833. /*
  1834. * literal: litlen data<0:litlen>
  1835. */
  1836. static char *
  1837. literal(void)
  1838. {
  1839. char *s;
  1840. ulong v;
  1841. v = litlen();
  1842. s = binalloc(&parseBin, v+1, 0);
  1843. if(s == nil)
  1844. parseErr("out of memory");
  1845. Bprint(&bout, "+ Ready for literal data\r\n");
  1846. if(Bflush(&bout) < 0)
  1847. writeErr();
  1848. if(v != 0 && Bread(&bin, s, v) != v)
  1849. badsyn();
  1850. s[v] = '\0';
  1851. return s;
  1852. }
  1853. /*
  1854. * digits; number is 32 bits
  1855. */
  1856. static ulong
  1857. number(int nonzero)
  1858. {
  1859. ulong v;
  1860. int c, first;
  1861. v = 0;
  1862. first = 1;
  1863. for(;;){
  1864. c = getc();
  1865. if(c < '0' || c > '9'){
  1866. ungetc();
  1867. if(first)
  1868. badsyn();
  1869. break;
  1870. }
  1871. if(nonzero && first && c == '0')
  1872. badsyn();
  1873. c -= '0';
  1874. first = 0;
  1875. if(v > UlongMax/10 || v == UlongMax/10 && c > UlongMax%10)
  1876. parseErr("number out of range\r\n");
  1877. v = v * 10 + c;
  1878. }
  1879. return v;
  1880. }
  1881. static int
  1882. getc(void)
  1883. {
  1884. return Bgetc(&bin);
  1885. }
  1886. static void
  1887. ungetc(void)
  1888. {
  1889. Bungetc(&bin);
  1890. }
  1891. static int
  1892. peekc(void)
  1893. {
  1894. int c;
  1895. c = Bgetc(&bin);
  1896. Bungetc(&bin);
  1897. return c;
  1898. }