Subversion Repositories planix.SVN

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 1
#include "common.h"
2
#include <ctype.h>
3
#include <auth.h>
4
#include <libsec.h>
5
 
6
typedef struct Cmd Cmd;
7
struct Cmd
8
{
9
	char *name;
10
	int needauth;
11
	int (*f)(char*);
12
};
13
 
14
static void hello(void);
15
static int apopcmd(char*);
16
static int capacmd(char*);
17
static int delecmd(char*);
18
static int listcmd(char*);
19
static int noopcmd(char*);
20
static int passcmd(char*);
21
static int quitcmd(char*);
22
static int rsetcmd(char*);
23
static int retrcmd(char*);
24
static int statcmd(char*);
25
static int stlscmd(char*);
26
static int topcmd(char*);
27
static int synccmd(char*);
28
static int uidlcmd(char*);
29
static int usercmd(char*);
30
static char *nextarg(char*);
31
static int getcrnl(char*, int);
32
static int readmbox(char*);
33
static void sendcrnl(char*, ...);
34
static int senderr(char*, ...);
35
static int sendok(char*, ...);
36
#pragma varargck argpos sendcrnl 1
37
#pragma varargck argpos senderr 1
38
#pragma varargck argpos sendok 1
39
 
40
Cmd cmdtab[] =
41
{
42
	"apop", 0, apopcmd,
43
	"capa", 0, capacmd,
44
	"dele", 1, delecmd,
45
	"list", 1, listcmd,
46
	"noop", 0, noopcmd,
47
	"pass", 0, passcmd,
48
	"quit", 0, quitcmd,
49
	"rset", 0, rsetcmd,
50
	"retr", 1, retrcmd,
51
	"stat", 1, statcmd,
52
	"stls", 0, stlscmd,
53
	"sync", 1, synccmd,
54
	"top", 1, topcmd,
55
	"uidl", 1, uidlcmd,
56
	"user", 0, usercmd,
57
	0, 0, 0,
58
};
59
 
60
static Biobuf in;
61
static Biobuf out;
62
static int passwordinclear;
63
static int didtls;
64
 
65
typedef struct Msg Msg;
66
struct Msg
67
{
68
	int upasnum;
69
	char digest[64];
70
	int bytes;
71
	int deleted;
72
};
73
 
74
static int totalbytes;
75
static int totalmsgs;
76
static Msg *msg;
77
static int nmsg;
78
static int loggedin;
79
static int debug;
80
static uchar *tlscert;
81
static int ntlscert;
82
static char *peeraddr;
83
static char tmpaddr[64];
84
 
85
void
86
usage(void)
87
{
88
	fprint(2, "usage: upas/pop3 [-a authmboxfile] [-d debugfile] [-p] "
89
		"[-r remote] [-t cert]\n");
90
	exits("usage");
91
}
92
 
93
void
94
main(int argc, char **argv)
95
{
96
	int fd;
97
	char *arg, cmdbuf[1024];
98
	Cmd *c;
99
 
100
	rfork(RFNAMEG);
101
	Binit(&in, 0, OREAD);
102
	Binit(&out, 1, OWRITE);
103
 
104
	ARGBEGIN{
105
	case 'a':
106
		loggedin = 1;
107
		if(readmbox(EARGF(usage())) < 0)
108
			exits(nil);
109
		break;
110
	case 'd':
111
		debug++;
112
		if((fd = create(EARGF(usage()), OWRITE, 0666)) >= 0 && fd != 2){
113
			dup(fd, 2);
114
			close(fd);
115
		}
116
		break;
117
	case 'p':
118
		passwordinclear = 1;
119
		break;
120
	case 'r':
121
		strecpy(tmpaddr, tmpaddr+sizeof tmpaddr, EARGF(usage()));
122
		if(arg = strchr(tmpaddr, '!'))
123
			*arg = '\0';
124
		peeraddr = tmpaddr;
125
		break;
126
	case 't':
127
		tlscert = readcert(EARGF(usage()), &ntlscert);
128
		if(tlscert == nil){
129
			senderr("cannot read TLS certificate: %r");
130
			exits(nil);
131
		}
132
		break;
133
	}ARGEND
134
 
135
	/* do before TLS */
136
	if(peeraddr == nil)
137
		peeraddr = remoteaddr(0,0);
138
 
139
	hello();
140
 
141
	while(Bflush(&out), getcrnl(cmdbuf, sizeof cmdbuf) > 0){
142
		arg = nextarg(cmdbuf);
143
		for(c=cmdtab; c->name; c++)
144
			if(cistrcmp(c->name, cmdbuf) == 0)
145
				break;
146
		if(c->name == 0){
147
			senderr("unknown command %s", cmdbuf);
148
			continue;
149
		}
150
		if(c->needauth && !loggedin){
151
			senderr("%s requires authentication", cmdbuf);
152
			continue;
153
		}
154
		(*c->f)(arg);
155
	}
156
	exits(nil);
157
}
158
 
159
/* sort directories in increasing message number order */
160
static int
161
dircmp(void *a, void *b)
162
{
163
	return atoi(((Dir*)a)->name) - atoi(((Dir*)b)->name);
164
}
165
 
166
static int
167
readmbox(char *box)
168
{
169
	int fd, i, n, nd, lines, pid;
170
	char buf[100], err[Errlen];
171
	char *p;
172
	Biobuf *b;
173
	Dir *d, *draw;
174
	Msg *m;
175
	Waitmsg *w;
176
 
177
	unmount(nil, "/mail/fs");
178
	switch(pid = fork()){
179
	case -1:
180
		return senderr("can't fork to start upas/fs");
181
 
182
	case 0:
183
		close(0);
184
		close(1);
185
		open("/dev/null", OREAD);
186
		open("/dev/null", OWRITE);
187
		execl("/bin/upas/fs", "upas/fs", "-np", "-f", box, nil);
188
		snprint(err, sizeof err, "upas/fs: %r");
189
		_exits(err);
190
		break;
191
 
192
	default:
193
		break;
194
	}
195
 
196
	if((w = wait()) == nil || w->pid != pid || w->msg[0] != '\0'){
197
		if(w && w->pid==pid)
198
			return senderr("%s", w->msg);
199
		else
200
			return senderr("can't initialize upas/fs");
201
	}
202
	free(w);
203
 
204
	if(chdir("/mail/fs/mbox") < 0)
205
		return senderr("can't initialize upas/fs: %r");
206
 
207
	if((fd = open(".", OREAD)) < 0)
208
		return senderr("cannot open /mail/fs/mbox: %r");
209
	nd = dirreadall(fd, &d);
210
	close(fd);
211
	if(nd < 0)
212
		return senderr("cannot read from /mail/fs/mbox: %r");
213
 
214
	msg = mallocz(sizeof(Msg)*nd, 1);
215
	if(msg == nil)
216
		return senderr("out of memory");
217
 
218
	if(nd == 0)
219
		return 0;
220
	qsort(d, nd, sizeof(d[0]), dircmp);
221
 
222
	for(i=0; i<nd; i++){
223
		m = &msg[nmsg];
224
		m->upasnum = atoi(d[i].name);
225
		sprint(buf, "%d/digest", m->upasnum);
226
		if((fd = open(buf, OREAD)) < 0)
227
			continue;
228
		n = readn(fd, m->digest, sizeof m->digest - 1);
229
		close(fd);
230
		if(n < 0)
231
			continue;
232
		m->digest[n] = '\0';
233
 
234
		/*
235
		 * We need the number of message lines so that we
236
		 * can adjust the byte count to include \r's.
237
		 * Upas/fs gives us the number of lines in the raw body
238
		 * in the lines file, but we have to count rawheader ourselves.
239
		 * There is one blank line between raw header and raw body.
240
		 */
241
		sprint(buf, "%d/rawheader", m->upasnum);
242
		if((b = Bopen(buf, OREAD)) == nil)
243
			continue;
244
		lines = 0;
245
		for(;;){
246
			p = Brdline(b, '\n');
247
			if(p == nil){
248
				if((n = Blinelen(b)) == 0)
249
					break;
250
				Bseek(b, n, 1);
251
			}else
252
				lines++;
253
		}
254
		Bterm(b);
255
		lines++;
256
		sprint(buf, "%d/lines", m->upasnum);
257
		if((fd = open(buf, OREAD)) < 0)
258
			continue;
259
		n = readn(fd, buf, sizeof buf - 1);
260
		close(fd);
261
		if(n < 0)
262
			continue;
263
		buf[n] = '\0';
264
		lines += atoi(buf);
265
 
266
		sprint(buf, "%d/raw", m->upasnum);
267
		if((draw = dirstat(buf)) == nil)
268
			continue;
269
		m->bytes = lines+draw->length;
270
		free(draw);
271
		nmsg++;
272
		totalmsgs++;
273
		totalbytes += m->bytes;
274
	}
275
	return 0;
276
}
277
 
278
/*
279
 *  get a line that ends in crnl or cr, turn terminating crnl into a nl
280
 *
281
 *  return 0 on EOF
282
 */
283
static int
284
getcrnl(char *buf, int n)
285
{
286
	int c;
287
	char *ep;
288
	char *bp;
289
	Biobuf *fp = &in;
290
 
291
	Bflush(&out);
292
 
293
	bp = buf;
294
	ep = bp + n - 1;
295
	while(bp != ep){
296
		c = Bgetc(fp);
297
		if(debug) {
298
			seek(2, 0, 2);
299
			fprint(2, "%c", c);
300
		}
301
		switch(c){
302
		case -1:
303
			*bp = 0;
304
			if(bp==buf)
305
				return 0;
306
			else
307
				return bp-buf;
308
		case '\r':
309
			c = Bgetc(fp);
310
			if(c == '\n'){
311
				if(debug) {
312
					seek(2, 0, 2);
313
					fprint(2, "%c", c);
314
				}
315
				*bp = 0;
316
				return bp-buf;
317
			}
318
			Bungetc(fp);
319
			c = '\r';
320
			break;
321
		case '\n':
322
			*bp = 0;
323
			return bp-buf;
324
		}
325
		*bp++ = c;
326
	}
327
	*bp = 0;
328
	return bp-buf;
329
}
330
 
331
static void
332
sendcrnl(char *fmt, ...)
333
{
334
	char buf[1024];
335
	va_list arg;
336
 
337
	va_start(arg, fmt);
338
	vseprint(buf, buf+sizeof(buf), fmt, arg);
339
	va_end(arg);
340
	if(debug)
341
		fprint(2, "-> %s\n", buf);
342
	Bprint(&out, "%s\r\n", buf);
343
}
344
 
345
static int
346
senderr(char *fmt, ...)
347
{
348
	char buf[1024];
349
	va_list arg;
350
 
351
	va_start(arg, fmt);
352
	vseprint(buf, buf+sizeof(buf), fmt, arg);
353
	va_end(arg);
354
	if(debug)
355
		fprint(2, "-> -ERR %s\n", buf);
356
	Bprint(&out, "-ERR %s\r\n", buf);
357
	return -1;
358
}
359
 
360
static int
361
sendok(char *fmt, ...)
362
{
363
	char buf[1024];
364
	va_list arg;
365
 
366
	va_start(arg, fmt);
367
	vseprint(buf, buf+sizeof(buf), fmt, arg);
368
	va_end(arg);
369
	if(*buf){
370
		if(debug)
371
			fprint(2, "-> +OK %s\n", buf);
372
		Bprint(&out, "+OK %s\r\n", buf);
373
	} else {
374
		if(debug)
375
			fprint(2, "-> +OK\n");
376
		Bprint(&out, "+OK\r\n");
377
	}
378
	return 0;
379
}
380
 
381
static int
382
capacmd(char*)
383
{
384
	sendok("");
385
	sendcrnl("TOP");
386
	if(passwordinclear || didtls)
387
		sendcrnl("USER");
388
	sendcrnl("PIPELINING");
389
	sendcrnl("UIDL");
390
	sendcrnl("STLS");
391
	sendcrnl(".");
392
	return 0;
393
}
394
 
395
static int
396
delecmd(char *arg)
397
{
398
	int n;
399
 
400
	if(*arg==0)
401
		return senderr("DELE requires a message number");
402
 
403
	n = atoi(arg)-1;
404
	if(n < 0 || n >= nmsg || msg[n].deleted)
405
		return senderr("no such message");
406
 
407
	msg[n].deleted = 1;
408
	totalmsgs--;
409
	totalbytes -= msg[n].bytes;
410
	sendok("message %d deleted", n+1);
411
	return 0;
412
}
413
 
414
static int
415
listcmd(char *arg)
416
{
417
	int i, n;
418
 
419
	if(*arg == 0){
420
		sendok("+%d message%s (%d octets)", totalmsgs, totalmsgs==1 ? "":"s", totalbytes);
421
		for(i=0; i<nmsg; i++){
422
			if(msg[i].deleted)
423
				continue;
424
			sendcrnl("%d %d", i+1, msg[i].bytes);
425
		}
426
		sendcrnl(".");
427
	}else{
428
		n = atoi(arg)-1;
429
		if(n < 0 || n >= nmsg || msg[n].deleted)
430
			return senderr("no such message");
431
		sendok("%d %d", n+1, msg[n].bytes);
432
	}
433
	return 0;
434
}
435
 
436
static int
437
noopcmd(char *arg)
438
{
439
	USED(arg);
440
	sendok("");
441
	return 0;
442
}
443
 
444
static void
445
_synccmd(char*)
446
{
447
	int i, fd;
448
	char *s;
449
	Fmt f;
450
 
451
	if(!loggedin){
452
		sendok("");
453
		return;
454
	}
455
 
456
	fmtstrinit(&f);
457
	fmtprint(&f, "delete mbox");
458
	for(i=0; i<nmsg; i++)
459
		if(msg[i].deleted)
460
			fmtprint(&f, " %d", msg[i].upasnum);
461
	s = fmtstrflush(&f);
462
	if(strcmp(s, "delete mbox") != 0){	/* must have something to delete */
463
		if((fd = open("../ctl", OWRITE)) < 0){
464
			senderr("open ctl to delete messages: %r");
465
			return;
466
		}
467
		if(write(fd, s, strlen(s)) < 0){
468
			senderr("error deleting messages: %r");
469
			return;
470
		}
471
	}
472
	sendok("");
473
}
474
 
475
static int
476
synccmd(char*)
477
{
478
	_synccmd(nil);
479
	return 0;
480
}
481
 
482
static int
483
quitcmd(char*)
484
{
485
	synccmd(nil);
486
	exits(nil);
487
	return 0;
488
}
489
 
490
static int
491
retrcmd(char *arg)
492
{
493
	int n;
494
	Biobuf *b;
495
	char buf[40], *p;
496
 
497
	if(*arg == 0)
498
		return senderr("RETR requires a message number");
499
	n = atoi(arg)-1;
500
	if(n < 0 || n >= nmsg || msg[n].deleted)
501
		return senderr("no such message");
502
	snprint(buf, sizeof buf, "%d/raw", msg[n].upasnum);
503
	if((b = Bopen(buf, OREAD)) == nil)
504
		return senderr("message disappeared");
505
	sendok("");
506
	while((p = Brdstr(b, '\n', 1)) != nil){
507
		if(p[0]=='.')
508
			Bwrite(&out, ".", 1);
509
		Bwrite(&out, p, strlen(p));
510
		Bwrite(&out, "\r\n", 2);
511
		free(p);
512
	}
513
	Bterm(b);
514
	sendcrnl(".");
515
	return 0;
516
}
517
 
518
static int
519
rsetcmd(char*)
520
{
521
	int i;
522
 
523
	for(i=0; i<nmsg; i++){
524
		if(msg[i].deleted){
525
			msg[i].deleted = 0;
526
			totalmsgs++;
527
			totalbytes += msg[i].bytes;
528
		}
529
	}
530
	return sendok("");
531
}
532
 
533
static int
534
statcmd(char*)
535
{
536
	return sendok("%d %d", totalmsgs, totalbytes);
537
}
538
 
539
static int
540
trace(char *fmt, ...)
541
{
542
	va_list arg;
543
	int n;
544
 
545
	va_start(arg, fmt);
546
	n = vfprint(2, fmt, arg);
547
	va_end(arg);
548
	return n;
549
}
550
 
551
static int
552
stlscmd(char*)
553
{
554
	int fd;
555
	TLSconn conn;
556
 
557
	if(didtls)
558
		return senderr("tls already started");
559
	if(!tlscert)
560
		return senderr("don't have any tls credentials");
561
	sendok("");
562
	Bflush(&out);
563
 
564
	memset(&conn, 0, sizeof conn);
565
	conn.cert = tlscert;
566
	conn.certlen = ntlscert;
567
	if(debug)
568
		conn.trace = trace;
569
	fd = tlsServer(0, &conn);
570
	if(fd < 0)
571
		sysfatal("tlsServer: %r");
572
	dup(fd, 0);
573
	dup(fd, 1);
574
	close(fd);
575
	Binit(&in, 0, OREAD);
576
	Binit(&out, 1, OWRITE);
577
	didtls = 1;
578
	return 0;
579
}
580
 
581
static int
582
topcmd(char *arg)
583
{
584
	int done, i, lines, n;
585
	char buf[40], *p;
586
	Biobuf *b;
587
 
588
	if(*arg == 0)
589
		return senderr("TOP requires a message number");
590
	n = atoi(arg)-1;
591
	if(n < 0 || n >= nmsg || msg[n].deleted)
592
		return senderr("no such message");
593
	arg = nextarg(arg);
594
	if(*arg == 0)
595
		return senderr("TOP requires a line count");
596
	lines = atoi(arg);
597
	if(lines < 0)
598
		return senderr("bad args to TOP");
599
	snprint(buf, sizeof buf, "%d/raw", msg[n].upasnum);
600
	if((b = Bopen(buf, OREAD)) == nil)
601
		return senderr("message disappeared");
602
	sendok("");
603
	while(p = Brdstr(b, '\n', 1)){
604
		if(p[0]=='.')
605
			Bputc(&out, '.');
606
		Bwrite(&out, p, strlen(p));
607
		Bwrite(&out, "\r\n", 2);
608
		done = p[0]=='\0';
609
		free(p);
610
		if(done)
611
			break;
612
	}
613
	for(i=0; i<lines; i++){
614
		p = Brdstr(b, '\n', 1);
615
		if(p == nil)
616
			break;
617
		if(p[0]=='.')
618
			Bwrite(&out, ".", 1);
619
		Bwrite(&out, p, strlen(p));
620
		Bwrite(&out, "\r\n", 2);
621
		free(p);
622
	}
623
	sendcrnl(".");
624
	Bterm(b);
625
	return 0;
626
}
627
 
628
static int
629
uidlcmd(char *arg)
630
{
631
	int n;
632
 
633
	if(*arg==0){
634
		sendok("");
635
		for(n=0; n<nmsg; n++){
636
			if(msg[n].deleted)
637
				continue;
638
			sendcrnl("%d %s", n+1, msg[n].digest);
639
		}
640
		sendcrnl(".");
641
	}else{
642
		n = atoi(arg)-1;
643
		if(n < 0 || n >= nmsg || msg[n].deleted)
644
			return senderr("no such message");
645
		sendok("%d %s", n+1, msg[n].digest);
646
	}
647
	return 0;
648
}
649
 
650
static char*
651
nextarg(char *p)
652
{
653
	while(*p && *p != ' ' && *p != '\t')
654
		p++;
655
	while(*p == ' ' || *p == '\t')
656
		*p++ = 0;
657
	return p;
658
}
659
 
660
/*
661
 * authentication
662
 */
663
Chalstate *chs;
664
char user[256];
665
char box[256];
666
char cbox[256];
667
 
668
static void
669
hello(void)
670
{
671
	fmtinstall('H', encodefmt);
672
	if((chs = auth_challenge("proto=apop role=server")) == nil){
673
		senderr("auth server not responding, try later");
674
		exits(nil);
675
	}
676
 
677
	sendok("POP3 server ready %s", chs->chal);
678
}
679
 
680
static int
681
setuser(char *arg)
682
{
683
	char *p;
684
 
685
	strcpy(box, "/mail/box/");
686
	strecpy(box+strlen(box), box+sizeof box-7, arg);
687
	strcpy(cbox, box);
688
	cleanname(cbox);
689
	if(strcmp(cbox, box) != 0)
690
		return senderr("bad mailbox name");
691
	strcat(box, "/mbox");
692
 
693
	strecpy(user, user+sizeof user, arg);
694
	if(p = strchr(user, '/'))
695
		*p = '\0';
696
	return 0;
697
}
698
 
699
static int
700
usercmd(char *arg)
701
{
702
	if(loggedin)
703
		return senderr("already authenticated");
704
	if(*arg == 0)
705
		return senderr("USER requires argument");
706
	if(setuser(arg) < 0)
707
		return -1;
708
	return sendok("");
709
}
710
 
711
static void
712
enableaddr(void)
713
{
714
	int fd;
715
	char buf[64];
716
 
717
	/* hide the peer IP address under a rock in the ratifier FS */
718
	if(peeraddr == 0 || *peeraddr == 0)
719
		return;
720
 
721
	sprint(buf, "/mail/ratify/trusted/%s#32", peeraddr);
722
 
723
	/*
724
	 * if the address is already there and the user owns it,
725
	 * remove it and recreate it to give him a new time quanta.
726
	 */
727
	if(access(buf, 0) >= 0  && remove(buf) < 0)
728
		return;
729
 
730
	fd = create(buf, OREAD, 0666);
731
	if(fd >= 0){
732
		close(fd);
733
//		syslog(0, "pop3", "ratified %s", peeraddr);
734
	}
735
}
736
 
737
static int
738
dologin(char *response)
739
{
740
	AuthInfo *ai;
741
	static int tries;
742
	static ulong delaysecs = 5;
743
 
744
	chs->user = user;
745
	chs->resp = response;
746
	chs->nresp = strlen(response);
747
	if((ai = auth_response(chs)) == nil){
748
		if(tries >= 20){
749
			senderr("authentication failed: %r; server exiting");
750
			exits(nil);
751
		}
752
		if(++tries == 3)
753
			syslog(0, "pop3", "likely password guesser from %s",
754
				peeraddr);
755
		delaysecs *= 2;
756
		if (delaysecs > 30*60)
757
			delaysecs = 30*60;		/* half-hour max. */
758
		sleep(delaysecs * 1000); /* prevent beating on our auth server */
759
		return senderr("authentication failed");
760
	}
761
 
762
	if(auth_chuid(ai, nil) < 0){
763
		senderr("chuid failed: %r; server exiting");
764
		exits(nil);
765
	}
766
	auth_freeAI(ai);
767
	auth_freechal(chs);
768
	chs = nil;
769
 
770
	loggedin = 1;
771
	if(newns(user, 0) < 0){
772
		senderr("newns failed: %r; server exiting");
773
		exits(nil);
774
	}
775
	syslog(0, "pop3", "user %s logged in", user);
776
	enableaddr();
777
	if(readmbox(box) < 0)
778
		exits(nil);
779
	return sendok("mailbox is %s", box);
780
}
781
 
782
static int
783
passcmd(char *arg)
784
{
785
	DigestState *s;
786
	uchar digest[MD5dlen];
787
	char response[2*MD5dlen+1];
788
 
789
	if(passwordinclear==0 && didtls==0)
790
		return senderr("password in the clear disallowed");
791
 
792
	/* use password to encode challenge */
793
	if((chs = auth_challenge("proto=apop role=server")) == nil)
794
		return senderr("couldn't get apop challenge");
795
 
796
	/* hash challenge with secret and convert to ascii */
797
	s = md5((uchar*)chs->chal, chs->nchal, 0, 0);
798
	md5((uchar*)arg, strlen(arg), digest, s);
799
	snprint(response, sizeof response, "%.*H", MD5dlen, digest);
800
	return dologin(response);
801
}
802
 
803
static int
804
apopcmd(char *arg)
805
{
806
	char *resp;
807
 
808
	resp = nextarg(arg);
809
	if(setuser(arg) < 0)
810
		return -1;
811
	return dologin(resp);
812
}
813