Previous Section Next Section

12.3 Write a Delivery Agent Script

The 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]

[8] 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.

12.3.1 Duplicates Discarded

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[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:

[9] Under V8 sendmail this is no longer a problem for duplicate programs listed in ~/.forward files (Section 13.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 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) Values

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 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.

    Previous Section Next Section