xref: /plan9-contrib/sys/src/cmd/upas/send/main.c (revision 219b2ee8daee37f4aad58d63f21287faa8e4ffdc)
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 
12 /* global to this file */
13 static String *errstring;
14 static message *mp;
15 static int interrupt;
16 static int savemail;
17 static Biobuf in;
18 
19 /* predeclared */
20 static int	send(dest *, message *, int);
21 static void	lesstedious(void);
22 static void	save_mail(message *);
23 static int	complain_mail(dest *, message *);
24 static int	pipe_mail(dest *, message *);
25 static int	cat_mail(dest *, message *);
26 static void	appaddr(String *, dest *);
27 static int	refuse(dest *, message *, char *, int);
28 static void	mkerrstring(String *, message *, dest *, dest *, char *, int);
29 static int	replymsg(String *, message *, dest *);
30 
31 void
32 main(int argc, char *argv[])
33 {
34 	dest *dp=0;
35 	int checkforward;
36 	char *base;
37 	int rv, holding;
38 
39 	srand(time(0));
40 
41 	/* process args */
42 	ARGBEGIN{
43 	case '#':
44 		nflg = 1;
45 		break;
46 	case 'x':
47 		nflg = 1;
48 		xflg = 1;
49 		break;
50 	case 'd':
51 		debug = 1;
52 		break;
53 	case 'r':
54 		rflg = 1;
55 		break;
56 	default:
57 		fprint(2, "usage: mail [-x] list-of-addresses\n");
58 		exit(1);
59 	}ARGEND
60 
61 	while(*argv)
62 		d_insert(&dp, d_new(s_copy(*argv++)));
63 
64 	if (dp == 0) {
65 		fprint(2, "usage: mail [-#] address-list\n");
66 		exit(1);
67 	}
68 
69 	/*
70 	 * get context:
71 	 *	- whether we're rmail or mail
72 	 */
73 	base = basename(argv0);
74 	checkforward = rmail = (strcmp(base, "rmail")==0) | rflg;
75 	thissys = sysname_read();
76 	altthissys = alt_sysname_read();
77 
78 	/*
79 	 *  read the mail.  If an interrupt occurs while reading, save in
80 	 *  dead.letter
81 	 */
82 	if (!nflg) {
83 		Binit(&in, 0, OREAD);
84 		if(rmail)
85 			mp = m_read(&in, rmail);
86 		else {
87 			holding = holdon();
88 			mp = m_read(&in, rmail);
89 			holdoff(holding);
90 		}
91 		if (mp == 0)
92 			exit(0);
93 		if (interrupt != 0) {
94 			save_mail(mp);
95 			exit(1);
96 		}
97 	} else {
98 		mp = m_new();
99 		default_from(mp);
100 	}
101 	errstring = s_new();
102 	getrules();
103 
104 	/*
105 	 *  If this is a gateway, translate the sender address into a local
106 	 *  address.  This only happens if mail to the local address is
107 	 *  forwarded to the sender.
108 	 */
109 	gateway(mp);
110 
111 	/*
112 	 *  Protect against shell characters in the sender name for
113 	 *  security reasons.
114 	 */
115 	USE(s_restart(mp->sender));
116 	if (shellchars(s_to_c(mp->sender)))
117 		mp->replyaddr = s_copy("postmaster");
118 	else
119 		mp->replyaddr = s_clone(mp->sender);
120 	USE(s_restart(mp->replyaddr));
121 
122 	/*
123 	 *  reject messages that are too long.  We don't do it earlier
124 	 *  in m_read since we haven't set up enough things yet.
125 	 */
126 	if(mp->size < 0)
127 		exit(refuse(dp, mp, "message too long", 0));
128 
129 	rv = send(dp, mp, checkforward);
130 	if(savemail)
131 		save_mail(mp);
132 	if(mp)
133 		m_free(mp);
134 	exit(rv);
135 }
136 
137 
138 
139 /* send a message to a list of sites */
140 static int
141 send(dest *destp, message *mp, int checkforward)
142 {
143 	dest *dp;		/* destination being acted upon */
144 	dest *bound;		/* bound destinations */
145 	int errors=0;
146 	static int forked;
147 
148 	/* bind the destinations to actions */
149 	bound = up_bind(destp, mp, checkforward);
150 
151 	/* loop through and execute commands */
152 	for (dp = d_rm(&bound); dp != 0; dp = d_rm(&bound)) {
153 		switch (dp->status) {
154 		case d_cat:
155 			errors += cat_mail(dp, mp);
156 			break;
157 		case d_pipeto:
158 		case d_pipe:
159 			if (!rmail && !nflg && !forked) {
160 				forked = 1;
161 				lesstedious();
162 			}
163 			errors += pipe_mail(dp, mp);
164 			break;
165 		default:
166 			errors += complain_mail(dp, mp);
167 			break;
168 		}
169 	}
170 
171 	return errors;
172 }
173 
174 /* avoid user tedium (as Mike Lesk said in a previous version) */
175 static void
176 lesstedious(void)
177 {
178 	int i;
179 
180 	if(debug)
181 		return;
182 
183 	switch(fork()){
184 	case -1:
185 		break;
186 	case 0:
187 		rfork(RFENVG|RFNAMEG|RFNOTEG);
188 		for(i=0; i<nsysfile; i++)
189 			close(i);
190 		savemail = 0;
191 		break;
192 	default:
193 		exit(0);
194 	}
195 }
196 
197 
198 /* save the mail */
199 static void
200 save_mail(message *mp)
201 {
202 	Biobuf *fp;
203 	String *file;
204 	static saved = 0;
205 
206 	file = s_new();
207 	mboxpath("dead.letter", getlog(), file, 0);
208 	if ((fp = sysopen(s_to_c(file), "cA", 0660)) == 0)
209 		return;
210 	m_bprint(mp, fp);
211 	sysclose(fp);
212 	fprint(2, "saved in %s\n", s_to_c(file));
213 	s_free(file);
214 }
215 
216 /* remember the interrupt happened */
217 /* dispose of incorrect addresses */
218 static int
219 complain_mail(dest *dp, message *mp)
220 {
221 	char *msg;
222 
223 	switch (dp->status) {
224 	case d_undefined:
225 		msg = "Invalid address"; /* a little different, for debugging */
226 		break;
227 	case d_syntax:
228 		msg = "invalid address";
229 		break;
230 	case d_unknown:
231 		msg = "unknown user";
232 		break;
233 	case d_eloop:
234 	case d_loop:
235 		msg = "forwarding loop";
236 		break;
237 	case d_noforward:
238 		if(dp->pstat && *s_to_c(dp->repl2))
239 			return refuse(dp, mp, s_to_c(dp->repl2), dp->pstat);
240 		else
241 			msg = "destination unknown or forwarding disallowed";
242 		break;
243 	case d_pipe:
244 		msg = "broken pipe";
245 		break;
246 	case d_cat:
247 		msg = "broken cat";
248 		break;
249 	case d_translate:
250 		if(dp->pstat && *s_to_c(dp->repl2))
251 			return refuse(dp, mp, s_to_c(dp->repl2), dp->pstat);
252 		else
253 			msg = "name translation failed";
254 		break;
255 	case d_alias:
256 		msg = "broken alias";
257 		break;
258 	case d_badmbox:
259 		msg = "corrupted mailbox";
260 		break;
261 	case d_resource:
262 		msg = "out of some resource.  Try again later.";
263 		break;
264 	default:
265 		msg = "unknown d_";
266 		break;
267 	}
268 	if (nflg) {
269 		print("%s: %s\n", msg, s_to_c(dp->addr));
270 		return 0;
271 	}
272 	return refuse(dp, mp, msg, 0);
273 }
274 
275 /* dispose of remote addresses */
276 static int
277 pipe_mail(dest *dp, message *mp)
278 {
279 	String *file;
280 	dest *next, *list=0;
281 	String *cmd;
282 	process *pp;
283 	int status, none;
284 	String *errstring=s_new();
285 
286 	none = dp->status == d_pipeto;
287 	/*
288 	 *  collect the arguments
289 	 */
290 	file = s_new();
291 	abspath(s_to_c(dp->addr), MAILROOT, file);
292 	next = d_rm_same(&dp);
293 	if(xflg)
294 		cmd = s_new();
295 	else
296 		cmd = s_clone(s_restart(next->repl1));
297 	for(; next != 0; next = d_rm_same(&dp)){
298 		if(xflg){
299 			s_append(cmd, s_to_c(next->addr));
300 			s_append(cmd, "\n");
301 		} else {
302 			if (next->repl2 != 0) {
303 				s_append(cmd, " ");
304 				s_append(cmd, s_to_c(next->repl2));
305 			}
306 		}
307 		d_insert(&list, next);
308 	}
309 
310 	if (nflg) {
311 		if(xflg)
312 			print("%s", s_to_c(cmd));
313 		else
314 			print("%s\n", s_to_c(cmd));
315 		s_free(cmd);
316 		s_free(file);
317 		return 0;
318 	}
319 
320 	/*
321 	 *  run the process
322 	 */
323 	pp = proc_start(s_to_c(cmd), instream(), 0, outstream(), 1, none);
324 	if(pp==0 || pp->std[0]==0 || pp->std[2]==0)
325 		return refuse(list, mp, "out of processes, pipes, or memory", 0);
326 	m_print(mp, pp->std[0]->fp, thissys, 0);
327 	stream_free(pp->std[0]);
328 	pp->std[0] = 0;
329 	while(s_read_line(pp->std[2]->fp, errstring))
330 		;
331 	status = proc_wait(pp);
332 	proc_free(pp);
333 	s_free(cmd);
334 
335 	/*
336 	 *  return status
337 	 */
338 	if (status != 0)
339 		return refuse(list, mp, s_to_c(errstring), status);
340 	loglist(list, mp, "remote");
341 	return 0;
342 }
343 
344 /* dispose of local addresses */
345 static int
346 cat_mail(dest *dp, message *mp)
347 {
348 	Biobuf *fp;
349 	char *rcvr, *cp;
350 	Lock *l;
351 	String *tmp;
352 
353 	if (nflg) {
354 		if(!xflg)
355 			print("cat >> %s\n", s_to_c(dp->repl1));
356 		else
357 			print("%s\n", s_to_c(dp->addr));
358 		return 0;
359 	}
360 	l = lock(s_to_c(dp->repl1));
361 	if(l == 0)
362 		return refuse(dp, mp, "can't lock mail file", 0);
363 	fp = sysopen(s_to_c(dp->repl1), "cal", MBOXMODE);
364 	if (fp == 0){
365 		tmp = s_append(0, s_to_c(dp->repl1));
366 		s_append(tmp, ".tmp");
367 		fp = sysopen(s_to_c(tmp), "cal", MBOXMODE);
368 		if(fp == 0){
369 			unlock(l);
370 			return refuse(dp, mp, "mail file cannot be opened", 0);
371 		}
372 		syslog(0, "mail", "error: used %s", s_to_c(tmp));
373 		s_free(tmp);
374 	}
375 	if(m_print(mp, fp, (char *)0, 1) < 0
376 	|| Bprint(fp, "\n") < 0
377 	|| Bflush(fp) < 0){
378 		sysclose(fp);
379 		unlock(l);
380 		return refuse(dp, mp, "error writing mail file", 0);
381 	}
382 	sysclose(fp);
383 	unlock(l);
384 	rcvr = s_to_c(dp->addr);
385 	if(cp = strrchr(rcvr, '!'))
386 		rcvr = cp+1;
387 	logdelivery(dp, rcvr, mp);
388 	return 0;
389 }
390 
391 static void
392 appaddr(String *sp, dest *dp)
393 {
394 	dest *parent;
395 
396 	if (dp->parent != 0) {
397 		for(parent=dp->parent; parent->parent!=0; parent=parent->parent)
398 			;
399 		s_append(sp, s_to_c(parent->addr));
400 		s_append(sp, "' alias `");
401 	}
402 	s_append(sp, s_to_c(dp->addr));
403 }
404 
405 /* reject delivery */
406 static int
407 refuse(dest *list, message *mp, char *cp, int status)
408 {
409 	String *errstring=s_new();
410 	dest *dp;
411 	int rv;
412 
413 	dp = d_rm(&list);
414 	mkerrstring(errstring, mp, dp, list, cp, status);
415 
416 	/*
417 	 * if on a tty just report the error.  Otherwise send mail
418 	 * reporting the error.  N.B. To avoid mail loops, don't
419 	 * send mail reporting a failure of mail to reach the postmaster.
420 	 */
421 	if (!rmail) {
422 		fprint(2, "%s\n", s_to_c(errstring));
423 		savemail = 1;
424 		rv = 1;
425 	} else {
426 		if (strcmp(s_to_c(mp->replyaddr), "postmaster")!=0)
427 			rv = replymsg(errstring, mp, dp);
428 		else
429 			rv = 1;
430 	}
431 	logrefusal(dp, mp, s_to_c(errstring));
432 	s_free(errstring);
433 	return rv;
434 }
435 
436 /* make the error message */
437 static void
438 mkerrstring(String *errstring, message *mp, dest *dp, dest *list, char *cp, int status)
439 {
440 	dest *next;
441 	char smsg[64];
442 
443 	/* list all aliases */
444 	s_append(errstring, "Mail to `");
445 	appaddr(errstring, dp);
446 	for(next = d_rm(&list); next != 0; next = d_rm(&list)) {
447 		s_append(errstring, "', '");
448 		appaddr(errstring, next);
449 		d_insert(&dp, next);
450 	}
451 	s_append(errstring, "' from '");
452 	s_append(errstring, s_to_c(mp->sender));
453 	s_append(errstring, "' failed.\n");
454 
455 	/* >> and | deserve different flavored messages */
456 	switch(dp->status) {
457 	case d_pipe:
458 		s_append(errstring, "The mailer `");
459 		s_append(errstring, s_to_c(dp->repl1));
460 		sprint(smsg, "' returned error status %x.\n", status);
461 		s_append(errstring, smsg);
462 		s_append(errstring, "The error message was:\n");
463 		s_append(errstring, cp);
464 		break;
465 	default:
466 		s_append(errstring, "The error message was:\n");
467 		s_append(errstring, cp);
468 		break;
469 	}
470 }
471 
472 /*
473  *  reply with up to 1024 characters of the
474  *  original message
475  */
476 static int
477 replymsg(String *errstring, message *mp, dest *dp)
478 {
479 	message *refp = m_new();
480 	dest *ndp;
481 	char *rcvr;
482 	int rv;
483 
484 	rcvr = dp->status==d_eloop ? "postmaster" : s_to_c(mp->replyaddr);
485 	ndp = d_new(s_copy(rcvr));
486 	s_append(refp->sender, "postmaster");
487 	s_append(refp->replyaddr, "postmaster");
488 	s_append(refp->date, thedate());
489 	s_append(refp->body, s_to_c(errstring));
490 	s_append(refp->body, "\nThe message began:\n");
491 	s_nappend(refp->body, s_to_c(mp->body), 8*1024);
492 	refp->size = strlen(s_to_c(refp->body));
493 	rv = send(ndp, refp, 0);
494 	m_free(refp);
495 	d_free(ndp);
496 	return rv;
497 }
498