imap4.c 16 KB

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