|
Chapter 20 The checkcompat() Cookbook
|
|
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
).
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.
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.
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
().
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.
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)
continue;
clrbitmap(h->h_mflags);
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.
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.
Convert a text string macro name into an integer
(int)
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
).
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.
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).
Look up a word in a class
(bool)
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
).
|