plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

imap4.c (16518B)


      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"; jpc */
     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*
     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
     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
    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
    126 strupr(char *s)
    127 {
    128 	for(; *s; s++)
    129 		if('a' <= *s && *s <= 'z')
    130 			*s += 'A'-'a';
    131 }
    132 
    133 static void
    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*
    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
    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*
    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 role=client service=imap server=%q user=%q", imap->host, imap->user);
    362 	else
    363 		up = auth_getuserpasswd(auth_getkey, "proto=pass role=client 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 /* */
    381 /* push tls onto a connection */
    382 /* */
    383 int
    384 mypushtls(int fd)
    385 {
    386 	int p[2];
    387 	char buf[10];
    388 
    389 	if(pipe(p) < 0)
    390 		return -1;
    391 
    392 	switch(fork()){
    393 	case -1:
    394 		close(p[0]);
    395 		close(p[1]);
    396 		return -1;
    397 	case 0:
    398 		close(p[1]);
    399 		dup(p[0], 0);
    400 		dup(p[0], 1);
    401 		sprint(buf, "/fd/%d", fd);
    402 		execl("/bin/tlsrelay", "tlsrelay", "-f", buf, nil);
    403 		_exits(nil);
    404 	default:
    405 		break;
    406 	}
    407 	close(fd);
    408 	close(p[0]);
    409 	return p[1];
    410 }
    411 
    412 /* */
    413 /* dial and handshake with the imap server */
    414 /* */
    415 static char*
    416 imap4dial(Imap *imap)
    417 {
    418 	char *err, *port;
    419 	uchar digest[SHA1dlen];
    420 	int sfd;
    421 	TLSconn conn;
    422 
    423 	if(imap->fd >= 0){
    424 		imap4cmd(imap, "noop");
    425 		if(isokay(imap4resp(imap)))
    426 			return nil;
    427 		close(imap->fd);
    428 		imap->fd = -1;
    429 	}
    430 
    431 	if(imap->mustssl)
    432 		port = "imaps";
    433 	else
    434 		port = "imap";
    435 
    436 	if((imap->fd = dial(netmkaddr(imap->host, "net", port), 0, 0, 0)) < 0)
    437 		return geterrstr();
    438 
    439 	if(imap->mustssl){
    440 		memset(&conn, 0, sizeof conn);
    441 		sfd = tlsClient(imap->fd, &conn);
    442 		if(sfd < 0)
    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);
    450 		}
    451 		free(conn.cert);
    452 		close(imap->fd);
    453 		imap->fd = sfd;
    454 
    455 		if(imap->debug){
    456 			char fn[128];
    457 			int fd;
    458 
    459 			snprint(fn, sizeof fn, "%s/ctl", conn.dir);
    460 			fd = open(fn, ORDWR);
    461 			if(fd < 0)
    462 				fprint(2, "opening ctl: %r\n");
    463 			if(fprint(fd, "debug") < 0)
    464 				fprint(2, "writing ctl: %r\n");
    465 			close(fd);
    466 		}
    467 	}
    468 	Binit(&imap->bin, imap->fd, OREAD);
    469 	Binit(&imap->bout, imap->fd, OWRITE);
    470 
    471 	if(err = imap4login(imap)) {
    472 		close(imap->fd);
    473 		return err;
    474 	}
    475 
    476 	return nil;
    477 }
    478 
    479 /* */
    480 /* close connection */
    481 /* */
    482 #if 0  /* jpc */
    483 static void
    484 imap4hangup(Imap *imap)
    485 {
    486 	imap4cmd(imap, "LOGOUT");
    487 	imap4resp(imap);
    488 	close(imap->fd);
    489 }
    490 #endif
    491 
    492 /* */
    493 /* download a single message */
    494 /* */
    495 static char*
    496 imap4fetch(Mailbox *mb, Message *m)
    497 {
    498 	int i;
    499 	char *p, *s, sdigest[2*SHA1dlen+1];
    500 	Imap *imap;
    501 
    502 	imap = mb->aux;
    503 
    504 	imap->size = 0;
    505 
    506 	if(!isokay(s = imap4resp(imap)))
    507 		return s;
    508 
    509 	p = imap->base;
    510 	if(p == nil)
    511 		return "did not get message body";
    512 
    513 	removecr(p);
    514 	free(m->start);
    515 	m->start = p;
    516 	m->end = p+strlen(p);
    517 	m->bend = m->rbend = m->end;
    518 	m->header = m->start;
    519 
    520 	imap->base = nil;
    521 	imap->data = nil;
    522 
    523 	parse(m, 0, mb, 1);
    524 
    525 	/* digest headers */
    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);
    530 
    531 	return nil;
    532 }
    533 
    534 /* */
    535 /* check for new messages on imap4 server */
    536 /* download new messages, mark deleted messages */
    537 /* */
    538 static char*
    539 imap4read(Imap *imap, Mailbox *mb, int doplumb)
    540 {
    541 	char *s;
    542 	int i, ignore, nnew, t;
    543 	Message *m, *next, **l;
    544 
    545 	imap4cmd(imap, "STATUS %Z (MESSAGES UIDVALIDITY)", imap->mbox);
    546 	if(!isokay(s = imap4resp(imap)))
    547 		return s;
    548 
    549 	imap->nuid = 0;
    550 	imap->uid = erealloc(imap->uid, imap->nmsg*sizeof(imap->uid[0]));
    551 	imap->muid = imap->nmsg;
    552 
    553 	if(imap->nmsg > 0){
    554 		imap4cmd(imap, "UID FETCH 1:* UID");
    555 		if(!isokay(s = imap4resp(imap)))
    556 			return s;
    557 	}
    558 
    559 	l = &mb->root->part;
    560 	for(i=0; i<imap->nuid; i++){
    561 		ignore = 0;
    562 		while(*l != nil){
    563 			if((*l)->imapuid == imap->uid[i]){
    564 				ignore = 1;
    565 				l = &(*l)->next;
    566 				break;
    567 			}else{
    568 				/* old mail, we don't have it anymore */
    569 				if(doplumb)
    570 					mailplumb(mb, *l, 1);
    571 				(*l)->inmbox = 0;
    572 				(*l)->deleted = 1;
    573 				l = &(*l)->next;
    574 			}
    575 		}
    576 		if(ignore)
    577 			continue;
    578 
    579 		/* new message */
    580 		m = newmessage(mb->root);
    581 		m->mallocd = 1;
    582 		m->inmbox = 1;
    583 		m->imapuid = imap->uid[i];
    584 
    585 		/* add to chain, will download soon */
    586 		*l = m;
    587 		l = &m->next;
    588 	}
    589 
    590 	/* whatever is left at the end of the chain is gone */
    591 	while(*l != nil){
    592 		if(doplumb)
    593 			mailplumb(mb, *l, 1);
    594 		(*l)->inmbox = 0;
    595 		(*l)->deleted = 1;
    596 		l = &(*l)->next;
    597 	}
    598 
    599 	/* download new messages */
    600 	t = imap->tag;
    601 	if(pipeline)
    602 	switch(rfork(RFPROC|RFMEM)){
    603 	case -1:
    604 		sysfatal("rfork: %r");
    605 	default:
    606 		break;
    607 	case 0:
    608 		for(m = mb->root->part; m != nil; m = m->next){
    609 			if(m->start != nil)
    610 				continue;
    611 			if(imap->debug)
    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);
    616 		}
    617 		Bflush(&imap->bout);
    618 		_exits(nil);
    619 	}
    620 
    621 	nnew = 0;
    622 	for(m=mb->root->part; m!=nil; m=next){
    623 		next = m->next;
    624 		if(m->start != nil)
    625 			continue;
    626 
    627 		if(!pipeline){
    628 			Bprint(&imap->bout, "9X%lud UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n",
    629 				(ulong)imap->tag, (ulong)m->imapuid);
    630 			Bflush(&imap->bout);
    631 		}
    632 
    633 		if(s = imap4fetch(mb, m)){
    634 			/* message disappeared?  unchain */
    635 			fprint(2, "download %lud: %s\n", (ulong)m->imapuid, s);
    636 			delmessage(mb, m);
    637 			mb->root->subname--;
    638 			continue;
    639 		}
    640 		nnew++;
    641 		if(doplumb)
    642 			mailplumb(mb, m, 0);
    643 	}
    644 	if(pipeline)
    645 		waitpid();
    646 
    647 	if(nnew || mb->vers == 0){
    648 		mb->vers++;
    649 		henter(PATH(0, Qtop), mb->name,
    650 			(Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
    651 	}
    652 	return nil;
    653 }
    654 
    655 /* */
    656 /* sync mailbox */
    657 /* */
    658 static void
    659 imap4purge(Imap *imap, Mailbox *mb)
    660 {
    661 	int ndel;
    662 	Message *m, *next;
    663 
    664 	ndel = 0;
    665 	for(m=mb->root->part; m!=nil; m=next){
    666 		next = 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))){
    671 					ndel++;
    672 					delmessage(mb, m);
    673 				}
    674 			}else
    675 				delmessage(mb, m);
    676 		}
    677 	}
    678 
    679 	if(ndel){
    680 		imap4cmd(imap, "EXPUNGE");
    681 		imap4resp(imap);
    682 	}
    683 }
    684 
    685 /* */
    686 /* connect to imap4 server, sync mailbox */
    687 /* */
    688 static char*
    689 imap4sync(Mailbox *mb, int doplumb)
    690 {
    691 	char *err;
    692 	Imap *imap;
    693 
    694 	imap = mb->aux;
    695 
    696 	if(err = imap4dial(imap)){
    697 		mb->waketime = time(0) + imap->refreshtime;
    698 		return err;
    699 	}
    700 
    701 	if((err = imap4read(imap, mb, doplumb)) == nil){
    702 		imap4purge(imap, mb);
    703 		mb->d->atime = mb->d->mtime = time(0);
    704 	}
    705 	/*
    706 	 * don't hang up; leave connection open for next time.
    707 	 */
    708 	/* imap4hangup(imap); */
    709 	mb->waketime = time(0) + imap->refreshtime;
    710 	return err;
    711 }
    712 
    713 static char Eimap4ctl[] = "bad imap4 control message";
    714 
    715 static char*
    716 imap4ctl(Mailbox *mb, int argc, char **argv)
    717 {
    718 	int n;
    719 	Imap *imap;
    720 
    721 	imap = mb->aux;
    722 	if(argc < 1)
    723 		return Eimap4ctl;
    724 
    725 	if(argc==1 && strcmp(argv[0], "debug")==0){
    726 		imap->debug = 1;
    727 		return nil;
    728 	}
    729 
    730 	if(argc==1 && strcmp(argv[0], "nodebug")==0){
    731 		imap->debug = 0;
    732 		return nil;
    733 	}
    734 
    735 	if(argc==1 && strcmp(argv[0], "thumbprint")==0){
    736 		if(imap->thumb)
    737 			freeThumbprints(imap->thumb);
    738 		imap->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude");
    739 	}
    740 	if(strcmp(argv[0], "refresh")==0){
    741 		if(argc==1){
    742 			imap->refreshtime = 60;
    743 			return nil;
    744 		}
    745 		if(argc==2){
    746 			n = atoi(argv[1]);
    747 			if(n < 15)
    748 				return Eimap4ctl;
    749 			imap->refreshtime = n;
    750 			return nil;
    751 		}
    752 	}
    753 
    754 	return Eimap4ctl;
    755 }
    756 
    757 /* */
    758 /* free extra memory associated with mb */
    759 /* */
    760 static void
    761 imap4close(Mailbox *mb)
    762 {
    763 	Imap *imap;
    764 
    765 	imap = mb->aux;
    766 	free(imap->freep);
    767 	free(imap->base);
    768 	free(imap->uid);
    769 	if(imap->fd >= 0)
    770 		close(imap->fd);
    771 	free(imap);
    772 }
    773 
    774 /* */
    775 /* open mailboxes of the form /imap/host/user */
    776 /* */
    777 char*
    778 imap4mbox(Mailbox *mb, char *path)
    779 {
    780 	char *f[10];
    781 	int mustssl, nf;
    782 	Imap *imap;
    783 
    784 	quotefmtinstall();
    785 	fmtinstall('Z', doublequote);
    786 	if(strncmp(path, "/imap/", 6) != 0 && strncmp(path, "/imaps/", 7) != 0)
    787 		return Enotme;
    788 	mustssl = (strncmp(path, "/imaps/", 7) == 0);
    789 
    790 	path = strdup(path);
    791 	if(path == nil)
    792 		return "out of memory";
    793 
    794 	nf = getfields(path, f, 5, 0, "/");
    795 	if(nf < 3){
    796 		free(path);
    797 		return "bad imap path syntax /imap[s]/system[/user[/mailbox]]";
    798 	}
    799 
    800 	imap = emalloc(sizeof(*imap));
    801 	imap->fd = -1;
    802 	imap->debug = debug;
    803 	imap->freep = path;
    804 	imap->mustssl = mustssl;
    805 	imap->host = f[2];
    806 	if(nf < 4)
    807 		imap->user = nil;
    808 	else
    809 		imap->user = f[3];
    810 	if(nf < 5)
    811 		imap->mbox = "Inbox";
    812 	else
    813 		imap->mbox = f[4];
    814 	imap->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude");
    815 
    816 	mb->aux = imap;
    817 	mb->sync = imap4sync;
    818 	mb->close = imap4close;
    819 	mb->ctl = imap4ctl;
    820 	mb->d = emalloc(sizeof(*mb->d));
    821 	/*mb->fetch = imap4fetch; */
    822 
    823 	return nil;
    824 }
    825 
    826 /* */
    827 /* Formatter for %" */
    828 /* Use double quotes to protect white space, frogs, \ and " */
    829 /* */
    830 enum
    831 {
    832 	Qok = 0,
    833 	Qquote,
    834 	Qbackslash
    835 };
    836 
    837 static int
    838 needtoquote(Rune r)
    839 {
    840 	if(r >= Runeself)
    841 		return Qquote;
    842 	if(r <= ' ')
    843 		return Qquote;
    844 	if(r=='\\' || r=='"')
    845 		return Qbackslash;
    846 	return Qok;
    847 }
    848 
    849 int
    850 doublequote(Fmt *f)
    851 {
    852 	char *s, *t;
    853 	int w, quotes;
    854 	Rune r;
    855 
    856 	s = va_arg(f->args, char*);
    857 	if(s == nil || *s == '\0')
    858 		return fmtstrcpy(f, "\"\"");
    859 
    860 	quotes = 0;
    861 	for(t=s; *t; t+=w){
    862 		w = chartorune(&r, t);
    863 		quotes |= needtoquote(r);
    864 	}
    865 	if(quotes == 0)
    866 		return fmtstrcpy(f, s);
    867 
    868 	fmtrune(f, '"');
    869 	for(t=s; *t; t+=w){
    870 		w = chartorune(&r, t);
    871 		if(needtoquote(r) == Qbackslash)
    872 			fmtrune(f, '\\');
    873 		fmtrune(f, r);
    874 	}
    875 	return fmtrune(f, '"');
    876 }