imap4.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874
  1. #include "common.h"
  2. #include <ctype.h>
  3. #include <plumb.h>
  4. #include <libsec.h>
  5. #include <auth.h>
  6. #include "dat.h"
  7. #pragma varargck argpos imap4cmd 2
  8. #pragma varargck type "Z" char*
  9. int doublequote(Fmt*);
  10. int pipeline = 1;
  11. static char Eio[] = "i/o error";
  12. typedef struct Imap Imap;
  13. struct Imap {
  14. char *freep; // free this to free the strings below
  15. char *host;
  16. char *user;
  17. char *mbox;
  18. int mustssl;
  19. int refreshtime;
  20. int debug;
  21. ulong tag;
  22. ulong validity;
  23. int nmsg;
  24. int size;
  25. char *base;
  26. char *data;
  27. vlong *uid;
  28. int nuid;
  29. int muid;
  30. Thumbprint *thumb;
  31. // open network connection
  32. Biobuf bin;
  33. Biobuf bout;
  34. int fd;
  35. };
  36. static char*
  37. removecr(char *s)
  38. {
  39. char *r, *w;
  40. for(r=w=s; *r; r++)
  41. if(*r != '\r')
  42. *w++ = *r;
  43. *w = '\0';
  44. return s;
  45. }
  46. //
  47. // send imap4 command
  48. //
  49. static void
  50. imap4cmd(Imap *imap, char *fmt, ...)
  51. {
  52. char buf[128], *p;
  53. va_list va;
  54. va_start(va, fmt);
  55. p = buf+sprint(buf, "9X%lud ", imap->tag);
  56. vseprint(p, buf+sizeof(buf), fmt, va);
  57. va_end(va);
  58. p = buf+strlen(buf);
  59. if(p > (buf+sizeof(buf)-3))
  60. sysfatal("imap4 command too long");
  61. if(imap->debug)
  62. fprint(2, "-> %s\n", buf);
  63. strcpy(p, "\r\n");
  64. Bwrite(&imap->bout, buf, strlen(buf));
  65. Bflush(&imap->bout);
  66. }
  67. enum {
  68. OK,
  69. NO,
  70. BAD,
  71. BYE,
  72. EXISTS,
  73. STATUS,
  74. FETCH,
  75. UNKNOWN,
  76. };
  77. static char *verblist[] = {
  78. [OK] "OK",
  79. [NO] "NO",
  80. [BAD] "BAD",
  81. [BYE] "BYE",
  82. [EXISTS] "EXISTS",
  83. [STATUS] "STATUS",
  84. [FETCH] "FETCH",
  85. };
  86. static int
  87. verbcode(char *verb)
  88. {
  89. int i;
  90. char *q;
  91. if(q = strchr(verb, ' '))
  92. *q = '\0';
  93. for(i=0; i<nelem(verblist); i++)
  94. if(verblist[i] && strcmp(verblist[i], verb)==0){
  95. if(q)
  96. *q = ' ';
  97. return i;
  98. }
  99. if(q)
  100. *q = ' ';
  101. return UNKNOWN;
  102. }
  103. static void
  104. strupr(char *s)
  105. {
  106. for(; *s; s++)
  107. if('a' <= *s && *s <= 'z')
  108. *s += 'A'-'a';
  109. }
  110. static void
  111. imapgrow(Imap *imap, int n)
  112. {
  113. int i;
  114. if(imap->data == nil){
  115. imap->base = emalloc(n+1);
  116. imap->data = imap->base;
  117. imap->size = n+1;
  118. }
  119. if(n >= imap->size){
  120. // friggin microsoft - reallocate
  121. i = imap->data - imap->base;
  122. imap->base = erealloc(imap->base, i+n+1);
  123. imap->data = imap->base + i;
  124. imap->size = n+1;
  125. }
  126. }
  127. //
  128. // get imap4 response line. there might be various
  129. // data or other informational lines mixed in.
  130. //
  131. static char*
  132. imap4resp(Imap *imap)
  133. {
  134. char *line, *p, *ep, *op, *q, *r, *en, *verb;
  135. int i, n;
  136. static char error[256];
  137. while(p = Brdline(&imap->bin, '\n')){
  138. ep = p+Blinelen(&imap->bin);
  139. while(ep > p && (ep[-1]=='\n' || ep[-1]=='\r'))
  140. *--ep = '\0';
  141. if(imap->debug)
  142. fprint(2, "<- %s\n", p);
  143. strupr(p);
  144. switch(p[0]){
  145. case '+':
  146. if(imap->tag == 0)
  147. fprint(2, "unexpected: %s\n", p);
  148. break;
  149. // ``unsolicited'' information; everything happens here.
  150. case '*':
  151. if(p[1]!=' ')
  152. continue;
  153. p += 2;
  154. line = p;
  155. n = strtol(p, &p, 10);
  156. if(*p==' ')
  157. p++;
  158. verb = p;
  159. if(p = strchr(verb, ' '))
  160. p++;
  161. else
  162. p = verb+strlen(verb);
  163. switch(verbcode(verb)){
  164. case OK:
  165. case NO:
  166. case BAD:
  167. // human readable text at p;
  168. break;
  169. case BYE:
  170. // early disconnect
  171. // human readable text at p;
  172. break;
  173. // * 32 EXISTS
  174. case EXISTS:
  175. imap->nmsg = n;
  176. break;
  177. // * STATUS Inbox (MESSAGES 2 UIDVALIDITY 960164964)
  178. case STATUS:
  179. if(q = strstr(p, "MESSAGES"))
  180. imap->nmsg = atoi(q+8);
  181. if(q = strstr(p, "UIDVALIDITY"))
  182. imap->validity = strtoul(q+11, 0, 10);
  183. break;
  184. case FETCH:
  185. // * 1 FETCH (uid 8889 RFC822.SIZE 3031 body[] {3031}
  186. // <3031 bytes of data>
  187. // )
  188. if(strstr(p, "RFC822.SIZE") && strstr(p, "BODY[]")){
  189. if((q = strchr(p, '{'))
  190. && (n=strtol(q+1, &en, 0), *en=='}')){
  191. if(imap->data == nil || n >= imap->size)
  192. imapgrow(imap, n);
  193. if((i = Bread(&imap->bin, imap->data, n)) != n){
  194. snprint(error, sizeof error,
  195. "short read %d != %d: %r\n",
  196. i, n);
  197. return error;
  198. }
  199. if(imap->debug)
  200. fprint(2, "<- read %d bytes\n", n);
  201. imap->data[n] = '\0';
  202. if(imap->debug)
  203. fprint(2, "<- %s\n", imap->data);
  204. imap->data += n;
  205. imap->size -= n;
  206. p = Brdline(&imap->bin, '\n');
  207. if(imap->debug)
  208. fprint(2, "<- ignoring %.*s\n",
  209. Blinelen(&imap->bin), p);
  210. }else if((q = strchr(p, '"')) && (r = strchr(q+1, '"'))){
  211. *r = '\0';
  212. q++;
  213. n = r-q;
  214. if(imap->data == nil || n >= imap->size)
  215. imapgrow(imap, n);
  216. memmove(imap->data, q, n);
  217. imap->data[n] = '\0';
  218. imap->data += n;
  219. imap->size -= n;
  220. }else
  221. return "confused about FETCH response";
  222. break;
  223. }
  224. // * 1 FETCH (UID 1 RFC822.SIZE 511)
  225. if(q=strstr(p, "RFC822.SIZE")){
  226. imap->size = atoi(q+11);
  227. break;
  228. }
  229. // * 1 FETCH (UID 1 RFC822.HEADER {496}
  230. // <496 bytes of data>
  231. // )
  232. // * 1 FETCH (UID 1 RFC822.HEADER "data")
  233. if(strstr(p, "RFC822.HEADER") || strstr(p, "RFC822.TEXT")){
  234. if((q = strchr(p, '{'))
  235. && (n=strtol(q+1, &en, 0), *en=='}')){
  236. if(imap->data == nil || n >= imap->size)
  237. imapgrow(imap, n);
  238. if((i = Bread(&imap->bin, imap->data, n)) != n){
  239. snprint(error, sizeof error,
  240. "short read %d != %d: %r\n",
  241. i, n);
  242. return error;
  243. }
  244. if(imap->debug)
  245. fprint(2, "<- read %d bytes\n", n);
  246. imap->data[n] = '\0';
  247. if(imap->debug)
  248. fprint(2, "<- %s\n", imap->data);
  249. imap->data += n;
  250. imap->size -= n;
  251. p = Brdline(&imap->bin, '\n');
  252. if(imap->debug)
  253. fprint(2, "<- ignoring %.*s\n",
  254. Blinelen(&imap->bin), p);
  255. }else if((q = strchr(p, '"')) && (r = strchr(q+1, '"'))){
  256. *r = '\0';
  257. q++;
  258. n = r-q;
  259. if(imap->data == nil || n >= imap->size)
  260. imapgrow(imap, n);
  261. memmove(imap->data, q, n);
  262. imap->data[n] = '\0';
  263. imap->data += n;
  264. imap->size -= n;
  265. }else
  266. return "confused about FETCH response";
  267. break;
  268. }
  269. // * 1 FETCH (UID 1)
  270. // * 2 FETCH (UID 6)
  271. if(q = strstr(p, "UID")){
  272. if(imap->nuid < imap->muid)
  273. imap->uid[imap->nuid++] = ((vlong)imap->validity<<32)|strtoul(q+3, nil, 10);
  274. break;
  275. }
  276. }
  277. if(imap->tag == 0)
  278. return line;
  279. break;
  280. case '9': // response to our message
  281. op = p;
  282. if(p[1]=='X' && strtoul(p+2, &p, 10)==imap->tag){
  283. while(*p==' ')
  284. p++;
  285. imap->tag++;
  286. return p;
  287. }
  288. fprint(2, "expected %lud; got %s\n", imap->tag, op);
  289. break;
  290. default:
  291. if(imap->debug || *p)
  292. fprint(2, "unexpected line: %s\n", p);
  293. }
  294. }
  295. snprint(error, sizeof error, "i/o error: %r\n");
  296. return error;
  297. }
  298. static int
  299. isokay(char *resp)
  300. {
  301. return strncmp(resp, "OK", 2)==0;
  302. }
  303. //
  304. // log in to IMAP4 server, select mailbox, no SSL at the moment
  305. //
  306. static char*
  307. imap4login(Imap *imap)
  308. {
  309. char *s;
  310. UserPasswd *up;
  311. imap->tag = 0;
  312. s = imap4resp(imap);
  313. if(!isokay(s))
  314. return "error in initial IMAP handshake";
  315. if(imap->user != nil)
  316. up = auth_getuserpasswd(auth_getkey, "proto=pass service=imap server=%q user=%q", imap->host, imap->user);
  317. else
  318. up = auth_getuserpasswd(auth_getkey, "proto=pass service=imap server=%q", imap->host);
  319. if(up == nil)
  320. return "cannot find IMAP password";
  321. imap->tag = 1;
  322. imap4cmd(imap, "LOGIN %Z %Z", up->user, up->passwd);
  323. free(up);
  324. if(!isokay(s = imap4resp(imap)))
  325. return s;
  326. imap4cmd(imap, "SELECT %Z", imap->mbox);
  327. if(!isokay(s = imap4resp(imap)))
  328. return s;
  329. return nil;
  330. }
  331. //
  332. // push tls onto a connection
  333. //
  334. int
  335. mypushtls(int fd)
  336. {
  337. int p[2];
  338. char buf[10];
  339. if(pipe(p) < 0)
  340. return -1;
  341. switch(fork()){
  342. case -1:
  343. close(p[0]);
  344. close(p[1]);
  345. return -1;
  346. case 0:
  347. close(p[1]);
  348. dup(p[0], 0);
  349. dup(p[0], 1);
  350. sprint(buf, "/fd/%d", fd);
  351. execl("/bin/tlsrelay", "tlsrelay", "-f", buf, nil);
  352. _exits(nil);
  353. default:
  354. break;
  355. }
  356. close(fd);
  357. close(p[0]);
  358. return p[1];
  359. }
  360. //
  361. // dial and handshake with the imap server
  362. //
  363. static char*
  364. imap4dial(Imap *imap)
  365. {
  366. char *err, *port;
  367. uchar digest[SHA1dlen];
  368. int sfd;
  369. TLSconn conn;
  370. if(imap->fd >= 0){
  371. imap4cmd(imap, "noop");
  372. if(isokay(imap4resp(imap)))
  373. return nil;
  374. close(imap->fd);
  375. imap->fd = -1;
  376. }
  377. if(imap->mustssl)
  378. port = "imaps";
  379. else
  380. port = "imap";
  381. if((imap->fd = dial(netmkaddr(imap->host, "net", port), 0, 0, 0)) < 0)
  382. return geterrstr();
  383. if(imap->mustssl){
  384. memset(&conn, 0, sizeof conn);
  385. sfd = tlsClient(imap->fd, &conn);
  386. if(sfd < 0)
  387. sysfatal("tlsClient: %r");
  388. if(conn.cert==nil || conn.certlen <= 0)
  389. sysfatal("server did not provide TLS certificate");
  390. sha1(conn.cert, conn.certlen, digest, nil);
  391. if(!imap->thumb || !okThumbprint(digest, imap->thumb)){
  392. fmtinstall('H', encodefmt);
  393. sysfatal("server certificate %.*H not recognized", SHA1dlen, digest);
  394. }
  395. free(conn.cert);
  396. close(imap->fd);
  397. imap->fd = sfd;
  398. if(imap->debug){
  399. char fn[128];
  400. int fd;
  401. snprint(fn, sizeof fn, "%s/ctl", conn.dir);
  402. fd = open(fn, ORDWR);
  403. if(fd < 0)
  404. fprint(2, "opening ctl: %r\n");
  405. if(fprint(fd, "debug") < 0)
  406. fprint(2, "writing ctl: %r\n");
  407. close(fd);
  408. }
  409. }
  410. Binit(&imap->bin, imap->fd, OREAD);
  411. Binit(&imap->bout, imap->fd, OWRITE);
  412. if(err = imap4login(imap)) {
  413. close(imap->fd);
  414. return err;
  415. }
  416. return nil;
  417. }
  418. //
  419. // close connection
  420. //
  421. static void
  422. imap4hangup(Imap *imap)
  423. {
  424. imap4cmd(imap, "LOGOUT");
  425. imap4resp(imap);
  426. close(imap->fd);
  427. }
  428. //
  429. // download a single message
  430. //
  431. static char*
  432. imap4fetch(Mailbox *mb, Message *m)
  433. {
  434. int i;
  435. char *p, *s, sdigest[2*SHA1dlen+1];
  436. Imap *imap;
  437. imap = mb->aux;
  438. imap->size = 0;
  439. if(!isokay(s = imap4resp(imap)))
  440. return s;
  441. p = imap->base;
  442. if(p == nil)
  443. return "did not get message body";
  444. removecr(p);
  445. free(m->start);
  446. m->start = p;
  447. m->end = p+strlen(p);
  448. m->bend = m->rbend = m->end;
  449. m->header = m->start;
  450. imap->base = nil;
  451. imap->data = nil;
  452. parse(m, 0, mb, 1);
  453. // digest headers
  454. sha1((uchar*)m->start, m->end - m->start, m->digest, nil);
  455. for(i = 0; i < SHA1dlen; i++)
  456. sprint(sdigest+2*i, "%2.2ux", m->digest[i]);
  457. m->sdigest = s_copy(sdigest);
  458. return nil;
  459. }
  460. //
  461. // check for new messages on imap4 server
  462. // download new messages, mark deleted messages
  463. //
  464. static char*
  465. imap4read(Imap *imap, Mailbox *mb, int doplumb)
  466. {
  467. char *s;
  468. int i, ignore, nnew, t;
  469. Message *m, *next, **l;
  470. imap4cmd(imap, "STATUS %Z (MESSAGES UIDVALIDITY)", imap->mbox);
  471. if(!isokay(s = imap4resp(imap)))
  472. return s;
  473. imap->nuid = 0;
  474. imap->uid = erealloc(imap->uid, imap->nmsg*sizeof(imap->uid[0]));
  475. imap->muid = imap->nmsg;
  476. if(imap->nmsg > 0){
  477. imap4cmd(imap, "UID FETCH 1:* UID");
  478. if(!isokay(s = imap4resp(imap)))
  479. return s;
  480. }
  481. l = &mb->root->part;
  482. for(i=0; i<imap->nuid; i++){
  483. ignore = 0;
  484. while(*l != nil){
  485. if((*l)->imapuid == imap->uid[i]){
  486. ignore = 1;
  487. l = &(*l)->next;
  488. break;
  489. }else{
  490. // old mail, we don't have it anymore
  491. if(doplumb)
  492. mailplumb(mb, *l, 1);
  493. (*l)->inmbox = 0;
  494. (*l)->deleted = 1;
  495. l = &(*l)->next;
  496. }
  497. }
  498. if(ignore)
  499. continue;
  500. // new message
  501. m = newmessage(mb->root);
  502. m->mallocd = 1;
  503. m->inmbox = 1;
  504. m->imapuid = imap->uid[i];
  505. // add to chain, will download soon
  506. *l = m;
  507. l = &m->next;
  508. }
  509. // whatever is left at the end of the chain is gone
  510. while(*l != nil){
  511. if(doplumb)
  512. mailplumb(mb, *l, 1);
  513. (*l)->inmbox = 0;
  514. (*l)->deleted = 1;
  515. l = &(*l)->next;
  516. }
  517. // download new messages
  518. t = imap->tag;
  519. if(pipeline)
  520. switch(rfork(RFPROC|RFMEM)){
  521. case -1:
  522. sysfatal("rfork: %r");
  523. default:
  524. break;
  525. case 0:
  526. for(m = mb->root->part; m != nil; m = m->next){
  527. if(m->start != nil)
  528. continue;
  529. if(imap->debug)
  530. fprint(2, "9X%d UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n",
  531. t, (ulong)m->imapuid);
  532. Bprint(&imap->bout, "9X%d UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n",
  533. t++, (ulong)m->imapuid);
  534. }
  535. Bflush(&imap->bout);
  536. _exits(nil);
  537. }
  538. nnew = 0;
  539. for(m=mb->root->part; m!=nil; m=next){
  540. next = m->next;
  541. if(m->start != nil)
  542. continue;
  543. if(!pipeline){
  544. Bprint(&imap->bout, "9X%lud UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n",
  545. (ulong)imap->tag, (ulong)m->imapuid);
  546. Bflush(&imap->bout);
  547. }
  548. if(s = imap4fetch(mb, m)){
  549. // message disappeared? unchain
  550. fprint(2, "download %lud: %s\n", (ulong)m->imapuid, s);
  551. delmessage(mb, m);
  552. mb->root->subname--;
  553. continue;
  554. }
  555. nnew++;
  556. if(doplumb)
  557. mailplumb(mb, m, 0);
  558. }
  559. if(pipeline)
  560. waitpid();
  561. if(nnew || mb->vers == 0){
  562. mb->vers++;
  563. henter(PATH(0, Qtop), mb->name,
  564. (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
  565. }
  566. return nil;
  567. }
  568. //
  569. // sync mailbox
  570. //
  571. static void
  572. imap4purge(Imap *imap, Mailbox *mb)
  573. {
  574. int ndel;
  575. Message *m, *next;
  576. ndel = 0;
  577. for(m=mb->root->part; m!=nil; m=next){
  578. next = m->next;
  579. if(m->deleted && m->refs==0){
  580. if(m->inmbox && (ulong)(m->imapuid>>32)==imap->validity){
  581. imap4cmd(imap, "UID STORE %lud +FLAGS (\\Deleted)", (ulong)m->imapuid);
  582. if(isokay(imap4resp(imap))){
  583. ndel++;
  584. delmessage(mb, m);
  585. }
  586. }else
  587. delmessage(mb, m);
  588. }
  589. }
  590. if(ndel){
  591. imap4cmd(imap, "EXPUNGE");
  592. imap4resp(imap);
  593. }
  594. }
  595. //
  596. // connect to imap4 server, sync mailbox
  597. //
  598. static char*
  599. imap4sync(Mailbox *mb, int doplumb)
  600. {
  601. char *err;
  602. Imap *imap;
  603. imap = mb->aux;
  604. if(err = imap4dial(imap)){
  605. mb->waketime = time(0) + imap->refreshtime;
  606. return err;
  607. }
  608. if((err = imap4read(imap, mb, doplumb)) == nil){
  609. imap4purge(imap, mb);
  610. mb->d->atime = mb->d->mtime = time(0);
  611. }
  612. /*
  613. * don't hang up; leave connection open for next time.
  614. */
  615. // imap4hangup(imap);
  616. mb->waketime = time(0) + imap->refreshtime;
  617. return err;
  618. }
  619. static char Eimap4ctl[] = "bad imap4 control message";
  620. static char*
  621. imap4ctl(Mailbox *mb, int argc, char **argv)
  622. {
  623. int n;
  624. Imap *imap;
  625. imap = mb->aux;
  626. if(argc < 1)
  627. return Eimap4ctl;
  628. if(argc==1 && strcmp(argv[0], "debug")==0){
  629. imap->debug = 1;
  630. return nil;
  631. }
  632. if(argc==1 && strcmp(argv[0], "nodebug")==0){
  633. imap->debug = 0;
  634. return nil;
  635. }
  636. if(argc==1 && strcmp(argv[0], "thumbprint")==0){
  637. if(imap->thumb)
  638. freeThumbprints(imap->thumb);
  639. imap->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude");
  640. }
  641. if(strcmp(argv[0], "refresh")==0){
  642. if(argc==1){
  643. imap->refreshtime = 60;
  644. return nil;
  645. }
  646. if(argc==2){
  647. n = atoi(argv[1]);
  648. if(n < 15)
  649. return Eimap4ctl;
  650. imap->refreshtime = n;
  651. return nil;
  652. }
  653. }
  654. return Eimap4ctl;
  655. }
  656. //
  657. // free extra memory associated with mb
  658. //
  659. static void
  660. imap4close(Mailbox *mb)
  661. {
  662. Imap *imap;
  663. imap = mb->aux;
  664. free(imap->freep);
  665. free(imap->base);
  666. free(imap->uid);
  667. if(imap->fd >= 0)
  668. close(imap->fd);
  669. free(imap);
  670. }
  671. //
  672. // open mailboxes of the form /imap/host/user
  673. //
  674. char*
  675. imap4mbox(Mailbox *mb, char *path)
  676. {
  677. char *f[10];
  678. int mustssl, nf;
  679. Imap *imap;
  680. quotefmtinstall();
  681. fmtinstall('Z', doublequote);
  682. if(strncmp(path, "/imap/", 6) != 0 && strncmp(path, "/imaps/", 7) != 0)
  683. return Enotme;
  684. mustssl = (strncmp(path, "/imaps/", 7) == 0);
  685. path = strdup(path);
  686. if(path == nil)
  687. return "out of memory";
  688. nf = getfields(path, f, 5, 0, "/");
  689. if(nf < 3){
  690. free(path);
  691. return "bad imap path syntax /imap[s]/system[/user[/mailbox]]";
  692. }
  693. imap = emalloc(sizeof(*imap));
  694. imap->fd = -1;
  695. imap->debug = debug;
  696. imap->freep = path;
  697. imap->mustssl = mustssl;
  698. imap->host = f[2];
  699. if(nf < 4)
  700. imap->user = nil;
  701. else
  702. imap->user = f[3];
  703. if(nf < 5)
  704. imap->mbox = "Inbox";
  705. else
  706. imap->mbox = f[4];
  707. imap->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude");
  708. mb->aux = imap;
  709. mb->sync = imap4sync;
  710. mb->close = imap4close;
  711. mb->ctl = imap4ctl;
  712. mb->d = emalloc(sizeof(*mb->d));
  713. //mb->fetch = imap4fetch;
  714. return nil;
  715. }
  716. //
  717. // Formatter for %"
  718. // Use double quotes to protect white space, frogs, \ and "
  719. //
  720. enum
  721. {
  722. Qok = 0,
  723. Qquote,
  724. Qbackslash,
  725. };
  726. static int
  727. needtoquote(Rune r)
  728. {
  729. if(r >= Runeself)
  730. return Qquote;
  731. if(r <= ' ')
  732. return Qquote;
  733. if(r=='\\' || r=='"')
  734. return Qbackslash;
  735. return Qok;
  736. }
  737. int
  738. doublequote(Fmt *f)
  739. {
  740. char *s, *t;
  741. int w, quotes;
  742. Rune r;
  743. s = va_arg(f->args, char*);
  744. if(s == nil || *s == '\0')
  745. return fmtstrcpy(f, "\"\"");
  746. quotes = 0;
  747. for(t=s; *t; t+=w){
  748. w = chartorune(&r, t);
  749. quotes |= needtoquote(r);
  750. }
  751. if(quotes == 0)
  752. return fmtstrcpy(f, s);
  753. fmtrune(f, '"');
  754. for(t=s; *t; t+=w){
  755. w = chartorune(&r, t);
  756. if(needtoquote(r) == Qbackslash)
  757. fmtrune(f, '\\');
  758. fmtrune(f, r);
  759. }
  760. return fmtrune(f, '"');
  761. }