sendmailSearch this book
Previous: 20.2 The CookbookChapter 20
The checkcompat() Cookbook
Next: III. Administration

20.3 Alphabetized V8.8 Subroutines

Many subroutines inside sendmail can be very useful in writing your own checkcompat() routine. Always try to use sendmail's internal routines rather than redesigning your own. Those supplied with sendmail have already been screened and tested by a large body of skilled programmers.

In this section we list the ones that we consider most useful and describe how best to use them (see Table 20.3).

Table 20.3: Handy Subroutines for Use with checkcompat()
addheaderSection 20.3.1, addheader()Add a new header to the list of headers
defineSection 20.3.2, define()Define a macro
Section 20.3.3How to remove a header
hvalueSection 20.3.4, hvalue()Look up a header's field by name
macidSection 20.3.5, macid()Convert a text string macro name into an integer
macvalueSection 20.3.6, macvalue()Fetch a macro's value
->map_lookupSection 20.3.7, ->map-lookup()Look up a key in an external database
wordinclassSection 20.3.8Look up a word in a class

Be aware that these subroutines are documented for V8.8 sendmail. If you are using a different version of sendmail, you must familiarize yourself with the source before attempting to use them. Mismatched parameters can cause errors that are difficult to diagnose.

20.3.1 addheader()

Add a new header to the list of headers

The addheader() routine is used to add a new header to the existing list of headers. Note that since it does not check for duplicates, you should screen for the existence of a header before adding it (unless it is okay for multiple headers to exist):

char value[MAXLINE];

(void)sprintf(value, "Screened by checkcompat() for %s", q_user);
(void)addheader("x-screened", value, e->e_header);

Memory for the name of the new header and its value are allocated internally by addheader(), so automatic variables can be safely passed. Note that the first argument to addheader() (line 4) is the name of the header. If you accidentally include a colon in that argument, the name will be stored with that colon included, and later the header name will be incorrectly emitted with two colons.

Note that addheader does not check for the validity of its arguments. If you pass it NULL or an illegal address, you may cause sendmail to core-dump and fail. You are dealing with sendmail internals here, so be very careful.

20.3.2 define()

Define a macro

Macros can be defined and redefined inside checkcompat() using the define() routine. It is called like this:

define( macid, value, envel);

Here, macid is the literal character in the case of a single-character macro name (such as 'A') or the identifying value for a multicharacter name (this latter being derived from a call to macid(). The value is the value to be assigned to the macro. (Note that if the macro already has a value, that prior value is overwritten.) The envel is a pointer to the envelope (in the case of checkcompat() it is e).

To illustrate, consider the following code fragment that assigns a new value to the macros $A and ${foo}:

int mid;

* A single-character name is given a value that is the address
* of a character constant.
define( 'A', "some text as a value", e);

* A multi-character name is given a value that is in malloc()d
* memory space.
mid = macid("{foo}", NULL);
define(mid, newstr(to->qpaddr), e);

In both cases the value stored is in permanent memory. You must not pass a pointer to automatic or volatile memory, because define() just stores the pointer, it does not copy the value. [5] In the second example (line 18), memory is allocated for the string with newstr(), another internal routine that allocates room for the string and the string's terminating zero, copies the string into that allocated space, and returns a pointer to the newly allocated string. But be careful to keep track of what you are doing. You can easily create a serious memory leak if you are careless with newstr().

[5] Technically, you should not use a string constant because string constants are stored in read-only program space and often may not be changed at runtime, for example, when a macro's value needs to be later modified in place.

The first argument to wordinclass() (line 17) is a string that contains the macro's literal name without a $ prefix. For multicharacter names, the enclosing curly braces are mandatory (see Section 31.4.2, "Multicharacter Names").

See the -d35 debugging switch (Section 37.5.120, -d35.9) for a way to watch macros being defined and redefined. Also see munchstring() in readcf.c if you need to parse a macro's literal name from among other text.


How to remove a header

The sendmail program's internal routines cannot be used to remove a header from the linked list of headers, but you can prevent one from being emitted into the delivered message by massaging its flags.

Consider the need to suppress custom X-Accounting: headers:

HDR *h;

for (h = e->e_header; h != NULL; h = h->h_link)
       if (strcasecmp(h->h_field, "x-accounting") != 0)
       h->h_flags |= H_ACHECK;

Here, we have to manually scan the linked list of headers (line 3). When the one that is being sought is found (note that the check is not case sensitive, line 5) we delete it. This is done by first zeroing all the ?flags? for the header (line 7). Then we add the H_ACHECK flag (line 8) to ensure that the header will be skipped on delivery. See Section 35.5.16, "Replacing Headers with H_ACHECK" for a discussion of the effect of the H_ACHECK flag.

20.3.4 hvalue()

Look up a header's field by name

The hvalue() routine can be used to look up a header and optionally change it, or insert it if it is missing. It is used like this:

char *field;

field = hvalue("message-id", e->e_header)
if (field == NULL)
        /* No Message-ID: in the message, so insert one */

Note that the first argument to hvalue() is the name of the header being looked up (line 3), without a trailing colon. If you accidentally include the colon, the lookup will always fail.

The hvalue() routine returns a string that is the field of the header that is looked up (see Section 35.3, "Header Field Contents"). If a particular header name can exist multiple times (such as Received:), hvalue() will return the first instance. If you need to process all such headers, you will need to scan for them manually.

20.3.5 macid()

Convert a text string macro name into an integer


Macros, class names, and rule-set names can be single-character or multicharacter in form. The macid() routine converts a macro's name from a text string form (literal form) into an integer. The resulting integer can later be used by other routines such as macvalue() and wordinclass().

To illustrate, consider the names A and {foo}:

int mid;

mid = macid("A", NULL);

mid = macid("{foo}", NULL);

The first argument to macid() must be a string that contains the name to be converted. For a single-character name (line 3) the name is converted to the character-constant value of the first letter ("A" becomes 'A'). For a multicharacter name (line 5) the name must be enclosed in curly braces. The name is converted to a unique integer value that is either new or one that was previously assigned by an earlier call to macid().

The second argument to macid is NULL in our examples because the string in the first argument contained only a name. When other text might appear in the string you may pass a nonnull second argument:

char *str = "localhost is ${lhost}, so there.";
char *cp, *ep;
int mid;

if ((cp = strchr(str, '$')) != NULL)
        mid = macid( cp+1, &ep );

The second argument (line 7) is the address of a character pointer. After the call to macid() that character pointer (ep) contains the address of the first character following the name - the comma in str (line 1).

20.3.6 macvalue()

Fetch a macro's value

The value stored in a macro can be fetched with the macvalue() routine. For example,

char *val;
int mid;

val = macvalue( 'A', e);

mid = macid( "{foo}", NULL);
val = macvalue( mid, e);

The first example (line 4) shows macvalue() being used to fetch the value stored in a single-character macro name (A). The second (line 6) shows a technique that is useful for multicharacter names and for times when you must process an arbitrary name in a string. The macid() routine converts a macro's literal name into an identifying integer. That integer is then looked up with macvalue() to return its value (line 7).

If the looked up macro is not defined, macvalue() returns NULL.

20.3.7 ->map-lookup()

Look up a key in an external database

Arbitrary keys can be looked up in external databases by using the routines that are indirectly set up when a database is declared. For example, consider a dbm database that will store login names as the keys and the words ok or drop as the values. You would first create a source text file with the makemap program (see Section 33.2). Next you would declare that database in the configuration file:

Kbouncelist dbm -o /etc/bouncelist

This K configuration command (see Section 33.3) gives the internal symbolic name bouncelist to the database.

Values can now be looked up using the following code fragment:

STAB *map;
char *p;
int  r = 0;

map = stab("bouncelist", ST_MAP, ST_FIND);
if (map == (STAB *)NULL)
        return (EX_OK);
p = (*map->s_map.map_class->map_lookup)(&map->s_map, to->q_ruser, NULL, &r);
if (p == NULL)
        return (EX_OK);

if (strcasecmp(p, "drop") == 0)
        /* drop the message on the floor */

The lookup is performed by calling the addresses stored in map_lookup (line 8). The second argument is the key that is being looked up (here, we use to->q_ruser, the login name of the recipient as the key).

Errors are returned in r. We ignore those errors here, but you can use them if you wish. They correspond to the error values listed in <sysexits.h>. But note that not all lookups change r (see map.c for specific details).

20.3.8 wordinclass()

Look up a word in a class


The words that form a class (see Section 32.1.1, "The C Class Command") are stored as symbols in the symbol table (see Section 32.2.4, "Class Name Hashing Algorithm"). The check to see whether a string of text was included in a given class is done with the wordinclass() routine. Single-character class names and multicharacter class names can be searched:

int mid;

if (wordinclass( "localhost", 'w') == TRUE)
        /* yes, it was found */

mid = macid("{foo}", NULL);
if (wordinclass( "localhost", mid) == FALSE)
        /* no, not found */

The first argument to macid is a string that contains the word being looked up. Care must be taken to ensure that the first argument is a legal address and not NULL because wordinclass() does not check.

The second argument is either an integer character-constant (such as the 'w', line 3), or an integer identifier returned by macid() (line 7).

Previous: 20.2 The CookbooksendmailNext: III. Administration
20.2 The CookbookBook IndexIII. Administration