The program that is driven by the prog
delivery agent may be a compiled
executable binary, a shell script, or even a perl(1) script.
The limitation on the kind of program that may be run is made by
the sh(1) shell (if sh -c
is used in the A=
)
or by execve(2) (if it is launched directly from the P=
).
You need to read the manuals on your system to determine your
limitations. For example, not all versions of sh(1) allow
constructs like the following in scripts:
#!/usr/local/bin/perl
When this appears as the first line of a script,
the #!
tells sh(1) or execve(2) to
run the program whose pathname follows, to execute the commands
in the script.
[6]
[6] Not all versions of UNIX support this feature, and on some of those that do support it, only a few shells are supported.
In writing a program for mail delivery using the prog
delivery agent, some unexpected problems can arise.
We will illustrate, using fragments from a Bourne shell script.
When sendmail gathers its list of recipients, it views a program to run as just another recipient. Before performing any delivery, it sorts the list of recipients and discards any duplicates. Ordinarily, this is just the behavior that is desired, but discarding duplicate programs from the aliases(5) file [7] can cause some users to lose mail. To illustrate, consider a program that notifies the system administrator that mail has arrived for a retired user:
[7] Under V8 sendmail this is no longer a problem for duplicate programs listed in ~/.forward files (see Section 25.7.4) but still is a problem for aliases. The solution that sendmail uses is to internally append the uid of the ~/.forward file's owner to the program name, thus making the program name more unique.
#!/bin/sh /usr/ucb/mail -s gone postmaster
This script reads everything (the mail message) from its standard
input and feeds what it reads to the /usr/ucb/mail program.
The command-line arguments to mail are
a subject line of gone
and a
recipient of postmaster
. Now consider two aliases that use
this program:
george: "|/usr/local/bin/gone" ben: "|/usr/local/bin/gone"
When mail is sent to both george
and ben
, sendmail
aliases each to the program |/usr/local/bin/gone
. But since
both the two addresses are identical, sendmail discards one.
To avoid this problem (which is most common in user ~/.forward files), design all delivery programs to require at least one unique argument. For example, the above program should be rewritten to require the user's name as an argument:
#!/bin/sh if [ ${#} -ne 2 ]; then echo $0 needs a user name. exit fi /usr/ucb/mail -s "$1 gone" postmaster
By requiring a username as an argument, the once-faulty aliases are made unique:
george: "|/usr/local/bin/gone george" ben: "|/usr/local/bin/gone ben"
Although the program paths are still the same, the addresses (name and arguments together) are different, and neither is discarded.
The sendmail program expects its A=
programs to exit
with reasonable exit(2) values. The values that it expects are
listed in <sysexits.h>. Exiting with unexpected values
causes sendmail to bounce mail and give an unclear message:
554 Unknown status val
Here, val
is the unexpected error value. To illustrate, consider
the following rewrite of the previous script:
#!/bin/sh EX_OK=0 # From <sysexits.h> EX_USAGE=64 # From <sysexits.h> if [ ${#} -ne 2 ]; then echo $0 needs a user name. exit $EX_USAGE fi /usr/ucb/mail -s "$1 gone" postmaster exit $EX_OK
Here, if the argument count is wrong, we exit with the value EX_USAGE, thus producing a clearer (two-line) error message:
/usr/local/bin/gone needs a user name. /usr/local/bin/gone... Bad usage.
If all goes well, we then exit with EX_OK so that sendmail knows that the mail was successfully delivered.
When sendmail sees that the A=
program exited with
EX_OK, it assumes that the mail message was successfully delivered.
It is vital for programs that deliver mail to exit with
EX_OK only if delivery was 100% successful. Failure to take precautions
to detect every possible error can result in lost mail and
angry users.
To illustrate, consider the following common C language statement:
(void)fclose(fp);
If the file that is being written to is remotely mounted, the written
data may be cached locally. All the preceding write statements
will have succeeded, but if the remote host crashes after
the last write (but before the close), some of the data can
be lost. The fclose(3) fails, but the (void)
prevents detection of that failure.
Even in writing small shell scripts, it is important to include error checking. The following rewrite of our gone program includes error checking but does not handle signals. We leave that as an exercise for the reader:
#!/bin/sh EX_OK=0 # From <sysexits.h> EX_USAGE=64 # From <sysexits.h> EX_SOFTWARE=70 # From <sysexits.h> if [ ${#} -ne 2 ]; then echo $0 needs a user name. exit $EX_USAGE fi if /usr/ucb/mail -s "$1 gone" postmaster >/dev/null 2>&1 then exit $EX_OK fi exit $EX_SOFTWARE