[net.unix-wizards] Bugs in 4.1bsd mailers

jim (01/04/83)

I have noticed several bugs in mail and rmail as delivered with 4.1bsd.

In rmail.c, where the command line for delivermail is built, we have:

	sprintf(cmd, "%s -r%s %s", MAILER, from, to);

I think this should be:

	sprintf(cmd, "%s -em -i -r%s %s", MAILER, from, to);

The "-em" says to mail back errors.  Without this, errors seem to get
mailed back anyway, I assume because delivermail is smart enough to
realize that the mail is coming from rmail.  Sometimes the returned
mail is missing the line that says why the mail was returned, and I
think this is because the "-em" is missing, but I haven't verified
this.  The "-i" flag tells delivermail to ignore lines containing just
a single ".".

In mail.c, after uucp mail is handed off to uux, we have the curious
combination:

	pclose(rmf);
	exit(0);

What this does is ignore any errors from uux.  Our uux returns 0 for no
errors, 1 for system type failures, and 101 (where did this number come
from?) if the host is not in the L.sys file.  It seems to me that a
better exit from mail would be something like:

#include <sysexits.h>
...
	switch (pclose(rmf)) {
	case 0:			/* Normal exit */
		exit(0);
	case (101 << 8):	/* Unknown host */
		exit(EX_NOHOST);
	default:		/* Unknown error from uux */
		exit(EX_OSERR);

Better yet, fix uux so it returns EX_* codes, and have mail.c do:

	exit(pclose(rmf) >> 8);

Before you run out with editor in hand to do battle with the sources, I
should mention that I have not tried this change.  I discovered the
problem when I noticed that our local version of the uucp mailer (we
don't use /bin/mail directly for this) returns mail addressed to
unknown hosts, but that /bin/mail just drops it.

Does anyone know why the handling of exit codes in mail.c is so sloppy?
Eric Allman (who wrote delivermail) seems to be much too careful to
allow something like this to happen.

dmmartindale (01/04/83)

Several comments about uw-beaver!jim's suggestions on how to handle
errors in /bin/mail:
Do NOT change /bin/mail to do "exit(pclose(rmf) >> 8);"
If uux dies from any sort of signal, this fact is indicated by the lower
8 bits of the return status and the upper 8 bits are zero, and you DON'T
want to indicate normal exit in this case.  The switch statement he
suggested is much better.  Unfortunately, it still won't return the proper
exit code to delivermail, since this exit is from a forked child of /bin/mail,
and is waited for by the parent who only checks the status against zero
(look at the wait() not far above the exit()).  Considerably more work would
be required to have the parent pass back the child's exit status.  Part of
the problem is that /bin/mail is written to handle multiple recipients so it
can't pass back a return code indicating what happened for each of them,
but delivermail makes one call of the deliverer for each recipient and wants
a meaningful code back.  Anyway, I'm currently running code which does:

	exit(pclose(rmf) != 0);

This at least causes delivermail to save a copy of the letter or mail
it back, whichever is appropriate.
While you're attacking /bin/mail, you should also wrap a "#ifndef DELIVERMAIL"
around the code which saves mail in dead.letter, as:

#ifndef DELIVERMAIL
	if (error) {
		setuid(getuid());
		malf = fopen(dead, "w");
		if (malf == NULL) {
			fprintf(stdout, "mail: cannot open %s\n", dead);
			fclose(tmpf);
			return;
		}
		copylet(0, malf, ZAP);
		fclose(malf);
		fprintf(stdout, "Mail saved in %s\n", dead);
	}
#endif

This allows delivermail to handle undeliverable mail, since it's cleverer
about what to do with it.  Once you've fixed mail to return the result
of pclose() properly you will sometimes get two copies of a letter saved in
dead.letter (for local mail) because both /bin/mail and delivermail save it.
Also, /bin/mail has always tried to save a copy of the letter in
/usr/spool/uucp, or somewhere similar, when incoming remote mail fails - this
is silly.  Much better to just return an exit status and let delivermail
worry about what to do with the error - it knows how to mail it back.

I suspect /bin/mail is sloppy because Eric didn't write it - it looks
like standard v7 mail hacked up a bit.

	Dave Martindale

jim (01/05/83)

    From: uw-beave!microsof!decvax!utzoo!watmath!watcgl!dmmartindale

    Several comments about uw-beaver!jim's suggestions on how to handle
    errors in /bin/mail: Do NOT change /bin/mail to do
    "exit(pclose(rmf) >> 8);" If uux dies from any sort of signal, this
    fact is indicated by the lower 8 bits of the return status and the
    upper 8 bits are zero, and you DON'T want to indicate normal exit
    in this case. ...

Dave Martindale's assessment of /bin/mail looks right to me.  As I
said, I wasn't advocating that you go right out and change your
sources, since I hadn't tested the modifications I proposed.  I was
just curious as to why the code was wrong and whether anyone had fixed
it.

The moral is that /bin/mail tries to do too many things and isn't very
good at any of them.  It is a user interface, a local mailer, and a
uucp mailer.  What we do is use /bin/mail for local delivery and a
different program for uucp delivery.  The uucp mailer checks to make
sure a site exists before doing the uux, and checks the exit status on
return just to make sure.