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