Subversion Repositories planix.SVN

Rev

Rev 2 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 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*
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 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*
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
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
	 */
33 7u83 419
	if(0 && (!imap->thumb || !okThumbprint(digest, SHA1dlen, imap->thumb))){
2 - 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*
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
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*
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*
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
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*
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*
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);
33 7u83 743
		imap->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude","x509");
2 - 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
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*
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];
33 7u83 819
	imap->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude","x509");
2 - 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
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
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
}