1 #include "common.h"
2 #include "send.h"
3 
4 /* globals to all files */
5 int rmail;
6 char *thissys, *altthissys;
7 int nflg;
8 int xflg;
9 int debug;
10 int rflg;
11 int iflg = 1;
12 int nosummary;
13 
14 /* global to this file */
15 static String *errstring;
16 static message *mp;
17 static int interrupt;
18 static int savemail;
19 static Biobuf in;
20 static int forked;
21 static int add822headers = 1;
22 static String *arglist;
23 
24 /* predeclared */
25 static int	send(dest *, message *, int);
26 static void	lesstedious(void);
27 static void	save_mail(message *);
28 static int	complain_mail(dest *, message *);
29 static int	pipe_mail(dest *, message *);
30 static void	appaddr(String *, dest *);
31 static void	mkerrstring(String *, message *, dest *, dest *, char *, int);
32 static int	replymsg(String *, message *, dest *);
33 static int	catchint(void*, char*);
34 
35 void
usage(void)36 usage(void)
37 {
38 	fprint(2, "usage: mail [-birtx] list-of-addresses\n");
39 	exits("usage");
40 }
41 
42 void
main(int argc,char * argv[])43 main(int argc, char *argv[])
44 {
45 	dest *dp=0;
46 	int checkforward;
47 	char *base;
48 	int rv;
49 
50 	/* process args */
51 	ARGBEGIN{
52 	case '#':
53 		nflg = 1;
54 		break;
55 	case 'b':
56 		add822headers = 0;
57 		break;
58 	case 'x':
59 		nflg = 1;
60 		xflg = 1;
61 		break;
62 	case 'd':
63 		debug = 1;
64 		break;
65 	case 'i':
66 		iflg = 0;
67 		break;
68 	case 'r':
69 		rflg = 1;
70 		break;
71 	default:
72 		usage();
73 	}ARGEND
74 
75 	while(*argv){
76 		if(shellchars(*argv)){
77 			fprint(2, "illegal characters in destination\n");
78 			exits("syntax");
79 		}
80 		d_insert(&dp, d_new(s_copy(*argv++)));
81 	}
82 
83 	if (dp == 0)
84 		usage();
85 	arglist = d_to(dp);
86 
87 	/*
88 	 * get context:
89 	 *	- whether we're rmail or mail
90 	 */
91 	base = basename(argv0);
92 	checkforward = rmail = (strcmp(base, "rmail")==0) | rflg;
93 	thissys = sysname_read();
94 	altthissys = alt_sysname_read();
95 	if(rmail)
96 		add822headers = 0;
97 
98 	/*
99 	 *  read the mail.  If an interrupt occurs while reading, save in
100 	 *  dead.letter
101 	 */
102 	if (!nflg) {
103 		Binit(&in, 0, OREAD);
104 		if(!rmail)
105 			atnotify(catchint, 1);
106 		mp = m_read(&in, rmail, !iflg);
107 		if (mp == 0)
108 			exit(0);
109 		if (interrupt != 0) {
110 			save_mail(mp);
111 			exit(1);
112 		}
113 	} else {
114 		mp = m_new();
115 		if(default_from(mp) < 0){
116 			fprint(2, "%s: can't determine login name\n", argv0);
117 			exit(1);
118 		}
119 	}
120 	errstring = s_new();
121 	getrules();
122 
123 	/*
124 	 *  If this is a gateway, translate the sender address into a local
125 	 *  address.  This only happens if mail to the local address is
126 	 *  forwarded to the sender.
127 	 */
128 	gateway(mp);
129 
130 	/*
131 	 *  Protect against shell characters in the sender name for
132 	 *  security reasons.
133 	 */
134 	mp->sender = escapespecial(mp->sender);
135 	if (shellchars(s_to_c(mp->sender)))
136 		mp->replyaddr = s_copy("postmaster");
137 	else
138 		mp->replyaddr = s_clone(mp->sender);
139 
140 	/*
141 	 *  reject messages that have been looping for too long
142 	 */
143 	if(mp->received > 32)
144 		exit(refuse(dp, mp, "possible forward loop", 0, 0));
145 
146 	/*
147 	 *  reject messages that are too long.  We don't do it earlier
148 	 *  in m_read since we haven't set up enough things yet.
149 	 */
150 	if(mp->size < 0)
151 		exit(refuse(dp, mp, "message too long", 0, 0));
152 
153 	rv = send(dp, mp, checkforward);
154 	if(savemail)
155 		save_mail(mp);
156 	if(mp)
157 		m_free(mp);
158 	exit(rv);
159 }
160 
161 /* send a message to a list of sites */
162 static int
send(dest * destp,message * mp,int checkforward)163 send(dest *destp, message *mp, int checkforward)
164 {
165 	dest *dp;		/* destination being acted upon */
166 	dest *bound;		/* bound destinations */
167 	int errors=0;
168 
169 	/* bind the destinations to actions */
170 	bound = up_bind(destp, mp, checkforward);
171 	if(add822headers && mp->haveto == 0){
172 		if(nosummary)
173 			mp->to = d_to(bound);
174 		else
175 			mp->to = arglist;
176 	}
177 
178 	/* loop through and execute commands */
179 	for (dp = d_rm(&bound); dp != 0; dp = d_rm(&bound)) {
180 		switch (dp->status) {
181 		case d_cat:
182 			errors += cat_mail(dp, mp);
183 			break;
184 		case d_pipeto:
185 		case d_pipe:
186 			if (!rmail && !nflg && !forked) {
187 				forked = 1;
188 				lesstedious();
189 			}
190 			errors += pipe_mail(dp, mp);
191 			break;
192 		default:
193 			errors += complain_mail(dp, mp);
194 			break;
195 		}
196 	}
197 
198 	return errors;
199 }
200 
201 /* avoid user tedium (as Mike Lesk said in a previous version) */
202 static void
lesstedious(void)203 lesstedious(void)
204 {
205 	int i;
206 
207 	if(debug)
208 		return;
209 
210 	switch(fork()){
211 	case -1:
212 		break;
213 	case 0:
214 		sysdetach();
215 		for(i=0; i<3; i++)
216 			close(i);
217 		savemail = 0;
218 		break;
219 	default:
220 		exit(0);
221 	}
222 }
223 
224 
225 /* save the mail */
226 static void
save_mail(message * mp)227 save_mail(message *mp)
228 {
229 	Biobuf *fp;
230 	String *file;
231 
232 	file = s_new();
233 	deadletter(file);
234 	fp = sysopen(s_to_c(file), "cAt", 0660);
235 	if (fp == 0)
236 		return;
237 	m_bprint(mp, fp);
238 	sysclose(fp);
239 	fprint(2, "saved in %s\n", s_to_c(file));
240 	s_free(file);
241 }
242 
243 /* remember the interrupt happened */
244 
245 static int
catchint(void * a,char * msg)246 catchint(void *a, char *msg)
247 {
248 	USED(a);
249 	if(strstr(msg, "interrupt") || strstr(msg, "hangup")) {
250 		interrupt = 1;
251 		return 1;
252 	}
253 	return 0;
254 }
255 
256 /* dispose of incorrect addresses */
257 static int
complain_mail(dest * dp,message * mp)258 complain_mail(dest *dp, message *mp)
259 {
260 	char *msg;
261 
262 	switch (dp->status) {
263 	case d_undefined:
264 		msg = "Invalid address"; /* a little different, for debugging */
265 		break;
266 	case d_syntax:
267 		msg = "invalid address";
268 		break;
269 	case d_unknown:
270 		msg = "unknown user";
271 		break;
272 	case d_eloop:
273 	case d_loop:
274 		msg = "forwarding loop";
275 		break;
276 	case d_noforward:
277 		if(dp->pstat && *s_to_c(dp->repl2))
278 			return refuse(dp, mp, s_to_c(dp->repl2), dp->pstat, 0);
279 		else
280 			msg = "destination unknown or forwarding disallowed";
281 		break;
282 	case d_pipe:
283 		msg = "broken pipe";
284 		break;
285 	case d_cat:
286 		msg = "broken cat";
287 		break;
288 	case d_translate:
289 		if(dp->pstat && *s_to_c(dp->repl2))
290 			return refuse(dp, mp, s_to_c(dp->repl2), dp->pstat, 0);
291 		else
292 			msg = "name translation failed";
293 		break;
294 	case d_alias:
295 		msg = "broken alias";
296 		break;
297 	case d_badmbox:
298 		msg = "corrupted mailbox";
299 		break;
300 	case d_resource:
301 		return refuse(dp, mp, "out of some resource.  Try again later.", 0, 1);
302 	default:
303 		msg = "unknown d_";
304 		break;
305 	}
306 	if (nflg) {
307 		print("%s: %s\n", msg, s_to_c(dp->addr));
308 		return 0;
309 	}
310 	return refuse(dp, mp, msg, 0, 0);
311 }
312 
313 /* dispose of remote addresses */
314 static int
pipe_mail(dest * dp,message * mp)315 pipe_mail(dest *dp, message *mp)
316 {
317 	dest *next, *list=0;
318 	String *cmd;
319 	process *pp;
320 	int status;
321 	char *none;
322 	String *errstring=s_new();
323 
324 	if (dp->status == d_pipeto)
325 		none = "none";
326 	else
327 		none = 0;
328 	/*
329 	 *  collect the arguments
330 	 */
331 	next = d_rm_same(&dp);
332 	if(xflg)
333 		cmd = s_new();
334 	else
335 		cmd = s_clone(next->repl1);
336 	for(; next != 0; next = d_rm_same(&dp)){
337 		if(xflg){
338 			s_append(cmd, s_to_c(next->addr));
339 			s_append(cmd, "\n");
340 		} else {
341 			if (next->repl2 != 0) {
342 				s_append(cmd, " ");
343 				s_append(cmd, s_to_c(next->repl2));
344 			}
345 		}
346 		d_insert(&list, next);
347 	}
348 
349 	if (nflg) {
350 		if(xflg)
351 			print("%s", s_to_c(cmd));
352 		else
353 			print("%s\n", s_to_c(cmd));
354 		s_free(cmd);
355 		return 0;
356 	}
357 
358 	/*
359 	 *  run the process
360 	 */
361 	pp = proc_start(s_to_c(cmd), instream(), 0, outstream(), 1, none);
362 	if(pp==0 || pp->std[0]==0 || pp->std[2]==0)
363 		return refuse(list, mp, "out of processes, pipes, or memory", 0, 1);
364 	pipesig(0);
365 	m_print(mp, pp->std[0]->fp, thissys, 0);
366 	pipesigoff();
367 	stream_free(pp->std[0]);
368 	pp->std[0] = 0;
369 	while(s_read_line(pp->std[2]->fp, errstring))
370 		;
371 	status = proc_wait(pp);
372 	proc_free(pp);
373 	s_free(cmd);
374 
375 	/*
376 	 *  return status
377 	 */
378 	if (status != 0)
379 		return refuse(list, mp, s_to_c(errstring), status, 0);
380 	loglist(list, mp, "remote");
381 	return 0;
382 }
383 
384 static void
appaddr(String * sp,dest * dp)385 appaddr(String *sp, dest *dp)
386 {
387 	dest *parent;
388 	String *s;
389 
390 	if (dp->parent != 0) {
391 		for(parent=dp->parent; parent->parent!=0; parent=parent->parent)
392 			;
393 		s = unescapespecial(s_clone(parent->addr));
394 		s_append(sp, s_to_c(s));
395 		s_free(s);
396 		s_append(sp, "' alias `");
397 	}
398 	s = unescapespecial(s_clone(dp->addr));
399 	s_append(sp, s_to_c(s));
400 	s_free(s);
401 }
402 
403 /*
404  *  reject delivery
405  *
406  *  returns	0	- if mail has been disposed of
407  *		other	- if mail has not been disposed
408  */
409 int
refuse(dest * list,message * mp,char * cp,int status,int outofresources)410 refuse(dest *list, message *mp, char *cp, int status, int outofresources)
411 {
412 	String *errstring=s_new();
413 	dest *dp;
414 	int rv;
415 
416 	dp = d_rm(&list);
417 	mkerrstring(errstring, mp, dp, list, cp, status);
418 
419 	/*
420 	 *  log first in case we get into trouble
421 	 */
422 	logrefusal(dp, mp, s_to_c(errstring));
423 
424 	/*
425 	 *  bulk mail is never replied to, if we're out of resources,
426 	 *  let the sender try again
427 	 */
428 	if(rmail){
429 		/* accept it or request a retry */
430 		if(outofresources){
431 			fprint(2, "Mail %s\n", s_to_c(errstring));
432 			rv = 1;					/* try again later */
433 		} else if(mp->bulk)
434 			rv = 0;					/* silently discard bulk */
435 		else
436 			rv = replymsg(errstring, mp, dp);	/* try later if we can't reply */
437 	} else {
438 		/* aysnchronous delivery only happens if !rmail */
439 		if(forked){
440 			/*
441 			 *  if spun off for asynchronous delivery, we own the mail now.
442 			 *  return it or dump it on the floor.  rv really doesn't matter.
443 			 */
444 			rv = 0;
445 			if(!outofresources && !mp->bulk)
446 				replymsg(errstring, mp, dp);
447 		} else {
448 			fprint(2, "Mail %s\n", s_to_c(errstring));
449 			savemail = 1;
450 			rv = 1;
451 		}
452 	}
453 
454 	s_free(errstring);
455 	return rv;
456 }
457 
458 /* make the error message */
459 static void
mkerrstring(String * errstring,message * mp,dest * dp,dest * list,char * cp,int status)460 mkerrstring(String *errstring, message *mp, dest *dp, dest *list, char *cp, int status)
461 {
462 	dest *next;
463 	char smsg[64];
464 	String *sender;
465 
466 	sender = unescapespecial(s_clone(mp->sender));
467 
468 	/* list all aliases */
469 	s_append(errstring, " from '");
470 	s_append(errstring, s_to_c(sender));
471 	s_append(errstring, "'\nto '");
472 	appaddr(errstring, dp);
473 	for(next = d_rm(&list); next != 0; next = d_rm(&list)) {
474 		s_append(errstring, "'\nand '");
475 		appaddr(errstring, next);
476 		d_insert(&dp, next);
477 	}
478 	s_append(errstring, "'\nfailed with error '");
479 	s_append(errstring, cp);
480 	s_append(errstring, "'.\n");
481 
482 	/* >> and | deserve different flavored messages */
483 	switch(dp->status) {
484 	case d_pipe:
485 		s_append(errstring, "The mailer `");
486 		s_append(errstring, s_to_c(dp->repl1));
487 		sprint(smsg, "' returned error status %x.\n\n", status);
488 		s_append(errstring, smsg);
489 		break;
490 	}
491 
492 	s_free(sender);
493 }
494 
495 /*
496  *  create a new boundary
497  */
498 static String*
mkboundary(void)499 mkboundary(void)
500 {
501 	char buf[32];
502 	int i;
503 	static int already;
504 
505 	if(already == 0){
506 		srand((time(0)<<16)|getpid());
507 		already = 1;
508 	}
509 	strcpy(buf, "upas-");
510 	for(i = 5; i < sizeof(buf)-1; i++)
511 		buf[i] = 'a' + nrand(26);
512 	buf[i] = 0;
513 	return s_copy(buf);
514 }
515 
516 /*
517  *  reply with up to 1024 characters of the
518  *  original message
519  */
520 static int
replymsg(String * errstring,message * mp,dest * dp)521 replymsg(String *errstring, message *mp, dest *dp)
522 {
523 	message *refp = m_new();
524 	dest *ndp;
525 	char *rcvr;
526 	int rv;
527 	String *boundary;
528 
529 	boundary = mkboundary();
530 
531 	refp->bulk = 1;
532 	refp->rfc822headers = 1;
533 	rcvr = dp->status==d_eloop ? "postmaster" : s_to_c(mp->replyaddr);
534 	ndp = d_new(s_copy(rcvr));
535 	s_append(refp->sender, "postmaster");
536 	s_append(refp->replyaddr, "/dev/null");
537 	s_append(refp->date, thedate());
538 	refp->haveto = 1;
539 	s_append(refp->body, "To: ");
540 	s_append(refp->body, rcvr);
541 	s_append(refp->body, "\n");
542 	s_append(refp->body, "Subject: bounced mail\n");
543 	s_append(refp->body, "MIME-Version: 1.0\n");
544 	s_append(refp->body, "Content-Type: multipart/mixed;\n");
545 	s_append(refp->body, "\tboundary=\"");
546 	s_append(refp->body, s_to_c(boundary));
547 	s_append(refp->body, "\"\n");
548 	s_append(refp->body, "Content-Disposition: inline\n");
549 	s_append(refp->body, "\n");
550 	s_append(refp->body, "This is a multi-part message in MIME format.\n");
551 	s_append(refp->body, "--");
552 	s_append(refp->body, s_to_c(boundary));
553 	s_append(refp->body, "\n");
554 	s_append(refp->body, "Content-Disposition: inline\n");
555 	s_append(refp->body, "Content-Type: text/plain; charset=\"US-ASCII\"\n");
556 	s_append(refp->body, "Content-Transfer-Encoding: 7bit\n");
557 	s_append(refp->body, "\n");
558 	s_append(refp->body, "The attached mail");
559 	s_append(refp->body, s_to_c(errstring));
560 	s_append(refp->body, "--");
561 	s_append(refp->body, s_to_c(boundary));
562 	s_append(refp->body, "\n");
563 	s_append(refp->body, "Content-Type: message/rfc822\n");
564 	s_append(refp->body, "Content-Disposition: inline\n\n");
565 	s_append(refp->body, s_to_c(mp->body));
566 	s_append(refp->body, "--");
567 	s_append(refp->body, s_to_c(boundary));
568 	s_append(refp->body, "--\n");
569 
570 	refp->size = s_len(refp->body);
571 	rv = send(ndp, refp, 0);
572 	m_free(refp);
573 	d_free(ndp);
574 	return rv;
575 }
576