plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

main.c (11855B)


      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
     36 usage(void)
     37 {
     38 	fprint(2, "usage: mail [-birtx] list-of-addresses\n");
     39 	exits("usage");
     40 }
     41 
     42 void
     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
    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
    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
    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
    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
    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
    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
    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
    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
    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*
    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
    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 }