8 #pragma varargck argpos imap4cmd 2
9 #pragma varargck type "Z" char*
11 int doublequote(Fmt*);
14 /* static char Eio[] = "i/o error"; jpc */
16 typedef struct Imap Imap;
18 char *freep; /* free this to free the strings below */
41 /* open network connection */
60 /* send imap4 command */
63 imap4cmd(Imap *imap, char *fmt, ...)
69 p = buf+sprint(buf, "9X%lud ", imap->tag);
70 vseprint(p, buf+sizeof(buf), fmt, va);
74 if(p > (buf+sizeof(buf)-3))
75 sysfatal("imap4 command too long");
78 fprint(2, "-> %s\n", buf);
80 Bwrite(&imap->bout, buf, strlen(buf));
95 static char *verblist[] = {
111 if(q = strchr(verb, ' '))
114 for(i=0; i<nelem(verblist); i++)
115 if(verblist[i] && strcmp(verblist[i], verb)==0){
129 if('a' <= *s && *s <= 'z')
134 imapgrow(Imap *imap, int n)
138 if(imap->data == nil){
139 imap->base = emalloc(n+1);
140 imap->data = imap->base;
144 /* friggin microsoft - reallocate */
145 i = imap->data - imap->base;
146 imap->base = erealloc(imap->base, i+n+1);
147 imap->data = imap->base + i;
154 /* get imap4 response line. there might be various */
155 /* data or other informational lines mixed in. */
158 imap4resp(Imap *imap)
160 char *line, *p, *ep, *op, *q, *r, *en, *verb;
162 static char error[256];
164 while(p = Brdline(&imap->bin, '\n')){
165 ep = p+Blinelen(&imap->bin);
166 while(ep > p && (ep[-1]=='\n' || ep[-1]=='\r'))
170 fprint(2, "<- %s\n", p);
176 fprint(2, "unexpected: %s\n", p);
179 /* ``unsolicited'' information; everything happens here. */
185 n = strtol(p, &p, 10);
190 if(p = strchr(verb, ' '))
193 p = verb+strlen(verb);
195 switch(verbcode(verb)){
199 /* human readable text at p; */
202 /* early disconnect */
203 /* human readable text at p; */
211 /* * STATUS Inbox (MESSAGES 2 UIDVALIDITY 960164964) */
213 if(q = strstr(p, "MESSAGES"))
214 imap->nmsg = atoi(q+8);
215 if(q = strstr(p, "UIDVALIDITY"))
216 imap->validity = strtoul(q+11, 0, 10);
220 /* * 1 FETCH (uid 8889 RFC822.SIZE 3031 body[] {3031} */
221 /* <3031 bytes of data> */
223 if(strstr(p, "RFC822.SIZE") && strstr(p, "BODY[]")){
224 if((q = strchr(p, '{'))
225 && (n=strtol(q+1, &en, 0), *en=='}')){
226 if(imap->data == nil || n >= imap->size)
228 if((i = Bread(&imap->bin, imap->data, n)) != n){
229 snprint(error, sizeof error,
230 "short read %d != %d: %r\n",
235 fprint(2, "<- read %d bytes\n", n);
236 imap->data[n] = '\0';
238 fprint(2, "<- %s\n", imap->data);
241 p = Brdline(&imap->bin, '\n');
243 fprint(2, "<- ignoring %.*s\n",
244 Blinelen(&imap->bin), p);
245 }else if((q = strchr(p, '"')) && (r = strchr(q+1, '"'))){
249 if(imap->data == nil || n >= imap->size)
251 memmove(imap->data, q, n);
252 imap->data[n] = '\0';
256 return "confused about FETCH response";
260 /* * 1 FETCH (UID 1 RFC822.SIZE 511) */
261 if(q=strstr(p, "RFC822.SIZE")){
262 imap->size = atoi(q+11);
266 /* * 1 FETCH (UID 1 RFC822.HEADER {496} */
267 /* <496 bytes of data> */
269 /* * 1 FETCH (UID 1 RFC822.HEADER "data") */
270 if(strstr(p, "RFC822.HEADER") || strstr(p, "RFC822.TEXT")){
271 if((q = strchr(p, '{'))
272 && (n=strtol(q+1, &en, 0), *en=='}')){
273 if(imap->data == nil || n >= imap->size)
275 if((i = Bread(&imap->bin, imap->data, n)) != n){
276 snprint(error, sizeof error,
277 "short read %d != %d: %r\n",
282 fprint(2, "<- read %d bytes\n", n);
283 imap->data[n] = '\0';
285 fprint(2, "<- %s\n", imap->data);
288 p = Brdline(&imap->bin, '\n');
290 fprint(2, "<- ignoring %.*s\n",
291 Blinelen(&imap->bin), p);
292 }else if((q = strchr(p, '"')) && (r = strchr(q+1, '"'))){
296 if(imap->data == nil || n >= imap->size)
298 memmove(imap->data, q, n);
299 imap->data[n] = '\0';
303 return "confused about FETCH response";
307 /* * 1 FETCH (UID 1) */
308 /* * 2 FETCH (UID 6) */
309 if(q = strstr(p, "UID")){
310 if(imap->nuid < imap->muid)
311 imap->uid[imap->nuid++] = ((vlong)imap->validity<<32)|strtoul(q+3, nil, 10);
320 case '9': /* response to our message */
322 if(p[1]=='X' && strtoul(p+2, &p, 10)==imap->tag){
328 fprint(2, "expected %lud; got %s\n", imap->tag, op);
332 if(imap->debug || *p)
333 fprint(2, "unexpected line: %s\n", p);
336 snprint(error, sizeof error, "i/o error: %r\n");
343 return strncmp(resp, "OK", 2)==0;
347 /* log in to IMAP4 server, select mailbox, no SSL at the moment */
350 imap4login(Imap *imap)
358 return "error in initial IMAP handshake";
360 if(imap->user != nil)
361 up = auth_getuserpasswd(auth_getkey, "proto=pass role=client service=imap server=%q user=%q", imap->host, imap->user);
363 up = auth_getuserpasswd(auth_getkey, "proto=pass role=client service=imap server=%q", imap->host);
365 return "cannot find IMAP password";
368 imap4cmd(imap, "LOGIN %Z %Z", up->user, up->passwd);
370 if(!isokay(s = imap4resp(imap)))
373 imap4cmd(imap, "SELECT %Z", imap->mbox);
374 if(!isokay(s = imap4resp(imap)))
381 /* push tls onto a connection */
401 sprint(buf, "/fd/%d", fd);
402 execl("/bin/tlsrelay", "tlsrelay", "-f", buf, nil);
413 /* dial and handshake with the imap server */
416 imap4dial(Imap *imap)
419 uchar digest[SHA1dlen];
424 imap4cmd(imap, "noop");
425 if(isokay(imap4resp(imap)))
436 if((imap->fd = dial(netmkaddr(imap->host, "net", port), 0, 0, 0)) < 0)
440 memset(&conn, 0, sizeof conn);
441 sfd = tlsClient(imap->fd, &conn);
443 sysfatal("tlsClient: %r");
444 if(conn.cert==nil || conn.certlen <= 0)
445 sysfatal("server did not provide TLS certificate");
446 sha1(conn.cert, conn.certlen, digest, nil);
447 if(!imap->thumb || !okThumbprint(digest, imap->thumb)){
448 fmtinstall('H', encodefmt);
449 sysfatal("server certificate %.*H not recognized", SHA1dlen, digest);
459 snprint(fn, sizeof fn, "%s/ctl", conn.dir);
460 fd = open(fn, ORDWR);
462 fprint(2, "opening ctl: %r\n");
463 if(fprint(fd, "debug") < 0)
464 fprint(2, "writing ctl: %r\n");
468 Binit(&imap->bin, imap->fd, OREAD);
469 Binit(&imap->bout, imap->fd, OWRITE);
471 if(err = imap4login(imap)) {
480 /* close connection */
484 imap4hangup(Imap *imap)
486 imap4cmd(imap, "LOGOUT");
493 /* download a single message */
496 imap4fetch(Mailbox *mb, Message *m)
499 char *p, *s, sdigest[2*SHA1dlen+1];
506 if(!isokay(s = imap4resp(imap)))
511 return "did not get message body";
516 m->end = p+strlen(p);
517 m->bend = m->rbend = m->end;
518 m->header = m->start;
526 sha1((uchar*)m->start, m->end - m->start, m->digest, nil);
527 for(i = 0; i < SHA1dlen; i++)
528 sprint(sdigest+2*i, "%2.2ux", m->digest[i]);
529 m->sdigest = s_copy(sdigest);
535 /* check for new messages on imap4 server */
536 /* download new messages, mark deleted messages */
539 imap4read(Imap *imap, Mailbox *mb, int doplumb)
542 int i, ignore, nnew, t;
543 Message *m, *next, **l;
545 imap4cmd(imap, "STATUS %Z (MESSAGES UIDVALIDITY)", imap->mbox);
546 if(!isokay(s = imap4resp(imap)))
550 imap->uid = erealloc(imap->uid, imap->nmsg*sizeof(imap->uid[0]));
551 imap->muid = imap->nmsg;
554 imap4cmd(imap, "UID FETCH 1:* UID");
555 if(!isokay(s = imap4resp(imap)))
560 for(i=0; i<imap->nuid; i++){
563 if((*l)->imapuid == imap->uid[i]){
568 /* old mail, we don't have it anymore */
570 mailplumb(mb, *l, 1);
580 m = newmessage(mb->root);
583 m->imapuid = imap->uid[i];
585 /* add to chain, will download soon */
590 /* whatever is left at the end of the chain is gone */
593 mailplumb(mb, *l, 1);
599 /* download new messages */
602 switch(rfork(RFPROC|RFMEM)){
604 sysfatal("rfork: %r");
608 for(m = mb->root->part; m != nil; m = m->next){
612 fprint(2, "9X%d UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n",
613 t, (ulong)m->imapuid);
614 Bprint(&imap->bout, "9X%d UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n",
615 t++, (ulong)m->imapuid);
622 for(m=mb->root->part; m!=nil; m=next){
628 Bprint(&imap->bout, "9X%lud UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n",
629 (ulong)imap->tag, (ulong)m->imapuid);
633 if(s = imap4fetch(mb, m)){
634 /* message disappeared? unchain */
635 fprint(2, "download %lud: %s\n", (ulong)m->imapuid, s);
647 if(nnew || mb->vers == 0){
649 henter(PATH(0, Qtop), mb->name,
650 (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
659 imap4purge(Imap *imap, Mailbox *mb)
665 for(m=mb->root->part; m!=nil; m=next){
667 if(m->deleted && m->refs==0){
668 if(m->inmbox && (ulong)(m->imapuid>>32)==imap->validity){
669 imap4cmd(imap, "UID STORE %lud +FLAGS (\\Deleted)", (ulong)m->imapuid);
670 if(isokay(imap4resp(imap))){
680 imap4cmd(imap, "EXPUNGE");
686 /* connect to imap4 server, sync mailbox */
689 imap4sync(Mailbox *mb, int doplumb)
696 if(err = imap4dial(imap)){
697 mb->waketime = time(0) + imap->refreshtime;
701 if((err = imap4read(imap, mb, doplumb)) == nil){
702 imap4purge(imap, mb);
703 mb->d->atime = mb->d->mtime = time(0);
706 * don't hang up; leave connection open for next time.
708 /* imap4hangup(imap); */
709 mb->waketime = time(0) + imap->refreshtime;
713 static char Eimap4ctl[] = "bad imap4 control message";
716 imap4ctl(Mailbox *mb, int argc, char **argv)
725 if(argc==1 && strcmp(argv[0], "debug")==0){
730 if(argc==1 && strcmp(argv[0], "nodebug")==0){
735 if(argc==1 && strcmp(argv[0], "thumbprint")==0){
737 freeThumbprints(imap->thumb);
738 imap->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude");
740 if(strcmp(argv[0], "refresh")==0){
742 imap->refreshtime = 60;
749 imap->refreshtime = n;
758 /* free extra memory associated with mb */
761 imap4close(Mailbox *mb)
775 /* open mailboxes of the form /imap/host/user */
778 imap4mbox(Mailbox *mb, char *path)
785 fmtinstall('Z', doublequote);
786 if(strncmp(path, "/imap/", 6) != 0 && strncmp(path, "/imaps/", 7) != 0)
788 mustssl = (strncmp(path, "/imaps/", 7) == 0);
792 return "out of memory";
794 nf = getfields(path, f, 5, 0, "/");
797 return "bad imap path syntax /imap[s]/system[/user[/mailbox]]";
800 imap = emalloc(sizeof(*imap));
804 imap->mustssl = mustssl;
811 imap->mbox = "Inbox";
814 imap->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude");
817 mb->sync = imap4sync;
818 mb->close = imap4close;
820 mb->d = emalloc(sizeof(*mb->d));
821 /*mb->fetch = imap4fetch; */
827 /* Formatter for %" */
828 /* Use double quotes to protect white space, frogs, \ and " */
844 if(r=='\\' || r=='"')
856 s = va_arg(f->args, char*);
857 if(s == nil || *s == '\0')
858 return fmtstrcpy(f, "\"\"");
862 w = chartorune(&r, t);
863 quotes |= needtoquote(r);
866 return fmtstrcpy(f, s);
870 w = chartorune(&r, t);
871 if(needtoquote(r) == Qbackslash)
875 return fmtrune(f, '"');