12.3 Write a Delivery Agent ScriptThe program that is driven by the prog delivery agent can be a compiled executable binary, a shell script, or even a perl(1) script. The limitation on the kind of program that can 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 such as 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.[8]
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. 12.3.1 Duplicates DiscardedWhen 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[9] 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:
#!/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 because both of the addresses are identical, sendmail discards one. To avoid this problem, design all delivery programs to require at least one unique argument. For example, the previous program should be rewritten to require the user's name as an argument: #!/bin/sh if [ ${#} -ne 2 ]; then echo $0 needs a username. 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. 12.3.2 Correct exit(2) ValuesThe 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 gives an unclear message: 554 5.0.0 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 username. 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 username. /usr/local/bin/gone... Bad usage. If all goes well, we then exit with EX_OK so that sendmail knows the mail was successfully delivered. 12.3.3 Is It Really EX_OK?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 can 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_TEMPFAIL=75 # From <sysexits.h> if [ ${#} -ne 2 ]; then echo $0 needs a username. exit $EX_USAGE fi if /usr/ucb/mail -s "$1 gone" postmaster >/dev/null 2>&1 then exit $EX_OK fi exit $EX_TEMPFAIL Note that by using EX_TEMPFAIL we cause the message to be requeued if this script fails. That way, a bug in the script can be fixed, and the next queue run will succeed. |