Subversion Repositories planix.SVN

Rev

Rev 33 | 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 type "M" uchar*
9
#pragma varargck argpos pop3cmd 2
10
 
11
typedef struct Pop Pop;
12
struct Pop {
13
	char *freep;	// free this to free the strings below
14
 
15
	char *host;
16
	char *user;
17
	char *port;
18
 
19
	int ppop;
20
	int refreshtime;
21
	int debug;
22
	int pipeline;
23
	int encrypted;
24
	int needtls;
25
	int notls;
26
	int needssl;
27
 
28
	// open network connection
29
	Biobuf bin;
30
	Biobuf bout;
31
	int fd;
32
	char *lastline;	// from Brdstr
33
 
34
	Thumbprint *thumb;
35
};
36
 
37
char*
38
geterrstr(void)
39
{
40
	static char err[Errlen];
41
 
42
	err[0] = '\0';
43
	errstr(err, sizeof(err));
44
	return err;
45
}
46
 
47
//
48
// get pop3 response line , without worrying
49
// about multiline responses; the clients
50
// will deal with that.
51
//
52
static int
53
isokay(char *s)
54
{
55
	return s!=nil && strncmp(s, "+OK", 3)==0;
56
}
57
 
58
static void
59
pop3cmd(Pop *pop, char *fmt, ...)
60
{
61
	char buf[128], *p;
62
	va_list va;
63
 
64
	va_start(va, fmt);
65
	vseprint(buf, buf+sizeof(buf), fmt, va);
66
	va_end(va);
67
 
68
	p = buf+strlen(buf);
69
	if(p > (buf+sizeof(buf)-3))
70
		sysfatal("pop3 command too long");
71
 
72
	if(pop->debug)
73
		fprint(2, "<- %s\n", buf);
74
	strcpy(p, "\r\n");
75
	Bwrite(&pop->bout, buf, strlen(buf));
76
	Bflush(&pop->bout);
77
}
78
 
79
static char*
80
pop3resp(Pop *pop)
81
{
82
	char *s;
83
	char *p;
84
 
85
	alarm(60*1000);
86
	if((s = Brdstr(&pop->bin, '\n', 0)) == nil){
87
		close(pop->fd);
88
		pop->fd = -1;
89
		alarm(0);
90
		return "unexpected eof";
91
	}
92
	alarm(0);
93
 
94
	p = s+strlen(s)-1;
95
	while(p >= s && (*p == '\r' || *p == '\n'))
96
		*p-- = '\0';
97
 
98
	if(pop->debug)
99
		fprint(2, "-> %s\n", s);
100
	free(pop->lastline);
101
	pop->lastline = s;
102
	return s;
103
}
104
 
105
static int
106
pop3log(char *fmt, ...)
107
{
108
	va_list ap;
109
 
110
	va_start(ap,fmt);
111
	syslog(0, "/sys/log/pop3", fmt, ap);
112
	va_end(ap);
113
	return 0;
114
}
115
 
116
static char*
117
pop3pushtls(Pop *pop)
118
{
119
	int fd;
120
	uchar digest[SHA1dlen];
121
	TLSconn conn;
122
 
123
	memset(&conn, 0, sizeof conn);
124
	// conn.trace = pop3log;
125
	fd = tlsClient(pop->fd, &conn);
126
	if(fd < 0)
127
		return "tls error";
128
	if(conn.cert==nil || conn.certlen <= 0){
129
		close(fd);
130
		return "server did not provide TLS certificate";
131
	}
132
	sha1(conn.cert, conn.certlen, digest, nil);
133
	/*
134
	 * don't do this any more.  our local it people are rotating their
135
	 * certificates faster than we can keep up.
136
	 */
33 7u83 137
	if(0 && (!pop->thumb || !okThumbprint(digest, SHA1dlen, pop->thumb))){
2 - 138
		fmtinstall('H', encodefmt);
139
		close(fd);
140
		free(conn.cert);
141
		fprint(2, "upas/fs pop3: server certificate %.*H not recognized\n", SHA1dlen, digest);
142
		return "bad server certificate";
143
	}
144
	free(conn.cert);
145
	close(pop->fd);
146
	pop->fd = fd;
147
	pop->encrypted = 1;
148
	Binit(&pop->bin, pop->fd, OREAD);
149
	Binit(&pop->bout, pop->fd, OWRITE);
150
	return nil;
151
}
152
 
153
//
154
// get capability list, possibly start tls
155
//
156
static char*
157
pop3capa(Pop *pop)
158
{
159
	char *s;
160
	int hastls;
161
 
162
	pop3cmd(pop, "CAPA");
163
	if(!isokay(pop3resp(pop)))
164
		return nil;
165
 
166
	hastls = 0;
167
	for(;;){
168
		s = pop3resp(pop);
169
		if(strcmp(s, ".") == 0 || strcmp(s, "unexpected eof") == 0)
170
			break;
171
		if(strcmp(s, "STLS") == 0)
172
			hastls = 1;
173
		if(strcmp(s, "PIPELINING") == 0)
174
			pop->pipeline = 1;
175
		if(strcmp(s, "EXPIRE 0") == 0)
176
			return "server does not allow mail to be left on server";
177
	}
178
 
179
	if(hastls && !pop->notls){
180
		pop3cmd(pop, "STLS");
181
		if(!isokay(s = pop3resp(pop)))
182
			return s;
183
		if((s = pop3pushtls(pop)) != nil)
184
			return s;
185
	}
186
	return nil;
187
}
188
 
189
//
190
// log in using APOP if possible, password if allowed by user
191
//
192
static char*
193
pop3login(Pop *pop)
194
{
195
	int n;
196
	char *s, *p, *q;
197
	char ubuf[128], user[128];
198
	char buf[500];
199
	UserPasswd *up;
200
 
201
	s = pop3resp(pop);
202
	if(!isokay(s))
203
		return "error in initial handshake";
204
 
205
	if(pop->user)
206
		snprint(ubuf, sizeof ubuf, " user=%q", pop->user);
207
	else
208
		ubuf[0] = '\0';
209
 
210
	// look for apop banner
211
	if(pop->ppop==0 && (p = strchr(s, '<')) && (q = strchr(p+1, '>'))) {
212
		*++q = '\0';
213
		if((n=auth_respond(p, q-p, user, sizeof user, buf, sizeof buf, auth_getkey, "proto=apop role=client server=%q%s",
214
			pop->host, ubuf)) < 0)
215
			return "factotum failed";
216
		if(user[0]=='\0')
217
			return "factotum did not return a user name";
218
 
219
		if(s = pop3capa(pop))
220
			return s;
221
 
222
		pop3cmd(pop, "APOP %s %.*s", user, n, buf);
223
		if(!isokay(s = pop3resp(pop)))
224
			return s;
225
 
226
		return nil;
227
	} else {
228
		if(pop->ppop == 0)
229
			return "no APOP hdr from server";
230
 
231
		if(s = pop3capa(pop))
232
			return s;
233
 
234
		if(pop->needtls && !pop->encrypted)
235
			return "could not negotiate TLS";
236
 
237
		up = auth_getuserpasswd(auth_getkey, "proto=pass service=pop dom=%q%s",
238
			pop->host, ubuf);
239
		if(up == nil)
240
			return "no usable keys found";
241
 
242
		pop3cmd(pop, "USER %s", up->user);
243
		if(!isokay(s = pop3resp(pop))){
244
			free(up);
245
			return s;
246
		}
247
		pop3cmd(pop, "PASS %s", up->passwd);
248
		free(up);
249
		if(!isokay(s = pop3resp(pop)))
250
			return s;
251
 
252
		return nil;
253
	}
254
}
255
 
256
//
257
// dial and handshake with pop server
258
//
259
static char*
260
pop3dial(Pop *pop)
261
{
262
	char *err;
263
 
264
	if((pop->fd = dial(netmkaddr(pop->host, "net", pop->needssl ? "pop3s" : "pop3"), 0, 0, 0)) < 0)
265
		return geterrstr();
266
 
267
	if(pop->needssl){
268
		if((err = pop3pushtls(pop)) != nil)
269
			return err;
270
	}else{
271
		Binit(&pop->bin, pop->fd, OREAD);
272
		Binit(&pop->bout, pop->fd, OWRITE);
273
	}
274
 
275
	if(err = pop3login(pop)) {
276
		close(pop->fd);
277
		return err;
278
	}
279
 
280
	return nil;
281
}
282
 
283
//
284
// close connection
285
//
286
static void
287
pop3hangup(Pop *pop)
288
{
289
	pop3cmd(pop, "QUIT");
290
	pop3resp(pop);
291
	close(pop->fd);
292
}
293
 
294
//
295
// download a single message
296
//
297
static char*
298
pop3download(Pop *pop, Message *m)
299
{
300
	char *s, *f[3], *wp, *ep;
301
	char sdigest[SHA1dlen*2+1];
302
	int i, l, sz;
303
 
304
	if(!pop->pipeline)
305
		pop3cmd(pop, "LIST %d", m->mesgno);
306
	if(!isokay(s = pop3resp(pop)))
307
		return s;
308
 
309
	if(tokenize(s, f, 3) != 3)
310
		return "syntax error in LIST response";
311
 
312
	if(atoi(f[1]) != m->mesgno)
313
		return "out of sync with pop3 server";
314
 
315
	sz = atoi(f[2])+200;	/* 200 because the plan9 pop3 server lies */
316
	if(sz == 0)
317
		return "invalid size in LIST response";
318
 
319
	m->start = wp = emalloc(sz+1);
320
	ep = wp+sz;
321
 
322
	if(!pop->pipeline)
323
		pop3cmd(pop, "RETR %d", m->mesgno);
324
	if(!isokay(s = pop3resp(pop))) {
325
		m->start = nil;
326
		free(wp);
327
		return s;
328
	}
329
 
330
	s = nil;
331
	while(wp <= ep) {
332
		s = pop3resp(pop);
333
		if(strcmp(s, "unexpected eof") == 0) {
334
			free(m->start);
335
			m->start = nil;
336
			return "unexpected end of conversation";
337
		}
338
		if(strcmp(s, ".") == 0)
339
			break;
340
 
341
		l = strlen(s)+1;
342
		if(s[0] == '.') {
343
			s++;
344
			l--;
345
		}
346
		/*
347
		 * grow by 10%/200bytes - some servers
348
		 *  lie about message sizes
349
		 */
350
		if(wp+l > ep) {
351
			int pos = wp - m->start;
352
			sz += ((sz / 10) < 200)? 200: sz/10;
353
			m->start = erealloc(m->start, sz+1);
354
			wp = m->start+pos;
355
			ep = m->start+sz;
356
		}
357
		memmove(wp, s, l-1);
358
		wp[l-1] = '\n';
359
		wp += l;
360
	}
361
 
362
	if(s == nil || strcmp(s, ".") != 0)
363
		return "out of sync with pop3 server";
364
 
365
	m->end = wp;
366
 
367
	// make sure there's a trailing null
368
	// (helps in body searches)
369
	*m->end = 0;
370
	m->bend = m->rbend = m->end;
371
	m->header = m->start;
372
 
373
	// digest message
374
	sha1((uchar*)m->start, m->end - m->start, m->digest, nil);
375
	for(i = 0; i < SHA1dlen; i++)
376
		sprint(sdigest+2*i, "%2.2ux", m->digest[i]);
377
	m->sdigest = s_copy(sdigest);
378
 
379
	return nil;
380
}
381
 
382
//
383
// check for new messages on pop server
384
// UIDL is not required by RFC 1939, but 
385
// netscape requires it, so almost every server supports it.
386
// we'll use it to make our lives easier.
387
//
388
static char*
389
pop3read(Pop *pop, Mailbox *mb, int doplumb)
390
{
391
	char *s, *p, *uidl, *f[2];
392
	int mesgno, ignore, nnew;
393
	Message *m, *next, **l;
394
 
395
	// Some POP servers disallow UIDL if the maildrop is empty.
396
	pop3cmd(pop, "STAT");
397
	if(!isokay(s = pop3resp(pop)))
398
		return s;
399
 
400
	// fetch message listing; note messages to grab
401
	l = &mb->root->part;
402
	if(strncmp(s, "+OK 0 ", 6) != 0) {
403
		pop3cmd(pop, "UIDL");
404
		if(!isokay(s = pop3resp(pop)))
405
			return s;
406
 
407
		for(;;){
408
			p = pop3resp(pop);
409
			if(strcmp(p, ".") == 0 || strcmp(p, "unexpected eof") == 0)
410
				break;
411
 
412
			if(tokenize(p, f, 2) != 2)
413
				continue;
414
 
415
			mesgno = atoi(f[0]);
416
			uidl = f[1];
417
			if(strlen(uidl) > 75)	// RFC 1939 says 70 characters max
418
				continue;
419
 
420
			ignore = 0;
421
			while(*l != nil) {
422
				if(strcmp((*l)->uidl, uidl) == 0) {
423
					// matches mail we already have, note mesgno for deletion
424
					(*l)->mesgno = mesgno;
425
					ignore = 1;
426
					l = &(*l)->next;
427
					break;
428
				} else {
429
					// old mail no longer in box mark deleted
430
					if(doplumb)
431
						mailplumb(mb, *l, 1);
432
					(*l)->inmbox = 0;
433
					(*l)->deleted = 1;
434
					l = &(*l)->next;
435
				}
436
			}
437
			if(ignore)
438
				continue;
439
 
440
			m = newmessage(mb->root);
441
			m->mallocd = 1;
442
			m->inmbox = 1;
443
			m->mesgno = mesgno;
444
			strcpy(m->uidl, uidl);
445
 
446
			// chain in; will fill in message later
447
			*l = m;
448
			l = &m->next;
449
		}
450
	}
451
 
452
	// whatever is left has been removed from the mbox, mark as deleted
453
	while(*l != nil) {
454
		if(doplumb)
455
			mailplumb(mb, *l, 1);
456
		(*l)->inmbox = 0;
457
		(*l)->deleted = 1;
458
		l = &(*l)->next;
459
	}
460
 
461
	// download new messages
462
	nnew = 0;
463
	if(pop->pipeline){
464
		switch(rfork(RFPROC|RFMEM)){
465
		case -1:
466
			fprint(2, "rfork: %r\n");
467
			pop->pipeline = 0;
468
 
469
		default:
470
			break;
471
 
472
		case 0:
473
			for(m = mb->root->part; m != nil; m = m->next){
474
				if(m->start != nil)
475
					continue;
476
				Bprint(&pop->bout, "LIST %d\r\nRETR %d\r\n", m->mesgno, m->mesgno);
477
			}
478
			Bflush(&pop->bout);
479
			_exits(nil);
480
		}
481
	}
482
 
483
	for(m = mb->root->part; m != nil; m = next) {
484
		next = m->next;
485
 
486
		if(m->start != nil)
487
			continue;
488
 
489
		if(s = pop3download(pop, m)) {
490
			// message disappeared? unchain
491
			fprint(2, "download %d: %s\n", m->mesgno, s);
492
			delmessage(mb, m);
493
			mb->root->subname--;
494
			continue;
495
		}
496
		nnew++;
497
		parse(m, 0, mb, 1);
498
 
499
		if(doplumb)
500
			mailplumb(mb, m, 0);
501
	}
502
	if(pop->pipeline)
503
		waitpid();
504
 
505
	if(nnew || mb->vers == 0) {
506
		mb->vers++;
507
		henter(PATH(0, Qtop), mb->name,
508
			(Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
509
	}
510
 
511
	return nil;	
512
}
513
 
514
//
515
// delete marked messages
516
//
517
static void
518
pop3purge(Pop *pop, Mailbox *mb)
519
{
520
	Message *m, *next;
521
 
522
	if(pop->pipeline){
523
		switch(rfork(RFPROC|RFMEM)){
524
		case -1:
525
			fprint(2, "rfork: %r\n");
526
			pop->pipeline = 0;
527
 
528
		default:
529
			break;
530
 
531
		case 0:
532
			for(m = mb->root->part; m != nil; m = next){
533
				next = m->next;
534
				if(m->deleted && m->refs == 0){
535
					if(m->inmbox)
536
						Bprint(&pop->bout, "DELE %d\r\n", m->mesgno);
537
				}
538
			}
539
			Bflush(&pop->bout);
540
			_exits(nil);
541
		}
542
	}
543
	for(m = mb->root->part; m != nil; m = next) {
544
		next = m->next;
545
		if(m->deleted && m->refs == 0) {
546
			if(m->inmbox) {
547
				if(!pop->pipeline)
548
					pop3cmd(pop, "DELE %d", m->mesgno);
549
				if(isokay(pop3resp(pop)))
550
					delmessage(mb, m);
551
			} else
552
				delmessage(mb, m);
553
		}
554
	}
555
}
556
 
557
 
558
// connect to pop3 server, sync mailbox
559
static char*
560
pop3sync(Mailbox *mb, int doplumb)
561
{
562
	char *err;
563
	Pop *pop;
564
 
565
	pop = mb->aux;
566
 
567
	if(err = pop3dial(pop)) {
568
		mb->waketime = time(0) + pop->refreshtime;
569
		return err;
570
	}
571
 
572
	if((err = pop3read(pop, mb, doplumb)) == nil){
573
		pop3purge(pop, mb);
574
		mb->d->atime = mb->d->mtime = time(0);
575
	}
576
	pop3hangup(pop);
577
	mb->waketime = time(0) + pop->refreshtime;
578
	return err;
579
}
580
 
581
static char Epop3ctl[] = "bad pop3 control message";
582
 
583
static char*
584
pop3ctl(Mailbox *mb, int argc, char **argv)
585
{
586
	int n;
587
	Pop *pop;
588
 
589
	pop = mb->aux;
590
	if(argc < 1)
591
		return Epop3ctl;
592
 
593
	if(argc==1 && strcmp(argv[0], "debug")==0){
594
		pop->debug = 1;
595
		return nil;
596
	}
597
 
598
	if(argc==1 && strcmp(argv[0], "nodebug")==0){
599
		pop->debug = 0;
600
		return nil;
601
	}
602
 
603
	if(argc==1 && strcmp(argv[0], "thumbprint")==0){
604
		if(pop->thumb)
605
			freeThumbprints(pop->thumb);
33 7u83 606
		pop->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude","x509");
2 - 607
	}
608
	if(strcmp(argv[0], "refresh")==0){
609
		if(argc==1){
610
			pop->refreshtime = 60;
611
			return nil;
612
		}
613
		if(argc==2){
614
			n = atoi(argv[1]);
615
			if(n < 15)
616
				return Epop3ctl;
617
			pop->refreshtime = n;
618
			return nil;
619
		}
620
	}
621
 
622
	return Epop3ctl;
623
}
624
 
625
// free extra memory associated with mb
626
static void
627
pop3close(Mailbox *mb)
628
{
629
	Pop *pop;
630
 
631
	pop = mb->aux;
632
	free(pop->freep);
633
	free(pop);
634
}
635
 
636
//
637
// open mailboxes of the form /pop/host/user or /apop/host/user
638
//
639
char*
640
pop3mbox(Mailbox *mb, char *path)
641
{
642
	char *f[10];
643
	int nf, apop, ppop, popssl, apopssl, apoptls, popnotls, apopnotls, poptls;
644
	Pop *pop;
645
 
646
	quotefmtinstall();
647
	popssl = strncmp(path, "/pops/", 6) == 0;
648
	apopssl = strncmp(path, "/apops/", 7) == 0;
649
	poptls = strncmp(path, "/poptls/", 8) == 0;
650
	popnotls = strncmp(path, "/popnotls/", 10) == 0;
651
	ppop = popssl || poptls || popnotls || strncmp(path, "/pop/", 5) == 0;
652
	apoptls = strncmp(path, "/apoptls/", 9) == 0;
653
	apopnotls = strncmp(path, "/apopnotls/", 11) == 0;
654
	apop = apopssl || apoptls || apopnotls || strncmp(path, "/apop/", 6) == 0;
655
 
656
	if(!ppop && !apop)
657
		return Enotme;
658
 
659
	path = strdup(path);
660
	if(path == nil)
661
		return "out of memory";
662
 
663
	nf = getfields(path, f, nelem(f), 0, "/");
664
	if(nf != 3 && nf != 4) {
665
		free(path);
666
		return "bad pop3 path syntax /[a]pop[tls|ssl]/system[/user]";
667
	}
668
 
669
	pop = emalloc(sizeof(*pop));
670
	pop->freep = path;
671
	pop->host = f[2];
672
	if(nf < 4)
673
		pop->user = nil;
674
	else
675
		pop->user = f[3];
676
	pop->ppop = ppop;
677
	pop->needssl = popssl || apopssl;
678
	pop->needtls = poptls || apoptls;
679
	pop->refreshtime = 60;
680
	pop->notls = popnotls || apopnotls;
33 7u83 681
	pop->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude","x509");
2 - 682
 
683
	mb->aux = pop;
684
	mb->sync = pop3sync;
685
	mb->close = pop3close;
686
	mb->ctl = pop3ctl;
687
	mb->d = emalloc(sizeof(*mb->d));
688
 
689
	return nil;
690
}
691