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