Warning: Attempt to read property "date" on null in /usr/local/www/websvn.planix.org/blame.php on line 247

Warning: Attempt to read property "msg" on null in /usr/local/www/websvn.planix.org/blame.php on line 247

Warning: Attempt to read property "date" on null in /usr/local/www/websvn.planix.org/blame.php on line 247

Warning: Attempt to read property "msg" on null in /usr/local/www/websvn.planix.org/blame.php on line 247
WebSVN – planix.SVN – Blame – /os/branches/feature_unix/sys/src/cmd/upas/smtp/smtp.c – Rev 33

Subversion Repositories planix.SVN

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 1
#include "common.h"
2
#include "smtp.h"
3
#include <ctype.h>
4
#include <mp.h>
5
#include <libsec.h>
6
#include <auth.h>
7
 
8
static	char*	connect(char*);
9
static	char*	dotls(char*);
10
static	char*	doauth(char*);
11
 
12
void	addhostdom(String*, char*);
13
String*	bangtoat(char*);
14
String*	convertheader(String*);
15
int	dBprint(char*, ...);
16
int	dBputc(int);
17
char*	data(String*, Biobuf*);
18
char*	domainify(char*, char*);
19
String*	fixrouteaddr(String*, Node*, Node*);
20
char*	getcrnl(String*);
21
int	getreply(void);
22
char*	hello(char*, int);
23
char*	mailfrom(char*);
24
int	printdate(Node*);
25
int	printheader(void);
26
void	putcrnl(char*, int);
27
void	quit(char*);
28
char*	rcptto(char*);
29
char	*rewritezone(char *);
30
 
31
#define Retry	"Retry, Temporary Failure"
32
#define Giveup	"Permanent Failure"
33
 
34
String	*reply;		/* last reply */
35
String	*toline;
36
 
37
int	alarmscale;
38
int	autistic;
39
int	debug;		/* true if we're debugging */
40
int	filter;
41
int	insecure;
42
int	last = 'n';	/* last character sent by putcrnl() */
43
int	ping;
44
int	quitting;	/* when error occurs in quit */
45
int	tryauth;	/* Try to authenticate, if supported */
46
int	trysecure;	/* Try to use TLS if the other side supports it */
47
int	okunksecure;	/* okay to use TLS to unknown servers */
48
 
49
char	*quitrv;	/* deferred return value when in quit */
50
char	ddomain[Maxdomain]; /* domain name of destination machine */
51
char	*gdomain;	/* domain name of gateway */
52
char	*uneaten;	/* first character after rfc822 headers */
53
char	*farend;	/* system we are trying to send to */
54
char	*user;		/* user we are authenticating as, if authenticating */
55
char	hostdomain[256];
56
 
57
Biobuf	bin;
58
Biobuf	bout;
59
Biobuf	berr;
60
Biobuf	bfile;
61
 
62
static int bustedmx;
63
 
64
void
65
usage(void)
66
{
67
	fprint(2, "usage: smtp [-aAdfips] [-b busted-mx] [-g gw] [-h host] "
68
		"[-u user] [.domain] net!host[!service] sender rcpt-list\n");
69
	exits(Giveup);
70
}
71
 
72
int
73
timeout(void *x, char *msg)
74
{
75
	USED(x);
76
	syslog(0, "smtp.fail", "interrupt: %s: %s", farend,  msg);
77
	if(strstr(msg, "alarm")){
78
		fprint(2, "smtp timeout: connection to %s timed out\n", farend);
79
		if(quitting)
80
			exits(quitrv);
81
		exits(Retry);
82
	}
83
	if(strstr(msg, "closed pipe")){
84
		/* call _exits() to prevent Bio from trying to flush closed pipe */
85
		fprint(2, "smtp timeout: connection closed to %s\n", farend);
86
		if(quitting){
87
			syslog(0, "smtp.fail", "closed pipe to %s", farend);
88
			_exits(quitrv);
89
		}
90
		_exits(Retry);
91
	}
92
	return 0;
93
}
94
 
95
void
96
removenewline(char *p)
97
{
98
	int n = strlen(p)-1;
99
 
100
	if(n < 0)
101
		return;
102
	if(p[n] == '\n')
103
		p[n] = 0;
104
}
105
 
106
void
107
main(int argc, char **argv)
108
{
109
	int i, ok, rcvrs;
110
	char *addr, *rv, *trv, *host, *domain;
111
	char **errs;
112
	char hellodomain[256];
113
	String *from, *fromm, *sender;
114
 
115
	alarmscale = 60*1000;	/* minutes */
116
	quotefmtinstall();
117
	errs = malloc(argc*sizeof(char*));
118
	reply = s_new();
119
	host = 0;
120
	ARGBEGIN{
121
	case 'a':
122
		tryauth = 1;
123
		trysecure = 1;
124
		break;
125
	case 'A':	/* autistic: won't talk to us until we talk (Verizon) */
126
		autistic = 1;
127
		break;
128
	case 'b':
129
		if (bustedmx >= Maxbustedmx)
130
			sysfatal("more than %d busted mxs given", Maxbustedmx);
131
		bustedmxs[bustedmx++] = EARGF(usage());
132
		break;
133
	case 'd':
134
		debug = 1;
135
		break;
136
	case 'f':
137
		filter = 1;
138
		break;
139
	case 'g':
140
		gdomain = EARGF(usage());
141
		break;
142
	case 'h':
143
		host = EARGF(usage());
144
		break;
145
	case 'i':
146
		insecure = 1;
147
		break;
148
	case 'o':
149
		okunksecure = 1;
150
		break;
151
	case 'p':
152
		alarmscale = 10*1000;	/* tens of seconds */
153
		ping = 1;
154
		break;
155
	case 's':
156
		trysecure = 1;
157
		break;
158
	case 'u':
159
		user = EARGF(usage());
160
		break;
161
	default:
162
		usage();
163
		break;
164
	}ARGEND;
165
 
166
	Binit(&berr, 2, OWRITE);
167
	Binit(&bfile, 0, OREAD);
168
 
169
	/*
170
	 *  get domain and add to host name
171
	 */
172
	if(*argv && **argv=='.') {
173
		domain = *argv;
174
		argv++; argc--;
175
	} else
176
		domain = domainname_read();
177
	if(domain == nil)
178
		fprint(2, "%s: nil domainname_read()\n", argv0);
179
	if(host == 0)
180
		host = sysname_read();
181
	strcpy(hostdomain, domainify(host, domain));
182
	strcpy(hellodomain, domainify(sysname_read(), domain));
183
 
184
	/*
185
	 *  get destination address
186
	 */
187
	if(*argv == 0)
188
		usage();
189
	addr = *argv++; argc--;
190
	farend = addr;
191
 
192
	/*
193
	 *  get sender's machine.
194
	 *  get sender in internet style.  domainify if necessary.
195
	 */
196
	if(*argv == 0)
197
		usage();
198
	sender = unescapespecial(s_copy(*argv++));
199
	argc--;
200
	fromm = s_clone(sender);
201
	rv = strrchr(s_to_c(fromm), '!');
202
	if(rv)
203
		*rv = 0;
204
	else
205
		*s_to_c(fromm) = 0;
206
	from = bangtoat(s_to_c(sender));
207
 
208
	/*
209
	 *  send the mail
210
	 */
211
	if(filter){
212
		Binit(&bout, 1, OWRITE);
213
		rv = data(from, &bfile);
214
		if(rv != 0)
215
			goto error;
216
		exits(0);
217
	}
218
 
219
	/* mxdial uses its own timeout handler */
220
	if((rv = connect(addr)) != 0)
221
		exits(rv);
222
 
223
	/* 10 minutes to get through the initial handshake */
224
	atnotify(timeout, 1);
225
	alarm(10*alarmscale);
226
	if((rv = hello(hellodomain, 0)) != 0)
227
		goto error;
228
	alarm(10*alarmscale);
229
	if((rv = mailfrom(s_to_c(from))) != 0)
230
		goto error;
231
 
232
	ok = 0;
233
	rcvrs = 0;
234
	/* if any rcvrs are ok, we try to send the message */
235
	for(i = 0; i < argc; i++){
236
		if((trv = rcptto(argv[i])) != 0){
237
			/* remember worst error */
238
			if(rv != Giveup)
239
				rv = trv;
240
			errs[rcvrs] = strdup(s_to_c(reply));
241
			removenewline(errs[rcvrs]);
242
		} else {
243
			ok++;
244
			errs[rcvrs] = 0;
245
		}
246
		rcvrs++;
247
	}
248
 
249
	/* if no ok rcvrs or worst error is retry, give up */
250
	if(ok == 0 || rv == Retry)
251
		goto error;
252
 
253
	if(ping){
254
		quit(0);
255
		exits(0);
256
	}
257
 
258
	rv = data(from, &bfile);
259
	if(rv != 0)
260
		goto error;
261
	quit(0);
262
	if(rcvrs == ok)
263
		exits(0);
264
 
265
	/*
266
	 *  here when some but not all rcvrs failed
267
	 */
268
	fprint(2, "%s connect to %s:\n", thedate(), addr);
269
	for(i = 0; i < rcvrs; i++){
270
		if(errs[i]){
271
			syslog(0, "smtp.fail", "delivery to %s at %s failed: %s", argv[i], addr, errs[i]);
272
			fprint(2, "  mail to %s failed: %s", argv[i], errs[i]);
273
		}
274
	}
275
	exits(Giveup);
276
 
277
	/*
278
	 *  here when all rcvrs failed
279
	 */
280
error:
281
	removenewline(s_to_c(reply));
282
	syslog(0, "smtp.fail", "%s to %s failed: %s",
283
		ping ? "ping" : "delivery",
284
		addr, s_to_c(reply));
285
	fprint(2, "%s connect to %s:\n%s\n", thedate(), addr, s_to_c(reply));
286
	if(!filter)
287
		quit(rv);
288
	exits(rv);
289
}
290
 
291
/*
292
 *  connect to the remote host
293
 */
294
static char *
295
connect(char* net)
296
{
297
	char buf[Errlen];
298
	int fd;
299
 
300
	fd = mxdial(net, ddomain, gdomain);
301
 
302
	if(fd < 0){
303
		rerrstr(buf, sizeof(buf));
304
		Bprint(&berr, "smtp: %s (%s)\n", buf, net);
305
		syslog(0, "smtp.fail", "%s (%s)", buf, net);
306
		if(strstr(buf, "illegal")
307
		|| strstr(buf, "unknown")
308
		|| strstr(buf, "can't translate"))
309
			return Giveup;
310
		else
311
			return Retry;
312
	}
313
	Binit(&bin, fd, OREAD);
314
	fd = dup(fd, -1);
315
	Binit(&bout, fd, OWRITE);
316
	return 0;
317
}
318
 
319
static char smtpthumbs[] =	"/sys/lib/tls/smtp";
320
static char smtpexclthumbs[] =	"/sys/lib/tls/smtp.exclude";
321
 
322
static char *
323
ckthumbs(TLSconn *c)
324
{
325
	Thumbprint *goodcerts;
326
	char *h, *err;
327
	uchar hash[SHA1dlen];
328
 
329
	err = nil;
33 7u83 330
	goodcerts = initThumbprints(smtpthumbs, smtpexclthumbs,"x509");
2 - 331
	if (goodcerts == nil) {
332
		if (!okunksecure)
333
			syslog(0, "smtp", "bad thumbprints in %s", smtpthumbs);
334
		return Giveup;		/* how to recover? TLS is started */
335
	}
336
 
337
	/* compute sha1 hash of remote's certificate, see if we know it */
338
	sha1(c->cert, c->certlen, hash, nil);
33 7u83 339
	if (!okThumbprint(hash, SHA1dlen, goodcerts ) && !okunksecure) {
2 - 340
		h = malloc(2*sizeof hash + 1);
341
		if (h != nil) {
342
			enc16(h, 2*sizeof hash + 1, hash, sizeof hash);
343
			syslog(0, "smtp", "remote cert. has bad thumbprint: "
344
				"x509 sha1=%s server=%q", h, ddomain);
345
			free(h);
346
		}
347
		err = Giveup;		/* how to recover? TLS is started */
348
	}
349
	freeThumbprints(goodcerts);
350
	return err;
351
}
352
 
353
/*
354
 *  exchange names with remote host, attempt to
355
 *  enable encryption and optionally authenticate.
356
 *  not fatal if we can't.
357
 */
358
static char *
359
dotls(char *me)
360
{
361
	TLSconn *c;
362
	char *err;
363
	int fd;
364
 
365
	c = mallocz(sizeof(*c), 1);	/* Note: not freed on success */
366
	if (c == nil)
367
		return Giveup;
368
 
369
	dBprint("STARTTLS\r\n");
370
	if (getreply() != 2)
371
		return Giveup;
372
 
373
	fd = tlsClient(Bfildes(&bout), c);
374
	if (fd < 0) {
375
		syslog(0, "smtp", "tlsClient to %q: %r", ddomain);
376
		return Giveup;
377
	}
378
 
379
	err = ckthumbs(c);
380
	if (err && !okunksecure) {
381
		free(c);
382
		close(fd);
383
		return err;		/* how to recover? TLS is started */
384
	}
385
 
386
	Bterm(&bin);
387
	Bterm(&bout);
388
 
389
	/*
390
	 * set up bin & bout to use the TLS fd, i/o upon which generates
391
	 * i/o on the original, underlying fd.
392
	 */
393
	Binit(&bin, fd, OREAD);
394
	fd = dup(fd, -1);
395
	Binit(&bout, fd, OWRITE);
396
 
397
	syslog(0, "smtp", "started TLS to %q", ddomain);
398
	return(hello(me, 1));
399
}
400
 
401
static char *
402
doauth(char *methods)
403
{
404
	char *buf, *base64;
405
	int n;
406
	DS ds;
407
	UserPasswd *p;
408
 
409
	dial_string_parse(ddomain, &ds);
410
 
411
	if(user != nil)
412
		p = auth_getuserpasswd(nil,
413
	  	  "proto=pass service=smtp server=%q user=%q", ds.host, user);
414
	else
415
		p = auth_getuserpasswd(nil,
416
	  	  "proto=pass service=smtp server=%q", ds.host);
417
	if (p == nil)
418
		return Giveup;
419
 
420
	if (strstr(methods, "LOGIN")){
421
		dBprint("AUTH LOGIN\r\n");
422
		if (getreply() != 3)
423
			return Retry;
424
 
425
		n = strlen(p->user);
426
		base64 = malloc(2*n);
427
		if (base64 == nil)
428
			return Retry;	/* Out of memory */
429
		enc64(base64, 2*n, (uchar *)p->user, n);
430
		dBprint("%s\r\n", base64);
431
		if (getreply() != 3)
432
			return Retry;
433
 
434
		n = strlen(p->passwd);
435
		base64 = malloc(2*n);
436
		if (base64 == nil)
437
			return Retry;	/* Out of memory */
438
		enc64(base64, 2*n, (uchar *)p->passwd, n);
439
		dBprint("%s\r\n", base64);
440
		if (getreply() != 2)
441
			return Retry;
442
 
443
		free(base64);
444
	}
445
	else
446
	if (strstr(methods, "PLAIN")){
447
		n = strlen(p->user) + strlen(p->passwd) + 3;
448
		buf = malloc(n);
449
		base64 = malloc(2 * n);
450
		if (buf == nil || base64 == nil) {
451
			free(buf);
452
			return Retry;	/* Out of memory */
453
		}
454
		snprint(buf, n, "%c%s%c%s", 0, p->user, 0, p->passwd);
455
		enc64(base64, 2 * n, (uchar *)buf, n - 1);
456
		free(buf);
457
		dBprint("AUTH PLAIN %s\r\n", base64);
458
		free(base64);
459
		if (getreply() != 2)
460
			return Retry;
461
	}
462
	else
463
		return "No supported AUTH method";
464
	return(0);
465
}
466
 
467
char *
468
hello(char *me, int encrypted)
469
{
470
	int ehlo;
471
	String *r;
472
	char *ret, *s, *t;
473
 
474
	if (!encrypted) {
475
		/*
476
		 * Verizon fails to print the smtp greeting banner when it
477
		 * answers a call.  Send a no-op in the hope of making it
478
		 * talk.
479
		 */
480
		if (autistic) {
481
			dBprint("NOOP\r\n");
482
			getreply();	/* consume the smtp greeting */
483
			/* next reply will be response to noop */
484
		}
485
		switch(getreply()){
486
		case 2:
487
			break;
488
		case 5:
489
			return Giveup;
490
		default:
491
			return Retry;
492
		}
493
	}
494
 
495
	ehlo = 1;
496
  Again:
497
	if(ehlo)
498
		dBprint("EHLO %s\r\n", me);
499
	else
500
		dBprint("HELO %s\r\n", me);
501
	switch (getreply()) {
502
	case 2:
503
		break;
504
	case 5:
505
		if(ehlo){
506
			ehlo = 0;
507
			goto Again;
508
		}
509
		return Giveup;
510
	default:
511
		return Retry;
512
	}
513
	r = s_clone(reply);
514
	if(r == nil)
515
		return Retry;	/* Out of memory or couldn't get string */
516
 
517
	/* Invariant: every line has a newline, a result of getcrlf() */
518
	for(s = s_to_c(r); (t = strchr(s, '\n')) != nil; s = t + 1){
519
		*t = '\0';
520
		for (t = s; *t != '\0'; t++)
521
			*t = toupper(*t);
522
		if(!encrypted && trysecure &&
523
		    (strcmp(s, "250-STARTTLS") == 0 ||
524
		     strcmp(s, "250 STARTTLS") == 0)){
525
			s_free(r);
526
			return dotls(me);
527
		}
528
		if(tryauth && (encrypted || insecure) &&
529
		    (strncmp(s, "250 AUTH", strlen("250 AUTH")) == 0 ||
530
		     strncmp(s, "250-AUTH", strlen("250 AUTH")) == 0)){
531
			ret = doauth(s + strlen("250 AUTH "));
532
			s_free(r);
533
			return ret;
534
		}
535
	}
536
	s_free(r);
537
	return 0;
538
}
539
 
540
/*
541
 *  report sender to remote
542
 */
543
char *
544
mailfrom(char *from)
545
{
546
	if(!returnable(from))
547
		dBprint("MAIL FROM:<>\r\n");
548
	else
549
	if(strchr(from, '@'))
550
		dBprint("MAIL FROM:<%s>\r\n", from);
551
	else
552
		dBprint("MAIL FROM:<%s@%s>\r\n", from, hostdomain);
553
	switch(getreply()){
554
	case 2:
555
		break;
556
	case 5:
557
		return Giveup;
558
	default:
559
		return Retry;
560
	}
561
	return 0;
562
}
563
 
564
/*
565
 *  report a recipient to remote
566
 */
567
char *
568
rcptto(char *to)
569
{
570
	String *s;
571
 
572
	s = unescapespecial(bangtoat(to));
573
	if(toline == 0)
574
		toline = s_new();
575
	else
576
		s_append(toline, ", ");
577
	s_append(toline, s_to_c(s));
578
	if(strchr(s_to_c(s), '@'))
579
		dBprint("RCPT TO:<%s>\r\n", s_to_c(s));
580
	else {
581
		s_append(toline, "@");
582
		s_append(toline, ddomain);
583
		dBprint("RCPT TO:<%s@%s>\r\n", s_to_c(s), ddomain);
584
	}
585
	alarm(10*alarmscale);
586
	switch(getreply()){
587
	case 2:
588
		break;
589
	case 5:
590
		return Giveup;
591
	default:
592
		return Retry;
593
	}
594
	return 0;
595
}
596
 
597
static char hex[] = "0123456789abcdef";
598
 
599
/*
600
 *  send the damn thing
601
 */
602
char *
603
data(String *from, Biobuf *b)
604
{
605
	char *buf, *cp;
606
	int i, n, nbytes, bufsize, eof, r;
607
	String *fromline;
608
	char errmsg[Errlen];
609
	char id[40];
610
 
611
	/*
612
	 *  input the header.
613
	 */
614
 
615
	buf = malloc(1);
616
	if(buf == 0){
617
		s_append(s_restart(reply), "out of memory");
618
		return Retry;
619
	}
620
	n = 0;
621
	eof = 0;
622
	for(;;){
623
		cp = Brdline(b, '\n');
624
		if(cp == nil){
625
			eof = 1;
626
			break;
627
		}
628
		nbytes = Blinelen(b);
629
		buf = realloc(buf, n+nbytes+1);
630
		if(buf == 0){
631
			s_append(s_restart(reply), "out of memory");
632
			return Retry;
633
		}
634
		strncpy(buf+n, cp, nbytes);
635
		n += nbytes;
636
		if(nbytes == 1)		/* end of header */
637
			break;
638
	}
639
	buf[n] = 0;
640
	bufsize = n;
641
 
642
	/*
643
	 *  parse the header, turn all addresses into @ format
644
	 */
645
	yyinit(buf, n);
646
	yyparse();
647
 
648
	/*
649
	 *  print message observing '.' escapes and using \r\n for \n
650
	 */
651
	alarm(20*alarmscale);
652
	if(!filter){
653
		dBprint("DATA\r\n");
654
		switch(getreply()){
655
		case 3:
656
			break;
657
		case 5:
658
			free(buf);
659
			return Giveup;
660
		default:
661
			free(buf);
662
			return Retry;
663
		}
664
	}
665
	/*
666
	 *  send header.  add a message-id, a sender, and a date if there
667
	 *  isn't one
668
	 */
669
	nbytes = 0;
670
	fromline = convertheader(from);
671
	uneaten = buf;
672
 
673
	srand(truerand());
674
	if(messageid == 0){
675
		for(i=0; i<16; i++){
676
			r = rand()&0xFF;
677
			id[2*i] = hex[r&0xF];
678
			id[2*i+1] = hex[(r>>4)&0xF];
679
		}
680
		id[2*i] = '\0';
681
		nbytes += Bprint(&bout, "Message-ID: <%s@%s>\r\n", id, hostdomain);
682
		if(debug)
683
			Bprint(&berr, "Message-ID: <%s@%s>\r\n", id, hostdomain);
684
	}
685
 
686
	if(originator==0){
687
		nbytes += Bprint(&bout, "From: %s\r\n", s_to_c(fromline));
688
		if(debug)
689
			Bprint(&berr, "From: %s\r\n", s_to_c(fromline));
690
	}
691
	s_free(fromline);
692
 
693
	if(destination == 0 && toline)
694
		if(*s_to_c(toline) == '@'){	/* route addr */
695
			nbytes += Bprint(&bout, "To: <%s>\r\n", s_to_c(toline));
696
			if(debug)
697
				Bprint(&berr, "To: <%s>\r\n", s_to_c(toline));
698
		} else {
699
			nbytes += Bprint(&bout, "To: %s\r\n", s_to_c(toline));
700
			if(debug)
701
				Bprint(&berr, "To: %s\r\n", s_to_c(toline));
702
		}
703
 
704
	if(date==0 && udate)
705
		nbytes += printdate(udate);
706
	if (usys)
707
		uneaten = usys->end + 1;
708
	nbytes += printheader();
709
	if (*uneaten != '\n')
710
		putcrnl("\n", 1);
711
 
712
	/*
713
	 *  send body
714
	 */
715
 
716
	putcrnl(uneaten, buf+n - uneaten);
717
	nbytes += buf+n - uneaten;
718
	if(eof == 0){
719
		for(;;){
720
			n = Bread(b, buf, bufsize);
721
			if(n < 0){
722
				rerrstr(errmsg, sizeof(errmsg));
723
				s_append(s_restart(reply), errmsg);
724
				free(buf);
725
				return Retry;
726
			}
727
			if(n == 0)
728
				break;
729
			alarm(10*alarmscale);
730
			putcrnl(buf, n);
731
			nbytes += n;
732
		}
733
	}
734
	free(buf);
735
	if(!filter){
736
		if(last != '\n')
737
			dBprint("\r\n.\r\n");
738
		else
739
			dBprint(".\r\n");
740
		alarm(10*alarmscale);
741
		switch(getreply()){
742
		case 2:
743
			break;
744
		case 5:
745
			return Giveup;
746
		default:
747
			return Retry;
748
		}
749
		syslog(0, "smtp", "%s sent %d bytes to %s", s_to_c(from),
750
				nbytes, s_to_c(toline));/**/
751
	}
752
	return 0;
753
}
754
 
755
/*
756
 *  we're leaving
757
 */
758
void
759
quit(char *rv)
760
{
761
		/* 60 minutes to quit */
762
	quitting = 1;
763
	quitrv = rv;
764
	alarm(60*alarmscale);
765
	dBprint("QUIT\r\n");
766
	getreply();
767
	Bterm(&bout);
768
	Bterm(&bfile);
769
}
770
 
771
/*
772
 *  read a reply into a string, return the reply code
773
 */
774
int
775
getreply(void)
776
{
777
	char *line;
778
	int rv;
779
 
780
	reply = s_reset(reply);
781
	for(;;){
782
		line = getcrnl(reply);
783
		if(debug)
784
			Bflush(&berr);
785
		if(line == 0)
786
			return -1;
787
		if(!isdigit(line[0]) || !isdigit(line[1]) || !isdigit(line[2]))
788
			return -1;
789
		if(line[3] != '-')
790
			break;
791
	}
792
	if(debug)
793
		Bflush(&berr);
794
	rv = atoi(line)/100;
795
	return rv;
796
}
797
void
798
addhostdom(String *buf, char *host)
799
{
800
	s_append(buf, "@");
801
	s_append(buf, host);
802
}
803
 
804
/*
805
 *	Convert from `bang' to `source routing' format.
806
 *
807
 *	   a.x.y!b.p.o!c!d ->	@a.x.y:c!d@b.p.o
808
 */
809
String *
810
bangtoat(char *addr)
811
{
812
	String *buf;
813
	register int i;
814
	int j, d;
815
	char *field[128];
816
 
817
	/* parse the '!' format address */
818
	buf = s_new();
819
	for(i = 0; addr; i++){
820
		field[i] = addr;
821
		addr = strchr(addr, '!');
822
		if(addr)
823
			*addr++ = 0;
824
	}
825
	if (i==1) {
826
		s_append(buf, field[0]);
827
		return buf;
828
	}
829
 
830
	/*
831
	 *  count leading domain fields (non-domains don't count)
832
	 */
833
	for(d = 0; d<i-1; d++)
834
		if(strchr(field[d], '.')==0)
835
			break;
836
	/*
837
	 *  if there are more than 1 leading domain elements,
838
	 *  put them in as source routing
839
	 */
840
	if(d > 1){
841
		addhostdom(buf, field[0]);
842
		for(j=1; j<d-1; j++){
843
			s_append(buf, ",");
844
			s_append(buf, "@");
845
			s_append(buf, field[j]);
846
		}
847
		s_append(buf, ":");
848
	}
849
 
850
	/*
851
	 *  throw in the non-domain elements separated by '!'s
852
	 */
853
	s_append(buf, field[d]);
854
	for(j=d+1; j<=i-1; j++) {
855
		s_append(buf, "!");
856
		s_append(buf, field[j]);
857
	}
858
	if(d)
859
		addhostdom(buf, field[d-1]);
860
	return buf;
861
}
862
 
863
/*
864
 *  convert header addresses to @ format.
865
 *  if the address is a source address, and a domain is specified,
866
 *  make sure it falls in the domain.
867
 */
868
String*
869
convertheader(String *from)
870
{
871
	Field *f;
872
	Node *p, *lastp;
873
	String *a;
874
 
875
	if(!returnable(s_to_c(from))){
876
		from = s_new();
877
		s_append(from, "Postmaster");
878
		addhostdom(from, hostdomain);
879
	} else
880
	if(strchr(s_to_c(from), '@') == 0){
881
		a = username(from);
882
		if(a) {
883
			s_append(a, " <");
884
			s_append(a, s_to_c(from));
885
			addhostdom(a, hostdomain);
886
			s_append(a, ">");
887
			from = a;
888
		} else {
889
			from = s_copy(s_to_c(from));
890
			addhostdom(from, hostdomain);
891
		}
892
	} else
893
		from = s_copy(s_to_c(from));
894
	for(f = firstfield; f; f = f->next){
895
		lastp = 0;
896
		for(p = f->node; p; lastp = p, p = p->next){
897
			if(!p->addr)
898
				continue;
899
			a = bangtoat(s_to_c(p->s));
900
			s_free(p->s);
901
			if(strchr(s_to_c(a), '@') == 0)
902
				addhostdom(a, hostdomain);
903
			else if(*s_to_c(a) == '@')
904
				a = fixrouteaddr(a, p->next, lastp);
905
			p->s = a;
906
		}
907
	}
908
	return from;
909
}
910
/*
911
 *	ensure route addr has brackets around it
912
 */
913
String*
914
fixrouteaddr(String *raddr, Node *next, Node *last)
915
{
916
	String *a;
917
 
918
	if(last && last->c == '<' && next && next->c == '>')
919
		return raddr;			/* properly formed already */
920
 
921
	a = s_new();
922
	s_append(a, "<");
923
	s_append(a, s_to_c(raddr));
924
	s_append(a, ">");
925
	s_free(raddr);
926
	return a;
927
}
928
 
929
/*
930
 *  print out the parsed header
931
 */
932
int
933
printheader(void)
934
{
935
	int n, len;
936
	Field *f;
937
	Node *p;
938
	char *cp;
939
	char c[1];
940
 
941
	n = 0;
942
	for(f = firstfield; f; f = f->next){
943
		for(p = f->node; p; p = p->next){
944
			if(p->s)
945
				n += dBprint("%s", s_to_c(p->s));
946
			else {
947
				c[0] = p->c;
948
				putcrnl(c, 1);
949
				n++;
950
			}
951
			if(p->white){
952
				cp = s_to_c(p->white);
953
				len = strlen(cp);
954
				putcrnl(cp, len);
955
				n += len;
956
			}
957
			uneaten = p->end;
958
		}
959
		putcrnl("\n", 1);
960
		n++;
961
		uneaten++;		/* skip newline */
962
	}
963
	return n;
964
}
965
 
966
/*
967
 *  add a domain onto an name, return the new name
968
 */
969
char *
970
domainify(char *name, char *domain)
971
{
972
	static String *s;
973
	char *p;
974
 
975
	if(domain==0 || strchr(name, '.')!=0)
976
		return name;
977
 
978
	s = s_reset(s);
979
	s_append(s, name);
980
	p = strchr(domain, '.');
981
	if(p == 0){
982
		s_append(s, ".");
983
		p = domain;
984
	}
985
	s_append(s, p);
986
	return s_to_c(s);
987
}
988
 
989
/*
990
 *  print message observing '.' escapes and using \r\n for \n
991
 */
992
void
993
putcrnl(char *cp, int n)
994
{
995
	int c;
996
 
997
	for(; n; n--, cp++){
998
		c = *cp;
999
		if(c == '\n')
1000
			dBputc('\r');
1001
		else if(c == '.' && last=='\n')
1002
			dBputc('.');
1003
		dBputc(c);
1004
		last = c;
1005
	}
1006
}
1007
 
1008
/*
1009
 *  Get a line including a crnl into a string.  Convert crnl into nl.
1010
 */
1011
char *
1012
getcrnl(String *s)
1013
{
1014
	int c;
1015
	int count;
1016
 
1017
	count = 0;
1018
	for(;;){
1019
		c = Bgetc(&bin);
1020
		if(debug)
1021
			Bputc(&berr, c);
1022
		switch(c){
1023
		case -1:
1024
			s_append(s, "connection closed unexpectedly by remote system");
1025
			s_terminate(s);
1026
			return 0;
1027
		case '\r':
1028
			c = Bgetc(&bin);
1029
			if(c == '\n'){
1030
		case '\n':
1031
				s_putc(s, c);
1032
				if(debug)
1033
					Bputc(&berr, c);
1034
				count++;
1035
				s_terminate(s);
1036
				return s->ptr - count;
1037
			}
1038
			Bungetc(&bin);
1039
			s_putc(s, '\r');
1040
			if(debug)
1041
				Bputc(&berr, '\r');
1042
			count++;
1043
			break;
1044
		default:
1045
			s_putc(s, c);
1046
			count++;
1047
			break;
1048
		}
1049
	}
1050
}
1051
 
1052
/*
1053
 *  print out a parsed date
1054
 */
1055
int
1056
printdate(Node *p)
1057
{
1058
	int n, sep = 0;
1059
 
1060
	n = dBprint("Date: %s,", s_to_c(p->s));
1061
	for(p = p->next; p; p = p->next){
1062
		if(p->s){
1063
			if(sep == 0) {
1064
				dBputc(' ');
1065
				n++;
1066
			}
1067
			if (p->next)
1068
				n += dBprint("%s", s_to_c(p->s));
1069
			else
1070
				n += dBprint("%s", rewritezone(s_to_c(p->s)));
1071
			sep = 0;
1072
		} else {
1073
			dBputc(p->c);
1074
			n++;
1075
			sep = 1;
1076
		}
1077
	}
1078
	n += dBprint("\r\n");
1079
	return n;
1080
}
1081
 
1082
char *
1083
rewritezone(char *z)
1084
{
1085
	int mindiff;
1086
	char s;
1087
	Tm *tm;
1088
	static char x[7];
1089
 
1090
	tm = localtime(time(0));
1091
	mindiff = tm->tzoff/60;
1092
 
1093
	/* if not in my timezone, don't change anything */
1094
	if(strcmp(tm->zone, z) != 0)
1095
		return z;
1096
 
1097
	if(mindiff < 0){
1098
		s = '-';
1099
		mindiff = -mindiff;
1100
	} else
1101
		s = '+';
1102
 
1103
	sprint(x, "%c%.2d%.2d", s, mindiff/60, mindiff%60);
1104
	return x;
1105
}
1106
 
1107
/*
1108
 *  stolen from libc/port/print.c
1109
 */
1110
#define	SIZE	4096
1111
int
1112
dBprint(char *fmt, ...)
1113
{
1114
	char buf[SIZE], *out;
1115
	va_list arg;
1116
	int n;
1117
 
1118
	va_start(arg, fmt);
1119
	out = vseprint(buf, buf+SIZE, fmt, arg);
1120
	va_end(arg);
1121
	if(debug){
1122
		Bwrite(&berr, buf, (long)(out-buf));
1123
		Bflush(&berr);
1124
	}
1125
	n = Bwrite(&bout, buf, (long)(out-buf));
1126
	Bflush(&bout);
1127
	return n;
1128
}
1129
 
1130
int
1131
dBputc(int x)
1132
{
1133
	if(debug)
1134
		Bputc(&berr, x);
1135
	return Bputc(&bout, x);
1136
}