imap4d.c 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098
  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(!cdExists(mboxDir, mbox)){
  549. check();
  550. Bprint(&bout, "%s NO [TRYCREATE] %s mailbox does not exist\r\n", tg, cmd);
  551. return;
  552. }
  553. max = selected->max;
  554. checkBox(selected, 0);
  555. ok = forMsgs(selected, ms, max, uids, copyCheck, nil);
  556. if(ok)
  557. ok = forMsgs(selected, ms, max, uids, copySave, mbox);
  558. status(1, uids);
  559. if(ok)
  560. Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
  561. else
  562. Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd);
  563. }
  564. static void
  565. createCmd(char *tg, char *cmd)
  566. {
  567. char *mbox, *m;
  568. int fd, slash;
  569. mustBe(' ');
  570. mbox = astring();
  571. crnl();
  572. check();
  573. m = strchr(mbox, '\0');
  574. slash = m != mbox && m[-1] == '/';
  575. mbox = mboxName(mbox);
  576. if(mbox == nil || !okMbox(mbox)){
  577. Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
  578. return;
  579. }
  580. if(cistrcmp(mbox, "inbox") == 0){
  581. Bprint(&bout, "%s NO %s cannot remotely create INBOX\r\n", tg, cmd);
  582. return;
  583. }
  584. if(access(mbox, AEXIST) >= 0){
  585. Bprint(&bout, "%s NO %s mailbox already exists\r\n", tg, cmd);
  586. return;
  587. }
  588. fd = createBox(mbox, slash);
  589. close(fd);
  590. if(fd < 0)
  591. Bprint(&bout, "%s NO %s cannot create mailbox %s\r\n", tg, cmd, mbox);
  592. else
  593. Bprint(&bout, "%s OK %s %s completed\r\n", tg, mbox, cmd);
  594. }
  595. static void
  596. deleteCmd(char *tg, char *cmd)
  597. {
  598. char *mbox, *imp;
  599. mustBe(' ');
  600. mbox = astring();
  601. crnl();
  602. check();
  603. mbox = mboxName(mbox);
  604. if(mbox == nil || !okMbox(mbox)){
  605. Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
  606. return;
  607. }
  608. imp = impName(mbox);
  609. if(cistrcmp(mbox, "inbox") == 0
  610. || imp != nil && cdRemove(mboxDir, imp) < 0 && cdExists(mboxDir, imp)
  611. || cdRemove(mboxDir, mbox) < 0)
  612. Bprint(&bout, "%s NO %s cannot delete mailbox %s\r\n", tg, cmd, mbox);
  613. else
  614. Bprint(&bout, "%s OK %s %s completed\r\n", tg, mbox, cmd);
  615. }
  616. static void
  617. expungeCmd(char *tg, char *cmd)
  618. {
  619. int ok;
  620. crnl();
  621. ok = deleteMsgs(selected);
  622. check();
  623. if(ok)
  624. Bprint(&bout, "%s OK %s messages erased\r\n", tg, cmd);
  625. else
  626. Bprint(&bout, "%s NO %s some messages not expunged\r\n", tg, cmd);
  627. }
  628. static void
  629. fetchCmd(char *tg, char *cmd)
  630. {
  631. fetchUCmd(tg, cmd, 0);
  632. }
  633. static void
  634. fetchUCmd(char *tg, char *cmd, int uids)
  635. {
  636. Fetch *f;
  637. MsgSet *ms;
  638. MbLock *ml;
  639. char *uid;
  640. ulong max;
  641. int ok;
  642. mustBe(' ');
  643. ms = msgSet(uids);
  644. mustBe(' ');
  645. f = fetchWhat();
  646. crnl();
  647. uid = "";
  648. if(uids)
  649. uid = "uid ";
  650. max = selected->max;
  651. ml = checkBox(selected, 1);
  652. if(ml != nil)
  653. forMsgs(selected, ms, max, uids, fetchSeen, f);
  654. closeImp(selected, ml);
  655. ok = ml != nil && forMsgs(selected, ms, max, uids, fetchMsg, f);
  656. status(uids, uids);
  657. if(ok)
  658. Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
  659. else
  660. Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd);
  661. }
  662. static void
  663. idleCmd(char *tg, char *cmd)
  664. {
  665. int c, pid;
  666. crnl();
  667. Bprint(&bout, "+ idling, waiting for done\r\n");
  668. if(Bflush(&bout) < 0)
  669. writeErr();
  670. if(idlepid < 0){
  671. pid = rfork(RFPROC|RFMEM|RFNOWAIT);
  672. if(pid == 0){
  673. for(;;){
  674. qlock(&imaplock);
  675. if(exiting)
  676. break;
  677. /*
  678. * parent may have changed curDir, but it doesn't change our .
  679. */
  680. resetCurDir();
  681. check();
  682. if(Bflush(&bout) < 0)
  683. writeErr();
  684. qunlock(&imaplock);
  685. sleep(15*1000);
  686. enableForwarding();
  687. }
  688. _exits("rob3");
  689. _exits(0);
  690. }
  691. idlepid = pid;
  692. }
  693. qunlock(&imaplock);
  694. /*
  695. * clear out the next line, which is supposed to contain (case-insensitive)
  696. * done\n
  697. * this is special code since it has to dance with the idle polling proc
  698. * and handle exiting correctly.
  699. */
  700. for(;;){
  701. c = getc();
  702. if(c < 0){
  703. qlock(&imaplock);
  704. if(!exiting)
  705. cleaner();
  706. _exits("rob4");
  707. _exits(0);
  708. }
  709. if(c == '\n')
  710. break;
  711. }
  712. qlock(&imaplock);
  713. if(exiting)
  714. {_exits("rob5");
  715. _exits(0);
  716. }
  717. /*
  718. * child may have changed curDir, but it doesn't change our .
  719. */
  720. resetCurDir();
  721. check();
  722. Bprint(&bout, "%s OK %s terminated\r\n", tg, cmd);
  723. }
  724. static void
  725. listCmd(char *tg, char *cmd)
  726. {
  727. char *s, *t, *ss, *ref, *mbox;
  728. int n;
  729. mustBe(' ');
  730. s = astring();
  731. mustBe(' ');
  732. t = listmbox();
  733. crnl();
  734. check();
  735. ref = mutf7str(s);
  736. mbox = mutf7str(t);
  737. if(ref == nil || mbox == nil){
  738. Bprint(&bout, "%s BAD %s mailbox name not in modified utf-7\r\n", tg, cmd);
  739. return;
  740. }
  741. /*
  742. * special request for hierarchy delimiter and root name
  743. * root name appears to be name up to and including any delimiter,
  744. * or the empty string, if there is no delimiter.
  745. *
  746. * this must change if the # namespace convention is supported.
  747. */
  748. if(*mbox == '\0'){
  749. s = strchr(ref, '/');
  750. if(s == nil)
  751. ref = "";
  752. else
  753. s[1] = '\0';
  754. Bprint(&bout, "* %s (\\Noselect) \"/\" \"%s\"\r\n", cmd, ref);
  755. Bprint(&bout, "%s OK %s\r\n", tg, cmd);
  756. return;
  757. }
  758. /*
  759. * massage the listing name:
  760. * clean up the components individually,
  761. * then rip off componenets from the ref to
  762. * take care of leading ..'s in the mbox.
  763. *
  764. * the cleanup can wipe out * followed by a ..
  765. * tough luck if such a stupid pattern is given.
  766. */
  767. cleanname(mbox);
  768. if(strcmp(mbox, ".") == 0)
  769. *mbox = '\0';
  770. if(mbox[0] == '/')
  771. *ref = '\0';
  772. else if(*ref != '\0'){
  773. cleanname(ref);
  774. if(strcmp(ref, ".") == 0)
  775. *ref = '\0';
  776. }else
  777. *ref = '\0';
  778. while(*ref && isdotdot(mbox)){
  779. s = strrchr(ref, '/');
  780. if(s == nil)
  781. s = ref;
  782. if(isdotdot(s))
  783. break;
  784. *s = '\0';
  785. mbox += 2;
  786. if(*mbox == '/')
  787. mbox++;
  788. }
  789. if(*ref == '\0'){
  790. s = mbox;
  791. ss = s;
  792. }else{
  793. n = strlen(ref) + strlen(mbox) + 2;
  794. t = binalloc(&parseBin, n, 0);
  795. if(t == nil)
  796. parseErr("out of memory");
  797. snprint(t, n, "%s/%s", ref, mbox);
  798. s = t;
  799. ss = s + strlen(ref);
  800. }
  801. /*
  802. * only allow activity in /mail/box
  803. */
  804. if(s[0] == '/' || isdotdot(s)){
  805. Bprint(&bout, "%s NO illegal mailbox pattern\r\n", tg);
  806. return;
  807. }
  808. if(cistrcmp(cmd, "lsub") == 0)
  809. lsubBoxes(cmd, s, ss);
  810. else
  811. listBoxes(cmd, s, ss);
  812. Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
  813. }
  814. static char*
  815. passCR(char*u, char*p)
  816. {
  817. static char Ebadch[] = "can't get challenge";
  818. static char nchall[64];
  819. static char response[64];
  820. static Chalstate *ch = nil;
  821. AuthInfo *ai;
  822. again:
  823. if (ch == nil){
  824. if(!(ch = auth_challenge("proto=p9cr role=server user=%q", u)))
  825. return Ebadch;
  826. snprint(nchall, 64, " encrypt challenge: %s", ch->chal);
  827. return nchall;
  828. } else {
  829. strncpy(response, p, 64);
  830. ch->resp = response;
  831. ch->nresp = strlen(response);
  832. ai = auth_response(ch);
  833. auth_freechal(ch);
  834. ch = nil;
  835. if (ai == nil)
  836. goto again;
  837. setupuser(ai);
  838. return nil;
  839. }
  840. }
  841. static void
  842. loginCmd(char *tg, char *cmd)
  843. {
  844. char *s, *t;
  845. AuthInfo *ai;
  846. char*r;
  847. mustBe(' ');
  848. s = astring(); /* uid */
  849. mustBe(' ');
  850. t = astring(); /* password */
  851. crnl();
  852. if(allowCR){
  853. if ((r = passCR(s, t)) == nil){
  854. Bprint(&bout, "%s OK %s succeeded\r\n", tg, cmd);
  855. imapState = SAuthed;
  856. } else {
  857. Bprint(&bout, "* NO [ALERT] %s\r\n", r);
  858. Bprint(&bout, "%s NO %s succeeded\r\n", tg, cmd);
  859. }
  860. return;
  861. }
  862. else if(allowPass){
  863. if(ai = passLogin(s, t)){
  864. setupuser(ai);
  865. Bprint(&bout, "%s OK %s succeeded\r\n", tg, cmd);
  866. imapState = SAuthed;
  867. }else
  868. Bprint(&bout, "%s NO %s failed check\r\n", tg, cmd);
  869. return;
  870. }
  871. Bprint(&bout, "%s NO %s plaintext passwords disallowed\r\n", tg, cmd);
  872. }
  873. /*
  874. * logout or x-exit, which doesn't expunge the mailbox
  875. */
  876. static void
  877. logoutCmd(char *tg, char *cmd)
  878. {
  879. crnl();
  880. if(cmd[0] != 'x' && selected){
  881. closeBox(selected, 1);
  882. selected = nil;
  883. }
  884. Bprint(&bout, "* bye\r\n");
  885. Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
  886. exits("rob6");
  887. exits(0);
  888. }
  889. static void
  890. namespaceCmd(char *tg, char *cmd)
  891. {
  892. crnl();
  893. check();
  894. /*
  895. * personal, other users, shared namespaces
  896. * send back nil or descriptions of (prefix heirarchy-delim) for each case
  897. */
  898. Bprint(&bout, "* NAMESPACE ((\"\" \"/\")) nil nil\r\n");
  899. Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
  900. }
  901. static void
  902. noopCmd(char *tg, char *cmd)
  903. {
  904. crnl();
  905. check();
  906. Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
  907. enableForwarding();
  908. }
  909. /*
  910. * this is only a partial implementation
  911. * should copy files to other directories,
  912. * and copy & truncate inbox
  913. */
  914. static void
  915. renameCmd(char *tg, char *cmd)
  916. {
  917. char *from, *to;
  918. int ok;
  919. mustBe(' ');
  920. from = astring();
  921. mustBe(' ');
  922. to = astring();
  923. crnl();
  924. check();
  925. to = mboxName(to);
  926. if(to == nil || !okMbox(to) || cistrcmp(to, "inbox") == 0){
  927. Bprint(&bout, "%s NO %s bad mailbox destination name\r\n", tg, cmd);
  928. return;
  929. }
  930. if(access(to, AEXIST) >= 0){
  931. Bprint(&bout, "%s NO %s mailbox already exists\r\n", tg, cmd);
  932. return;
  933. }
  934. from = mboxName(from);
  935. if(from == nil || !okMbox(from)){
  936. Bprint(&bout, "%s NO %s bad mailbox destination name\r\n", tg, cmd);
  937. return;
  938. }
  939. if(cistrcmp(from, "inbox") == 0)
  940. ok = copyBox(from, to, 0);
  941. else
  942. ok = moveBox(from, to);
  943. if(ok)
  944. Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
  945. else
  946. Bprint(&bout, "%s NO %s failed\r\n", tg, cmd);
  947. }
  948. static void
  949. searchCmd(char *tg, char *cmd)
  950. {
  951. searchUCmd(tg, cmd, 0);
  952. }
  953. static void
  954. searchUCmd(char *tg, char *cmd, int uids)
  955. {
  956. Search rock;
  957. Msg *m;
  958. char *uid;
  959. ulong id;
  960. mustBe(' ');
  961. rock.next = nil;
  962. searchKeys(1, &rock);
  963. crnl();
  964. uid = "";
  965. if(uids)
  966. uid = "uid ";
  967. if(rock.next != nil && rock.next->key == SKCharset){
  968. if(cistrstr(rock.next->s, "utf-8") != 0
  969. && cistrcmp(rock.next->s, "us-ascii") != 0){
  970. Bprint(&bout, "%s NO [BADCHARSET] (\"US-ASCII\" \"UTF-8\") %s%s failed\r\n", tg, uid, cmd);
  971. checkBox(selected, 0);
  972. status(uids, uids);
  973. return;
  974. }
  975. rock.next = rock.next->next;
  976. }
  977. Bprint(&bout, "* search");
  978. for(m = selected->msgs; m != nil; m = m->next)
  979. m->matched = searchMsg(m, rock.next);
  980. for(m = selected->msgs; m != nil; m = m->next){
  981. if(m->matched){
  982. if(uids)
  983. id = m->uid;
  984. else
  985. id = m->seq;
  986. Bprint(&bout, " %lud", id);
  987. }
  988. }
  989. Bprint(&bout, "\r\n");
  990. checkBox(selected, 0);
  991. status(uids, uids);
  992. Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
  993. }
  994. static void
  995. selectCmd(char *tg, char *cmd)
  996. {
  997. Msg *m;
  998. char *s, *mbox;
  999. mustBe(' ');
  1000. mbox = astring();
  1001. crnl();
  1002. if(selected){
  1003. imapState = SAuthed;
  1004. closeBox(selected, 1);
  1005. selected = nil;
  1006. }
  1007. mbox = mboxName(mbox);
  1008. if(mbox == nil || !okMbox(mbox)){
  1009. Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
  1010. return;
  1011. }
  1012. selected = openBox(mbox, "imap", cistrcmp(cmd, "select") == 0);
  1013. if(selected == nil){
  1014. Bprint(&bout, "%s NO %s can't open mailbox %s: %r\r\n", tg, cmd, mbox);
  1015. return;
  1016. }
  1017. imapState = SSelected;
  1018. Bprint(&bout, "* FLAGS (\\Seen \\Answered \\Flagged \\Deleted \\Draft)\r\n");
  1019. Bprint(&bout, "* %lud EXISTS\r\n", selected->max);
  1020. selected->toldMax = selected->max;
  1021. Bprint(&bout, "* %lud RECENT\r\n", selected->recent);
  1022. selected->toldRecent = selected->recent;
  1023. for(m = selected->msgs; m != nil; m = m->next){
  1024. if(!m->expunged && (m->flags & MSeen) != MSeen){
  1025. Bprint(&bout, "* OK [UNSEEN %ld]\r\n", m->seq);
  1026. break;
  1027. }
  1028. }
  1029. Bprint(&bout, "* OK [PERMANENTFLAGS (\\Seen \\Answered \\Flagged \\Draft \\Deleted)]\r\n");
  1030. Bprint(&bout, "* OK [UIDNEXT %ld]\r\n", selected->uidnext);
  1031. Bprint(&bout, "* OK [UIDVALIDITY %ld]\r\n", selected->uidvalidity);
  1032. s = "READ-ONLY";
  1033. if(selected->writable)
  1034. s = "READ-WRITE";
  1035. Bprint(&bout, "%s OK [%s] %s %s completed\r\n", tg, s, cmd, mbox);
  1036. }
  1037. static NamedInt statusItems[] =
  1038. {
  1039. {"MESSAGES", SMessages},
  1040. {"RECENT", SRecent},
  1041. {"UIDNEXT", SUidNext},
  1042. {"UIDVALIDITY", SUidValidity},
  1043. {"UNSEEN", SUnseen},
  1044. {nil, 0}
  1045. };
  1046. static void
  1047. statusCmd(char *tg, char *cmd)
  1048. {
  1049. Box *box;
  1050. Msg *m;
  1051. char *s, *mbox;
  1052. ulong v;
  1053. int si, i;
  1054. mustBe(' ');
  1055. mbox = astring();
  1056. mustBe(' ');
  1057. mustBe('(');
  1058. si = 0;
  1059. for(;;){
  1060. s = atom();
  1061. i = mapInt(statusItems, s);
  1062. if(i == 0)
  1063. parseErr("illegal status item");
  1064. si |= i;
  1065. if(peekc() == ')')
  1066. break;
  1067. mustBe(' ');
  1068. }
  1069. mustBe(')');
  1070. crnl();
  1071. mbox = mboxName(mbox);
  1072. if(mbox == nil || !okMbox(mbox)){
  1073. check();
  1074. Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
  1075. return;
  1076. }
  1077. box = openBox(mbox, "status", 1);
  1078. if(box == nil){
  1079. check();
  1080. Bprint(&bout, "%s NO [TRYCREATE] %s can't open mailbox %s: %r\r\n", tg, cmd, mbox);
  1081. return;
  1082. }
  1083. Bprint(&bout, "* STATUS (");
  1084. s = "";
  1085. for(i = 0; statusItems[i].name != nil; i++){
  1086. if(si & statusItems[i].v){
  1087. v = 0;
  1088. switch(statusItems[i].v){
  1089. case SMessages:
  1090. v = box->max;
  1091. break;
  1092. case SRecent:
  1093. v = box->recent;
  1094. break;
  1095. case SUidNext:
  1096. v = box->uidnext;
  1097. break;
  1098. case SUidValidity:
  1099. v = box->uidvalidity;
  1100. break;
  1101. case SUnseen:
  1102. v = 0;
  1103. for(m = box->msgs; m != nil; m = m->next)
  1104. if((m->flags & MSeen) != MSeen)
  1105. v++;
  1106. break;
  1107. default:
  1108. Bprint(&bout, ")");
  1109. bye("internal error: status item not implemented");
  1110. break;
  1111. }
  1112. Bprint(&bout, "%s%s %lud", s, statusItems[i].name, v);
  1113. s = " ";
  1114. }
  1115. }
  1116. Bprint(&bout, ")\r\n");
  1117. closeBox(box, 1);
  1118. check();
  1119. Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
  1120. }
  1121. static void
  1122. storeCmd(char *tg, char *cmd)
  1123. {
  1124. storeUCmd(tg, cmd, 0);
  1125. }
  1126. static void
  1127. storeUCmd(char *tg, char *cmd, int uids)
  1128. {
  1129. Store *st;
  1130. MsgSet *ms;
  1131. MbLock *ml;
  1132. char *uid;
  1133. ulong max;
  1134. int ok;
  1135. mustBe(' ');
  1136. ms = msgSet(uids);
  1137. mustBe(' ');
  1138. st = storeWhat();
  1139. crnl();
  1140. uid = "";
  1141. if(uids)
  1142. uid = "uid ";
  1143. max = selected->max;
  1144. ml = checkBox(selected, 1);
  1145. ok = ml != nil && forMsgs(selected, ms, max, uids, storeMsg, st);
  1146. closeImp(selected, ml);
  1147. status(uids, uids);
  1148. if(ok)
  1149. Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
  1150. else
  1151. Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd);
  1152. }
  1153. /*
  1154. * minimal implementation of subscribe
  1155. * all folders are automatically subscribed,
  1156. * and can't be unsubscribed
  1157. */
  1158. static void
  1159. subscribeCmd(char *tg, char *cmd)
  1160. {
  1161. Box *box;
  1162. char *mbox;
  1163. int ok;
  1164. mustBe(' ');
  1165. mbox = astring();
  1166. crnl();
  1167. check();
  1168. mbox = mboxName(mbox);
  1169. ok = 0;
  1170. if(mbox != nil && okMbox(mbox)){
  1171. box = openBox(mbox, "subscribe", 0);
  1172. if(box != nil){
  1173. ok = subscribe(mbox, 's');
  1174. closeBox(box, 1);
  1175. }
  1176. }
  1177. if(!ok)
  1178. Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
  1179. else
  1180. Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
  1181. }
  1182. static void
  1183. uidCmd(char *tg, char *cmd)
  1184. {
  1185. char *sub;
  1186. mustBe(' ');
  1187. sub = atom();
  1188. if(cistrcmp(sub, "copy") == 0)
  1189. copyUCmd(tg, sub, 1);
  1190. else if(cistrcmp(sub, "fetch") == 0)
  1191. fetchUCmd(tg, sub, 1);
  1192. else if(cistrcmp(sub, "search") == 0)
  1193. searchUCmd(tg, sub, 1);
  1194. else if(cistrcmp(sub, "store") == 0)
  1195. storeUCmd(tg, sub, 1);
  1196. else{
  1197. clearcmd();
  1198. Bprint(&bout, "%s BAD %s illegal uid command %s\r\n", tg, cmd, sub);
  1199. }
  1200. }
  1201. static void
  1202. unsubscribeCmd(char *tg, char *cmd)
  1203. {
  1204. char *mbox;
  1205. mustBe(' ');
  1206. mbox = astring();
  1207. crnl();
  1208. check();
  1209. mbox = mboxName(mbox);
  1210. if(mbox == nil || !okMbox(mbox) || !subscribe(mbox, 'u'))
  1211. Bprint(&bout, "%s NO %s can't unsubscribe\r\n", tg, cmd);
  1212. else
  1213. Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
  1214. }
  1215. static void
  1216. badsyn(void)
  1217. {
  1218. parseErr("bad syntax");
  1219. }
  1220. static void
  1221. clearcmd(void)
  1222. {
  1223. int c;
  1224. for(;;){
  1225. c = getc();
  1226. if(c < 0)
  1227. bye("end of input");
  1228. if(c == '\n')
  1229. return;
  1230. }
  1231. }
  1232. static void
  1233. crnl(void)
  1234. {
  1235. int c;
  1236. c = getc();
  1237. if(c == '\n')
  1238. return;
  1239. if(c != '\r' || getc() != '\n')
  1240. badsyn();
  1241. }
  1242. static void
  1243. mustBe(int c)
  1244. {
  1245. if(getc() != c){
  1246. ungetc();
  1247. badsyn();
  1248. }
  1249. }
  1250. /*
  1251. * flaglist : '(' ')' | '(' flags ')'
  1252. */
  1253. static int
  1254. flagList(void)
  1255. {
  1256. int f;
  1257. mustBe('(');
  1258. f = 0;
  1259. if(peekc() != ')')
  1260. f = flags();
  1261. mustBe(')');
  1262. return f;
  1263. }
  1264. /*
  1265. * flags : flag | flags ' ' flag
  1266. * flag : '\' atom | atom
  1267. */
  1268. static int
  1269. flags(void)
  1270. {
  1271. int ff, flags;
  1272. char *s;
  1273. int c;
  1274. flags = 0;
  1275. for(;;){
  1276. c = peekc();
  1277. if(c == '\\'){
  1278. mustBe('\\');
  1279. s = atomString(atomStop, "\\");
  1280. }else if(strchr(atomStop, c) != nil)
  1281. s = atom();
  1282. else
  1283. break;
  1284. ff = mapFlag(s);
  1285. if(ff == 0)
  1286. parseErr("flag not supported");
  1287. flags |= ff;
  1288. if(peekc() != ' ')
  1289. break;
  1290. mustBe(' ');
  1291. }
  1292. if(flags == 0)
  1293. parseErr("no flags given");
  1294. return flags;
  1295. }
  1296. /*
  1297. * storeWhat : osign 'FLAGS' ' ' storeflags
  1298. * | osign 'FLAGS.SILENT' ' ' storeflags
  1299. * osign :
  1300. * | '+' | '-'
  1301. * storeflags : flagList | flags
  1302. */
  1303. static Store*
  1304. storeWhat(void)
  1305. {
  1306. int f;
  1307. char *s;
  1308. int c, w;
  1309. c = peekc();
  1310. if(c == '+' || c == '-')
  1311. mustBe(c);
  1312. else
  1313. c = 0;
  1314. s = atom();
  1315. w = 0;
  1316. if(cistrcmp(s, "flags") == 0)
  1317. w = STFlags;
  1318. else if(cistrcmp(s, "flags.silent") == 0)
  1319. w = STFlagsSilent;
  1320. else
  1321. parseErr("illegal store attribute");
  1322. mustBe(' ');
  1323. if(peekc() == '(')
  1324. f = flagList();
  1325. else
  1326. f = flags();
  1327. return mkStore(c, w, f);
  1328. }
  1329. /*
  1330. * fetchWhat : "ALL" | "FULL" | "FAST" | fetchAtt | '(' fetchAtts ')'
  1331. * fetchAtts : fetchAtt | fetchAtts ' ' fetchAtt
  1332. */
  1333. static char *fetchAtom = "(){}%*\"\\[]";
  1334. static Fetch*
  1335. fetchWhat(void)
  1336. {
  1337. Fetch *f;
  1338. char *s;
  1339. if(peekc() == '('){
  1340. getc();
  1341. f = nil;
  1342. for(;;){
  1343. s = atomString(fetchAtom, "");
  1344. f = fetchAtt(s, f);
  1345. if(peekc() == ')')
  1346. break;
  1347. mustBe(' ');
  1348. }
  1349. getc();
  1350. return revFetch(f);
  1351. }
  1352. s = atomString(fetchAtom, "");
  1353. if(cistrcmp(s, "all") == 0)
  1354. f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, mkFetch(FEnvelope, nil))));
  1355. else if(cistrcmp(s, "fast") == 0)
  1356. f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, nil)));
  1357. else if(cistrcmp(s, "full") == 0)
  1358. f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, mkFetch(FEnvelope, mkFetch(FBody, nil)))));
  1359. else
  1360. f = fetchAtt(s, nil);
  1361. return f;
  1362. }
  1363. /*
  1364. * fetchAtt : "ENVELOPE" | "FLAGS" | "INTERNALDATE"
  1365. * | "RFC822" | "RFC822.HEADER" | "RFC822.SIZE" | "RFC822.TEXT"
  1366. * | "BODYSTRUCTURE"
  1367. * | "UID"
  1368. * | "BODY"
  1369. * | "BODY" bodysubs
  1370. * | "BODY.PEEK" bodysubs
  1371. * bodysubs : sect
  1372. * | sect '<' number '.' nz-number '>'
  1373. * sect : '[' sectSpec ']'
  1374. * sectSpec : sectMsgText
  1375. * | sectPart
  1376. * | sectPart '.' sectText
  1377. * sectPart : nz-number
  1378. * | sectPart '.' nz-number
  1379. */
  1380. static Fetch*
  1381. fetchAtt(char *s, Fetch *f)
  1382. {
  1383. NList *sect;
  1384. int c;
  1385. if(cistrcmp(s, "envelope") == 0)
  1386. return mkFetch(FEnvelope, f);
  1387. if(cistrcmp(s, "flags") == 0)
  1388. return mkFetch(FFlags, f);
  1389. if(cistrcmp(s, "internaldate") == 0)
  1390. return mkFetch(FInternalDate, f);
  1391. if(cistrcmp(s, "RFC822") == 0)
  1392. return mkFetch(FRfc822, f);
  1393. if(cistrcmp(s, "RFC822.header") == 0)
  1394. return mkFetch(FRfc822Head, f);
  1395. if(cistrcmp(s, "RFC822.size") == 0)
  1396. return mkFetch(FRfc822Size, f);
  1397. if(cistrcmp(s, "RFC822.text") == 0)
  1398. return mkFetch(FRfc822Text, f);
  1399. if(cistrcmp(s, "bodystructure") == 0)
  1400. return mkFetch(FBodyStruct, f);
  1401. if(cistrcmp(s, "uid") == 0)
  1402. return mkFetch(FUid, f);
  1403. if(cistrcmp(s, "body") == 0){
  1404. if(peekc() != '[')
  1405. return mkFetch(FBody, f);
  1406. f = mkFetch(FBodySect, f);
  1407. }else if(cistrcmp(s, "body.peek") == 0)
  1408. f = mkFetch(FBodyPeek, f);
  1409. else
  1410. parseErr("illegal fetch attribute");
  1411. mustBe('[');
  1412. c = peekc();
  1413. if(c >= '1' && c <= '9'){
  1414. sect = mkNList(number(1), nil);
  1415. while(peekc() == '.'){
  1416. getc();
  1417. c = peekc();
  1418. if(c >= '1' && c <= '9'){
  1419. sect = mkNList(number(1), sect);
  1420. }else{
  1421. break;
  1422. }
  1423. }
  1424. f->sect = revNList(sect);
  1425. }
  1426. if(peekc() != ']')
  1427. sectText(f, f->sect != nil);
  1428. mustBe(']');
  1429. if(peekc() != '<')
  1430. return f;
  1431. f->partial = 1;
  1432. mustBe('<');
  1433. f->start = number(0);
  1434. mustBe('.');
  1435. f->size = number(1);
  1436. mustBe('>');
  1437. return f;
  1438. }
  1439. /*
  1440. * sectText : sectMsgText | "MIME"
  1441. * sectMsgText : "HEADER"
  1442. * | "TEXT"
  1443. * | "HEADER.FIELDS" ' ' hdrList
  1444. * | "HEADER.FIELDS.NOT" ' ' hdrList
  1445. * hdrList : '(' hdrs ')'
  1446. * hdrs: : astring
  1447. * | hdrs ' ' astring
  1448. */
  1449. static void
  1450. sectText(Fetch *f, int mimeOk)
  1451. {
  1452. SList *h;
  1453. char *s;
  1454. s = atomString(fetchAtom, "");
  1455. if(cistrcmp(s, "header") == 0){
  1456. f->part = FPHead;
  1457. return;
  1458. }
  1459. if(cistrcmp(s, "text") == 0){
  1460. f->part = FPText;
  1461. return;
  1462. }
  1463. if(mimeOk && cistrcmp(s, "mime") == 0){
  1464. f->part = FPMime;
  1465. return;
  1466. }
  1467. if(cistrcmp(s, "header.fields") == 0)
  1468. f->part = FPHeadFields;
  1469. else if(cistrcmp(s, "header.fields.not") == 0)
  1470. f->part = FPHeadFieldsNot;
  1471. else
  1472. parseErr("illegal fetch section text");
  1473. mustBe(' ');
  1474. mustBe('(');
  1475. h = nil;
  1476. for(;;){
  1477. h = mkSList(astring(), h);
  1478. if(peekc() == ')')
  1479. break;
  1480. mustBe(' ');
  1481. }
  1482. mustBe(')');
  1483. f->hdrs = revSList(h);
  1484. }
  1485. /*
  1486. * searchWhat : "CHARSET" ' ' astring searchkeys | searchkeys
  1487. * searchkeys : searchkey | searchkeys ' ' searchkey
  1488. * searchkey : "ALL" | "ANSWERED" | "DELETED" | "FLAGGED" | "NEW" | "OLD" | "RECENT"
  1489. * | "SEEN" | "UNANSWERED" | "UNDELETED" | "UNFLAGGED" | "DRAFT" | "UNDRAFT"
  1490. * | astrkey ' ' astring
  1491. * | datekey ' ' date
  1492. * | "KEYWORD" ' ' flag | "UNKEYWORD" flag
  1493. * | "LARGER" ' ' number | "SMALLER" ' ' number
  1494. * | "HEADER" astring ' ' astring
  1495. * | set | "UID" ' ' set
  1496. * | "NOT" ' ' searchkey
  1497. * | "OR" ' ' searchkey ' ' searchkey
  1498. * | '(' searchkeys ')'
  1499. * astrkey : "BCC" | "BODY" | "CC" | "FROM" | "SUBJECT" | "TEXT" | "TO"
  1500. * datekey : "BEFORE" | "ON" | "SINCE" | "SENTBEFORE" | "SENTON" | "SENTSINCE"
  1501. */
  1502. static NamedInt searchMap[] =
  1503. {
  1504. {"ALL", SKAll},
  1505. {"ANSWERED", SKAnswered},
  1506. {"DELETED", SKDeleted},
  1507. {"FLAGGED", SKFlagged},
  1508. {"NEW", SKNew},
  1509. {"OLD", SKOld},
  1510. {"RECENT", SKRecent},
  1511. {"SEEN", SKSeen},
  1512. {"UNANSWERED", SKUnanswered},
  1513. {"UNDELETED", SKUndeleted},
  1514. {"UNFLAGGED", SKUnflagged},
  1515. {"DRAFT", SKDraft},
  1516. {"UNDRAFT", SKUndraft},
  1517. {"UNSEEN", SKUnseen},
  1518. {nil, 0}
  1519. };
  1520. static NamedInt searchMapStr[] =
  1521. {
  1522. {"CHARSET", SKCharset},
  1523. {"BCC", SKBcc},
  1524. {"BODY", SKBody},
  1525. {"CC", SKCc},
  1526. {"FROM", SKFrom},
  1527. {"SUBJECT", SKSubject},
  1528. {"TEXT", SKText},
  1529. {"TO", SKTo},
  1530. {nil, 0}
  1531. };
  1532. static NamedInt searchMapDate[] =
  1533. {
  1534. {"BEFORE", SKBefore},
  1535. {"ON", SKOn},
  1536. {"SINCE", SKSince},
  1537. {"SENTBEFORE", SKSentBefore},
  1538. {"SENTON", SKSentOn},
  1539. {"SENTSINCE", SKSentSince},
  1540. {nil, 0}
  1541. };
  1542. static NamedInt searchMapFlag[] =
  1543. {
  1544. {"KEYWORD", SKKeyword},
  1545. {"UNKEYWORD", SKUnkeyword},
  1546. {nil, 0}
  1547. };
  1548. static NamedInt searchMapNum[] =
  1549. {
  1550. {"SMALLER", SKSmaller},
  1551. {"LARGER", SKLarger},
  1552. {nil, 0}
  1553. };
  1554. static Search*
  1555. searchKeys(int first, Search *tail)
  1556. {
  1557. Search *s;
  1558. for(;;){
  1559. if(peekc() == '('){
  1560. getc();
  1561. tail = searchKeys(0, tail);
  1562. mustBe(')');
  1563. }else{
  1564. s = searchKey(first);
  1565. tail->next = s;
  1566. tail = s;
  1567. }
  1568. first = 0;
  1569. if(peekc() != ' ')
  1570. break;
  1571. getc();
  1572. }
  1573. return tail;
  1574. }
  1575. static Search*
  1576. searchKey(int first)
  1577. {
  1578. Search *sr, rock;
  1579. Tm tm;
  1580. char *a;
  1581. int i, c;
  1582. sr = binalloc(&parseBin, sizeof(Search), 1);
  1583. if(sr == nil)
  1584. parseErr("out of memory");
  1585. c = peekc();
  1586. if(c >= '0' && c <= '9'){
  1587. sr->key = SKSet;
  1588. sr->set = msgSet(0);
  1589. return sr;
  1590. }
  1591. a = atom();
  1592. if(i = mapInt(searchMap, a))
  1593. sr->key = i;
  1594. else if(i = mapInt(searchMapStr, a)){
  1595. if(!first && i == SKCharset)
  1596. parseErr("illegal search key");
  1597. sr->key = i;
  1598. mustBe(' ');
  1599. sr->s = astring();
  1600. }else if(i = mapInt(searchMapDate, a)){
  1601. sr->key = i;
  1602. mustBe(' ');
  1603. c = peekc();
  1604. if(c == '"')
  1605. getc();
  1606. a = atom();
  1607. if(!imap4Date(&tm, a))
  1608. parseErr("bad date format");
  1609. sr->year = tm.year;
  1610. sr->mon = tm.mon;
  1611. sr->mday = tm.mday;
  1612. if(c == '"')
  1613. mustBe('"');
  1614. }else if(i = mapInt(searchMapFlag, a)){
  1615. sr->key = i;
  1616. mustBe(' ');
  1617. c = peekc();
  1618. if(c == '\\'){
  1619. mustBe('\\');
  1620. a = atomString(atomStop, "\\");
  1621. }else
  1622. a = atom();
  1623. i = mapFlag(a);
  1624. if(i == 0)
  1625. parseErr("flag not supported");
  1626. sr->num = i;
  1627. }else if(i = mapInt(searchMapNum, a)){
  1628. sr->key = i;
  1629. mustBe(' ');
  1630. sr->num = number(0);
  1631. }else if(cistrcmp(a, "HEADER") == 0){
  1632. sr->key = SKHeader;
  1633. mustBe(' ');
  1634. sr->hdr = astring();
  1635. mustBe(' ');
  1636. sr->s = astring();
  1637. }else if(cistrcmp(a, "UID") == 0){
  1638. sr->key = SKUid;
  1639. mustBe(' ');
  1640. sr->set = msgSet(0);
  1641. }else if(cistrcmp(a, "NOT") == 0){
  1642. sr->key = SKNot;
  1643. mustBe(' ');
  1644. rock.next = nil;
  1645. searchKeys(0, &rock);
  1646. sr->left = rock.next;
  1647. }else if(cistrcmp(a, "OR") == 0){
  1648. sr->key = SKOr;
  1649. mustBe(' ');
  1650. rock.next = nil;
  1651. searchKeys(0, &rock);
  1652. sr->left = rock.next;
  1653. mustBe(' ');
  1654. rock.next = nil;
  1655. searchKeys(0, &rock);
  1656. sr->right = rock.next;
  1657. }else
  1658. parseErr("illegal search key");
  1659. return sr;
  1660. }
  1661. /*
  1662. * set : seqno
  1663. * | seqno ':' seqno
  1664. * | set ',' set
  1665. * seqno: nz-number
  1666. * | '*'
  1667. *
  1668. */
  1669. static MsgSet*
  1670. msgSet(int uids)
  1671. {
  1672. MsgSet head, *last, *ms;
  1673. ulong from, to;
  1674. last = &head;
  1675. head.next = nil;
  1676. for(;;){
  1677. from = uids ? uidNo() : seqNo();
  1678. to = from;
  1679. if(peekc() == ':'){
  1680. getc();
  1681. to = uids ? uidNo() : seqNo();
  1682. }
  1683. ms = binalloc(&parseBin, sizeof(MsgSet), 0);
  1684. if(ms == nil)
  1685. parseErr("out of memory");
  1686. ms->from = from;
  1687. ms->to = to;
  1688. ms->next = nil;
  1689. last->next = ms;
  1690. last = ms;
  1691. if(peekc() != ',')
  1692. break;
  1693. getc();
  1694. }
  1695. return head.next;
  1696. }
  1697. static ulong
  1698. seqNo(void)
  1699. {
  1700. if(peekc() == '*'){
  1701. getc();
  1702. return ~0UL;
  1703. }
  1704. return number(1);
  1705. }
  1706. static ulong
  1707. uidNo(void)
  1708. {
  1709. if(peekc() == '*'){
  1710. getc();
  1711. return ~0UL;
  1712. }
  1713. return number(0);
  1714. }
  1715. /*
  1716. * 7 bit, non-ctl chars, no (){%*"\
  1717. * NIL is special case for nstring or parenlist
  1718. */
  1719. static char *
  1720. atom(void)
  1721. {
  1722. return atomString(atomStop, "");
  1723. }
  1724. /*
  1725. * like an atom, but no +
  1726. */
  1727. static char *
  1728. tag(void)
  1729. {
  1730. return atomString("+(){%*\"\\", "");
  1731. }
  1732. /*
  1733. * string or atom allowing %*
  1734. */
  1735. static char *
  1736. listmbox(void)
  1737. {
  1738. int c;
  1739. c = peekc();
  1740. if(c == '{')
  1741. return literal();
  1742. if(c == '"')
  1743. return quoted();
  1744. return atomString("(){\"\\", "");
  1745. }
  1746. /*
  1747. * string or atom
  1748. */
  1749. static char *
  1750. astring(void)
  1751. {
  1752. int c;
  1753. c = peekc();
  1754. if(c == '{')
  1755. return literal();
  1756. if(c == '"')
  1757. return quoted();
  1758. return atom();
  1759. }
  1760. /*
  1761. * 7 bit, non-ctl chars, none from exception list
  1762. */
  1763. static char *
  1764. atomString(char *disallowed, char *initial)
  1765. {
  1766. char *s;
  1767. int c, ns, as;
  1768. ns = strlen(initial);
  1769. s = binalloc(&parseBin, ns + StrAlloc, 0);
  1770. if(s == nil)
  1771. parseErr("out of memory");
  1772. strcpy(s, initial);
  1773. as = ns + StrAlloc;
  1774. for(;;){
  1775. c = getc();
  1776. if(c <= ' ' || c >= 0x7f || strchr(disallowed, c) != nil){
  1777. ungetc();
  1778. break;
  1779. }
  1780. s[ns++] = c;
  1781. if(ns >= as){
  1782. s = bingrow(&parseBin, s, as, as + StrAlloc, 0);
  1783. if(s == nil)
  1784. parseErr("out of memory");
  1785. as += StrAlloc;
  1786. }
  1787. }
  1788. if(ns == 0)
  1789. badsyn();
  1790. s[ns] = '\0';
  1791. return s;
  1792. }
  1793. /*
  1794. * quoted: '"' chars* '"'
  1795. * chars: 1-128 except \r and \n
  1796. */
  1797. static char *
  1798. quoted(void)
  1799. {
  1800. char *s;
  1801. int c, ns, as;
  1802. mustBe('"');
  1803. s = binalloc(&parseBin, StrAlloc, 0);
  1804. if(s == nil)
  1805. parseErr("out of memory");
  1806. as = StrAlloc;
  1807. ns = 0;
  1808. for(;;){
  1809. c = getc();
  1810. if(c == '"')
  1811. break;
  1812. if(c < 1 || c > 0x7f || c == '\r' || c == '\n')
  1813. badsyn();
  1814. if(c == '\\'){
  1815. c = getc();
  1816. if(c != '\\' && c != '"')
  1817. badsyn();
  1818. }
  1819. s[ns++] = c;
  1820. if(ns >= as){
  1821. s = bingrow(&parseBin, s, as, as + StrAlloc, 0);
  1822. if(s == nil)
  1823. parseErr("out of memory");
  1824. as += StrAlloc;
  1825. }
  1826. }
  1827. s[ns] = '\0';
  1828. return s;
  1829. }
  1830. /*
  1831. * litlen: {number}\r\n
  1832. */
  1833. static ulong
  1834. litlen(void)
  1835. {
  1836. ulong v;
  1837. mustBe('{');
  1838. v = number(0);
  1839. mustBe('}');
  1840. crnl();
  1841. return v;
  1842. }
  1843. /*
  1844. * literal: litlen data<0:litlen>
  1845. */
  1846. static char *
  1847. literal(void)
  1848. {
  1849. char *s;
  1850. ulong v;
  1851. v = litlen();
  1852. s = binalloc(&parseBin, v+1, 0);
  1853. if(s == nil)
  1854. parseErr("out of memory");
  1855. Bprint(&bout, "+ Ready for literal data\r\n");
  1856. if(Bflush(&bout) < 0)
  1857. writeErr();
  1858. if(v != 0 && Bread(&bin, s, v) != v)
  1859. badsyn();
  1860. s[v] = '\0';
  1861. return s;
  1862. }
  1863. /*
  1864. * digits; number is 32 bits
  1865. */
  1866. static ulong
  1867. number(int nonzero)
  1868. {
  1869. ulong v;
  1870. int c, first;
  1871. v = 0;
  1872. first = 1;
  1873. for(;;){
  1874. c = getc();
  1875. if(c < '0' || c > '9'){
  1876. ungetc();
  1877. if(first)
  1878. badsyn();
  1879. break;
  1880. }
  1881. if(nonzero && first && c == '0')
  1882. badsyn();
  1883. c -= '0';
  1884. first = 0;
  1885. if(v > UlongMax/10 || v == UlongMax/10 && c > UlongMax%10)
  1886. parseErr("number out of range\r\n");
  1887. v = v * 10 + c;
  1888. }
  1889. return v;
  1890. }
  1891. static int
  1892. getc(void)
  1893. {
  1894. return Bgetc(&bin);
  1895. }
  1896. static void
  1897. ungetc(void)
  1898. {
  1899. Bungetc(&bin);
  1900. }
  1901. static int
  1902. peekc(void)
  1903. {
  1904. int c;
  1905. c = Bgetc(&bin);
  1906. Bungetc(&bin);
  1907. return c;
  1908. }