imap4d.c 40 KB

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