imap4.c 18 KB

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