/*++
/* NAME
/* cleanup_milter 3
/* SUMMARY
/* external mail filter support
/* SYNOPSIS
/* #include <cleanup.h>
/*
/* void cleanup_milter_header_checks_init(void)
/*
/* void cleanup_milter_receive(state, count)
/* CLEANUP_STATE *state;
/* int count;
/*
/* void cleanup_milter_inspect(state, milters)
/* CLEANUP_STATE *state;
/* MILTERS *milters;
/*
/* cleanup_milter_emul_mail(state, milters, sender)
/* CLEANUP_STATE *state;
/* MILTERS *milters;
/* const char *sender;
/*
/* cleanup_milter_emul_rcpt(state, milters, recipient)
/* CLEANUP_STATE *state;
/* MILTERS *milters;
/* const char *recipient;
/*
/* cleanup_milter_emul_data(state, milters)
/* CLEANUP_STATE *state;
/* MILTERS *milters;
/* DESCRIPTION
/* This module implements support for Sendmail-style mail
/* filter (milter) applications, including in-place queue file
/* modification.
/*
/* cleanup_milter_header_checks_init() does pre-jail
/* initializations.
/*
/* cleanup_milter_receive() receives mail filter definitions,
/* typically from an smtpd(8) server process, and registers
/* local call-back functions for macro expansion and for queue
/* file modification.
/*
/* cleanup_milter_inspect() sends the current message headers
/* and body to the mail filters that were received with
/* cleanup_milter_receive(), or that are specified with the
/* cleanup_milters configuration parameter.
/*
/* cleanup_milter_emul_mail() emulates connect, helo and mail
/* events for mail that does not arrive via the smtpd(8) server.
/* The emulation pretends that mail arrives from localhost/127.0.0.1
/* via ESMTP. Milters can reject emulated connect, helo, mail
/* or data events, but not emulated rcpt events as described
/* next.
/*
/* cleanup_milter_emul_rcpt() emulates an rcpt event for mail
/* that does not arrive via the smtpd(8) server. This reports
/* a server configuration error condition when the milter
/* rejects an emulated rcpt event.
/*
/* cleanup_milter_emul_data() emulates a data event for mail
/* that does not arrive via the smtpd(8) server. It's OK for
/* milters to reject emulated data events.
/* SEE ALSO
/* milter(3) generic mail filter interface
/* DIAGNOSTICS
/* Fatal errors: memory allocation problem.
/* Panic: interface violation.
/* Warnings: I/O errors (state->errs is updated accordingly).
/* LICENSE
/* .ad
/* .fi
/* The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/* Wietse Venema
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*
/* Wietse Venema
/* Google, Inc.
/* 111 8th Avenue
/* New York, NY 10011, USA
/*--*/
/* System library. */
#include <sys_defs.h>
#include <sys/socket.h> /* AF_INET */
#include <string.h>
#include <errno.h>
#ifdef STRCASECMP_IN_STRINGS_H
#include <strings.h>
#endif
/* Utility library. */
#include <msg.h>
#include <vstream.h>
#include <vstring.h>
#include <stringops.h>
#include <inet_proto.h>
/* Global library. */
#include <off_cvt.h>
#include <dsn_mask.h>
#include <rec_type.h>
#include <cleanup_user.h>
#include <record.h>
#include <rec_attr_map.h>
#include <mail_proto.h>
#include <mail_params.h>
#include <lex_822.h>
#include <is_header.h>
#include <quote_821_local.h>
#include <dsn_util.h>
#include <xtext.h>
#include <info_log_addr_form.h>
#include <header_opts.h>
/* Application-specific. */
#include <cleanup.h>
/*
* How Postfix 2 . 4 edits queue file information :
*
* Mail filter applications ( Milters ) can send modification requests after
* receiving the end of the message body . Postfix implements these
* modifications in the cleanup server , so that it can edit the queue file
* in place . This avoids the temporary files that would be needed when
* modifications were implemented in the SMTP server ( Postfix normally does
* not store the whole message in main memory ) . Once a Milter is done
* editing , the queue file can be used as input for the next Milter , and so
* on . Finally , the cleanup server changes file permissions , calls fsync ( ) ,
* and waits for successful completion .
*
* To implement in - place queue file edits , we need to introduce surprisingly
* little change to the existing Postfix queue file structure . All we need
* is a way to mark a record as deleted , and to jump from one place in the
* queue file to another . We could implement deleted records with jumps , but
* marking is sometimes simpler .
*
* Postfix does not store queue files as plain text files . Instead all
* information is stored in records with an explicit type and length , for
* sender , recipient , arrival time , and so on . Even the content that makes
* up the message header and body is stored as records with explicit types
* and lengths . This organization makes it very easy to mark a record as
* deleted , and to introduce the pointer records that we will use to jump
* from one place in a queue file to another place .
*
* - Deleting a recipient is easiest - simply modify the record type into one
* that is skipped by the software that delivers mail . We won ' t try to reuse
* the deleted recipient for other purposes . When deleting a recipient , we
* may need to delete multiple recipient records that result from virtual
* alias expansion of the original recipient address .
*
* - Replacing a header record involves pointer records . A record is replaced
* by overwriting it with a forward pointer to space after the end of the
* queue file , putting the new record there , followed by a reverse pointer
* to the record that follows the replaced header . To simplify
* implementation we follow a short header record with a filler record so
* that we can always overwrite a header record with a pointer .
*
* N . B . This is a major difference with Postfix version 2 . 3 , which needed
* complex code to save records that follow a short header , before it could
* overwrite a short header record . This code contained two of the three
* post - release bugs that were found with Postfix header editing .
*
* - Inserting a header record is like replacing one , except that we also
* relocate the record that is being overwritten by the forward pointer .
*
* - Deleting a message header is simplest when we replace it by a " skip "
* pointer to the information that follows the header . With a multi - line
* header we need to update only the first line .
*
* - Appending a recipient or header record involves pointer records as well .
* To make this convenient , the queue file already contains dummy pointer
* records at the locations where we want to append recipient or header
* content . To append , change the dummy pointer into a forward pointer to
* space after the end of a message , put the new recipient or header record
* there , followed by a reverse pointer to the record that follows the
* forward pointer .
*
* - To append another header or recipient record , replace the reverse pointer
* by a forward pointer to space after the end of a message , put the new
* record there , followed by the value of the reverse pointer that we
* replace . Thus , there is no one - to - one correspondence between forward and
* backward pointers . Instead , there can be multiple forward pointers for
* one reverse pointer .
*
* - When a mail filter wants to replace an entire body , we overwrite existing
* body records until we run out of space , and then write a pointer to space
* after the end of the queue file , followed by more body content . There may
* be multiple regions with body content ; regions are connected by forward
* pointers , and the last region ends with a pointer to the marker that ends
* the message content segment . Body regions can be large and therefore they
* are reused to avoid wasting space . Sendmail mail filters currently do not
* replace individual body records , and that is a good thing .
*
* Making queue file modifications safe :
*
* Postfix queue files are segmented . The first segment is for envelope
* records , the second for message header and body content , and the third
* segment is for information that was extracted or generated from the
* message header or body content . Each segment is terminated by a marker
* record . For now we don ' t want to change their location . That is , we want
* to avoid moving the records that mark the start or end of a queue file
* segment .
*
* To ensure that we can always replace a header or body record by a pointer
* record , without having to relocate a marker record , the cleanup server
* places a dummy pointer record at the end of the recipients and at the end
* of the message header . To support message body modifications , a dummy
* pointer record is also placed at the end of the message content .
*
* With all these changes in queue file organization , REC_TYPE_END is no longer
* guaranteed to be the last record in a queue file . If an application were
* to read beyond the REC_TYPE_END marker , it would go into an infinite
* loop , because records after REC_TYPE_END alternate with reverse pointers
* to the middle of the queue file . For robustness , the record reading
* routine skips forward to the end - of - file position after reading the
* REC_TYPE_END marker .
*/
/*#define msg_verbose 2*/
static HBC_CHECKS *cleanup_milter_hbc_checks;
static VSTRING *cleanup_milter_hbc_reply;
static void cleanup_milter_set_error(CLEANUP_STATE *, int );
static const char *cleanup_add_rcpt_par(void *, const char *, const char *);
#define STR(x) vstring_str(x)
#define LEN(x) VSTRING_LEN(x)
/* cleanup_milter_hbc_log - log post-milter header/body_checks action */
static void cleanup_milter_hbc_log(void *context, const char *action,
const char *where, const char *line,
const char *optional_text)
{
const CLEANUP_STATE *state = (CLEANUP_STATE *) context;
const char *attr;
vstring_sprintf(state->temp1, "%s: milter-%s-%s: %s %.200s from %s[%s];" ,
state->queue_id, where, action, where, line,
state->client_name, state->client_addr);
if (state->sender)
vstring_sprintf_append(state->temp1, " from=<%s>" ,
info_log_addr_form_sender(state->sender));
if (state->recip)
vstring_sprintf_append(state->temp1, " to=<%s>" ,
info_log_addr_form_recipient(state->recip));
if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_PROTO_NAME)) != 0 )
vstring_sprintf_append(state->temp1, " proto=%s" , attr);
if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_HELO_NAME)) != 0 )
vstring_sprintf_append(state->temp1, " helo=<%s>" , attr);
if (optional_text)
vstring_sprintf_append(state->temp1, ": %s" , optional_text);
msg_info("%s" , vstring_str(state->temp1));
}
/* cleanup_milter_header_prepend - prepend header to milter-generated header */
static void cleanup_milter_header_prepend(void *context, int rec_type,
const char *buf, ssize_t len, off_t offset)
{
/* XXX save prepended header to buffer. */
msg_warn("the milter_header/body_checks prepend action is not implemented" );
}
/* cleanup_milter_hbc_extend - additional header/body_checks actions */
static char *cleanup_milter_hbc_extend(void *context, const char *command,
ssize_t cmd_len, const char *optional_text,
const char *where, const char *buf,
ssize_t buf_len, off_t offset)
{
CLEANUP_STATE *state = (CLEANUP_STATE *) context;
const char *map_class = VAR_MILT_HEAD_CHECKS; /* XXX */
#define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0 )
/*
* These are currently our mutually - exclusive ways of not receiving mail :
* " reject " and " discard " . Only these can be reported to the up - stream
* Postfix libmilter code , because sending any reply there causes Postfix
* libmilter to skip further " edit " requests . By way of safety net , each
* of these must also reset CLEANUP_FLAG_FILTER_ALL .
*/
#define CLEANUP_MILTER_REJECTING_OR_DISCARDING_MESSAGE(state) \
((state->flags & CLEANUP_FLAG_DISCARD) || (state->errs & CLEANUP_STAT_CONT))
/*
* We log all header / body - checks actions here , because we know the
* details of the message content that triggered the action . We report
* detail - free milter - reply values ( reject / discard , stored in the
* milter_hbc_reply state member ) to the Postfix libmilter code , so that
* Postfix libmilter can stop sending requests .
*
* We also set all applicable cleanup flags here , because there is no
* guarantee that Postfix libmilter will propagate our own milter - reply
* value to cleanup_milter_inspect ( ) which calls cleanup_milter_apply ( ) .
* The latter translates responses from Milter applications into cleanup
* flags , and logs the response text . Postfix libmilter can convey only
* one milter - reply value per email message , and that reply may even come
* from outside Postfix .
*
* To suppress redundant logging , cleanup_milter_apply ( ) does nothing when
* the milter - reply value matches the saved text in the milter_hbc_reply
* state member . As we remember only one milter - reply value , we can ' t
* report multiple milter - reply values per email message . We satisfy this
* constraint , because we already clear the CLEANUP_FLAG_FILTER_ALL flags
* to terminate further header inspection .
*/
if ((state->flags & CLEANUP_FLAG_FILTER_ALL) == 0 )
return ((char *) buf);
if (STREQUAL(command, "BCC" , cmd_len)) {
if (strchr(optional_text, '@' ) == 0 ) {
msg_warn("bad BCC address \" %s\" in %s map -- "
"need user@domain" ,
optional_text, VAR_MILT_HEAD_CHECKS);
} else {
cleanup_milter_hbc_log(context, "bcc" , where, buf, optional_text);
/* Caller checks state error flags. */
(void ) cleanup_add_rcpt_par(state, optional_text, "" );
}
return ((char *) buf);
}
if (STREQUAL(command, "REJECT" , cmd_len)) {
const CLEANUP_STAT_DETAIL *detail;
if (state->reason)
myfree(state->reason);
detail = cleanup_stat_detail(CLEANUP_STAT_CONT);
if (*optional_text) {
state->reason = dsn_prepend(detail->dsn, optional_text);
if (*state->reason != '4' && *state->reason != '5' ) {
msg_warn("bad DSN action in %s -- need 4.x.x or 5.x.x" ,
optional_text);
*state->reason = '4' ;
}
} else {
state->reason = dsn_prepend(detail->dsn, detail->text);
}
if (*state->reason == '4' )
state->errs |= CLEANUP_STAT_DEFER;
else
state->errs |= CLEANUP_STAT_CONT;
state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
cleanup_milter_hbc_log(context, "reject" , where, buf, state->reason);
vstring_sprintf(cleanup_milter_hbc_reply, "%d %s" ,
detail->smtp, state->reason);
STR(cleanup_milter_hbc_reply)[0 ] = *state->reason;
return ((char *) buf);
}
if (STREQUAL(command, "FILTER" , cmd_len)) {
if (*optional_text == 0 ) {
msg_warn("missing FILTER command argument in %s map" , map_class);
} else if (strchr(optional_text, ':' ) == 0 ) {
msg_warn("bad FILTER command %s in %s -- "
"need transport:destination" ,
optional_text, map_class);
} else {
if (state->filter)
myfree(state->filter);
state->filter = mystrdup(optional_text);
cleanup_milter_hbc_log(context, "filter" , where, buf,
optional_text);
}
return ((char *) buf);
}
if (STREQUAL(command, "DISCARD" , cmd_len)) {
cleanup_milter_hbc_log(context, "discard" , where, buf, optional_text);
vstring_strcpy(cleanup_milter_hbc_reply, "D" );
state->flags |= CLEANUP_FLAG_DISCARD;
state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
return ((char *) buf);
}
if (STREQUAL(command, "HOLD" , cmd_len)) {
if ((state->flags & (CLEANUP_FLAG_HOLD | CLEANUP_FLAG_DISCARD)) == 0 ) {
cleanup_milter_hbc_log(context, "hold" , where, buf, optional_text);
state->flags |= CLEANUP_FLAG_HOLD;
}
return ((char *) buf);
}
if (STREQUAL(command, "REDIRECT" , cmd_len)) {
if (strchr(optional_text, '@' ) == 0 ) {
msg_warn("bad REDIRECT target \" %s\" in %s map -- "
"need user@domain" ,
optional_text, map_class);
} else {
if (state->redirect)
myfree(state->redirect);
state->redirect = mystrdup(optional_text);
cleanup_milter_hbc_log(context, "redirect" , where, buf,
optional_text);
state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
}
return ((char *) buf);
}
return ((char *) HBC_CHECKS_STAT_UNKNOWN);
}
/* cleanup_milter_header_checks - inspect Milter-generated header */
static int cleanup_milter_header_checks(CLEANUP_STATE *state, VSTRING *buf)
{
char *ret;
/*
* Milter application " add / insert / replace header " requests happen at the
* end - of - message stage , therefore all the header operations are relative
* to the primary message header .
*/
ret = hbc_header_checks((void *) state, cleanup_milter_hbc_checks,
MIME_HDR_PRIMARY, (HEADER_OPTS *) 0 ,
buf, (off_t) 0 );
if (ret == 0 ) {
return (0 );
} else if (ret == HBC_CHECKS_STAT_ERROR) {
msg_warn("%s: %s map lookup problem -- "
"message not accepted, try again later" ,
state->queue_id, VAR_MILT_HEAD_CHECKS);
state->errs |= CLEANUP_STAT_WRITE;
return (0 );
} else {
if (ret != STR(buf)) {
vstring_strcpy(buf, ret);
myfree(ret);
}
return (1 );
}
}
/* cleanup_milter_hbc_add_meta_records - add REDIRECT or FILTER meta records */
static void cleanup_milter_hbc_add_meta_records(CLEANUP_STATE *state)
{
const char *myname = "cleanup_milter_hbc_add_meta_records" ;
off_t reverse_ptr_offset;
off_t new_meta_offset;
/*
* Note : this code runs while the Milter infrastructure is being torn
* down . For this reason we handle all I / O errors here on the spot ,
* instead of reporting them back through the Milter infrastructure .
*/
/*
* Sanity check .
*/
if (state->append_meta_pt_offset < 0 )
msg_panic("%s: no meta append pointer location" , myname);
if (state->append_meta_pt_target < 0 )
msg_panic("%s: no meta append pointer target" , myname);
/*
* Allocate space after the end of the queue file , and write the meta
* record ( s ) , followed by a reverse pointer record that points to the
* target of the old " meta record append " pointer record . This reverse
* pointer record becomes the new " meta record append " pointer record .
* Although the new " meta record append " pointer record will never be
* used , we update it here to make the code more similar to other code
* that inserts / appends content , so that common code can be factored out
* later .
*/
if ((new_meta_offset = vstream_fseek(state->dst, (off_t) 0 , SEEK_END)) < 0 ) {
cleanup_milter_set_error(state, errno);
return ;
}
if (state->filter != 0 )
cleanup_out_string(state, REC_TYPE_FILT, state->filter);
if (state->redirect != 0 )
cleanup_out_string(state, REC_TYPE_RDR, state->redirect);
if ((reverse_ptr_offset = vstream_ftell(state->dst)) < 0 ) {
msg_warn("%s: vstream_ftell file %s: %m" , myname, cleanup_path);
state->errs |= CLEANUP_STAT_WRITE;
return ;
}
cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
(long ) state->append_meta_pt_target);
/*
* Pointer flipping : update the old " meta record append " pointer record
* value with the location of the new meta record .
*/
if (vstream_fseek(state->dst, state->append_meta_pt_offset, SEEK_SET) < 0 ) {
cleanup_milter_set_error(state, errno);
return ;
}
cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
(long ) new_meta_offset);
/*
* Update the in - memory " meta append " pointer record location with the
* location of the reverse pointer record that follows the new meta
* record . The target of the " meta append " pointer record does not
* change ; it ' s always the record that follows the dummy pointer record
* that was written while Postfix received the message .
*/
state->append_meta_pt_offset = reverse_ptr_offset;
/*
* Note : state - > append_meta_pt_target never changes .
*/
}
/* cleanup_milter_header_checks_init - initialize post-Milter header checks */
void cleanup_milter_header_checks_init(void )
{
static const char myname[] = "cleanup_milter_header_checks_init" ;
#define NO_NESTED_HDR_NAME ""
#define NO_NESTED_HDR_VALUE ""
#define NO_MIME_HDR_NAME ""
#define NO_MIME_HDR_VALUE ""
static /* XXX not const */ HBC_CALL_BACKS call_backs = {
cleanup_milter_hbc_log,
cleanup_milter_header_prepend,
cleanup_milter_hbc_extend,
};
if (*var_milt_head_checks == 0 )
msg_panic("%s: %s is empty" , myname, VAR_MILT_HEAD_CHECKS);
if (cleanup_milter_hbc_checks)
msg_panic("%s: cleanup_milter_hbc_checks is not null" , myname);
cleanup_milter_hbc_checks =
hbc_header_checks_create(VAR_MILT_HEAD_CHECKS, var_milt_head_checks,
NO_MIME_HDR_NAME, NO_MIME_HDR_VALUE,
NO_NESTED_HDR_NAME, NO_NESTED_HDR_VALUE,
&call_backs);
if (cleanup_milter_hbc_reply)
msg_panic("%s: cleanup_milter_hbc_reply is not null" , myname);
cleanup_milter_hbc_reply = vstring_alloc(100 );
}
#ifdef TEST
/* cleanup_milter_header_checks_deinit - undo cleanup_milter_header_checks_init */
static void cleanup_milter_header_checks_deinit(void )
{
static const char myname[] = "cleanup_milter_header_checks_deinit" ;
if (cleanup_milter_hbc_checks == 0 )
msg_panic("%s: cleanup_milter_hbc_checks is null" , myname);
hbc_header_checks_free(cleanup_milter_hbc_checks);
cleanup_milter_hbc_checks = 0 ;
if (cleanup_milter_hbc_reply == 0 )
msg_panic("%s: cleanup_milter_hbc_reply is null" , myname);
vstring_free(cleanup_milter_hbc_reply);
cleanup_milter_hbc_reply = 0 ;
}
#endif
/* cleanup_milter_header_checks_reinit - re-init post-Milter header checks */
static void cleanup_milter_header_checks_reinit(CLEANUP_STATE *state)
{
if (state->filter)
myfree(state->filter);
state->filter = 0 ;
if (state->redirect)
myfree(state->redirect);
state->redirect = 0 ;
VSTRING_RESET(cleanup_milter_hbc_reply);
}
/* cleanup_milter_hbc_finish - finalize post-Milter header checks */
static void cleanup_milter_hbc_finish(CLEANUP_STATE *state)
{
if (CLEANUP_OUT_OK(state)
&& !CLEANUP_MILTER_REJECTING_OR_DISCARDING_MESSAGE(state)
&& (state->filter || state->redirect))
cleanup_milter_hbc_add_meta_records(state);
}
/*
* Milter replies .
*/
#define CLEANUP_MILTER_SET_REASON(__state, __reason) do { \
if ((__state)->reason) \
myfree((__state)->reason); \
(__state)->reason = mystrdup(__reason); \
if ((__state)->smtp_reply) { \
myfree((__state)->smtp_reply); \
(__state)->smtp_reply = 0 ; \
} \
} while (0 )
#define CLEANUP_MILTER_SET_SMTP_REPLY(__state, __smtp_reply) do { \
if ((__state)->reason) \
myfree((__state)->reason); \
(__state)->reason = mystrdup(__smtp_reply + 4 ); \
printable((__state)->reason, '_' ); \
if ((__state)->smtp_reply) \
myfree((__state)->smtp_reply); \
(__state)->smtp_reply = mystrdup(__smtp_reply); \
} while (0 )
/* cleanup_milter_set_error - set error flag from errno */
static void cleanup_milter_set_error(CLEANUP_STATE *state, int err)
{
if (err == EFBIG) {
msg_warn("%s: queue file size limit exceeded" , state->queue_id);
state->errs |= CLEANUP_STAT_SIZE;
} else {
msg_warn("%s: write queue file: %m" , state->queue_id);
state->errs |= CLEANUP_STAT_WRITE;
}
}
/* cleanup_milter_error - return dummy error description */
static const char *cleanup_milter_error(CLEANUP_STATE *state, int err)
{
const char *myname = "cleanup_milter_error" ;
const CLEANUP_STAT_DETAIL *dp;
/*
* For consistency with error reporting within the milter infrastructure ,
* content manipulation routines return a null pointer on success , and an
* SMTP - like response on error .
*
* However , when cleanup_milter_apply ( ) receives this error response from
* the milter infrastructure , it ignores the text since the appropriate
* cleanup error flags were already set by cleanup_milter_set_error ( ) .
*
* Specify a null error number when the " errno to error flag " mapping was
* already done elsewhere , possibly outside this module .
*/
if (err)
cleanup_milter_set_error(state, err);
else if (CLEANUP_OUT_OK(state))
msg_panic("%s: missing errno to error flag mapping" , myname);
if (state->milter_err_text == 0 )
state->milter_err_text = vstring_alloc(50 );
dp = cleanup_stat_detail(state->errs);
return (STR(vstring_sprintf(state->milter_err_text,
"%d %s %s" , dp->smtp, dp->dsn, dp->text)));
}
/* cleanup_add_header - append message header */
static const char *cleanup_add_header(void *context, const char *name,
const char *space,
const char *value)
{
const char *myname = "cleanup_add_header" ;
CLEANUP_STATE *state = (CLEANUP_STATE *) context;
VSTRING *buf;
off_t reverse_ptr_offset;
off_t new_hdr_offset;
/*
* To simplify implementation , the cleanup server writes a dummy " header
* append " pointer record after the last message header . We cache both
* the location and the target of the current " header append " pointer
* record .
*/
if (state->append_hdr_pt_offset < 0 )
msg_panic("%s: no header append pointer location" , myname);
if (state->append_hdr_pt_target < 0 )
msg_panic("%s: no header append pointer target" , myname);
/*
* Return early when Milter header checks request that this header record
* be dropped , or that the message is discarded . Note : CLEANUP_OUT_OK ( )
* tests CLEANUP_FLAG_DISCARD . We don ' t want to report the latter as an
* error .
*/
buf = vstring_alloc(100 );
vstring_sprintf(buf, "%s:%s%s" , name, space, value);
if (cleanup_milter_hbc_checks) {
if (cleanup_milter_header_checks(state, buf) == 0
|| (state->flags & CLEANUP_FLAG_DISCARD)) {
vstring_free(buf);
return (0 );
}
if (CLEANUP_OUT_OK(state) == 0 ) {
vstring_free(buf);
return (cleanup_milter_error(state, 0 ));
}
}
/*
* Allocate space after the end of the queue file , and write the header
* record ( s ) , followed by a reverse pointer record that points to the
* target of the old " header append " pointer record . This reverse pointer
* record becomes the new " header append " pointer record .
*/
if ((new_hdr_offset = vstream_fseek(state->dst, (off_t) 0 , SEEK_END)) < 0 ) {
msg_warn("%s: seek file %s: %m" , myname, cleanup_path);
vstring_free(buf);
return (cleanup_milter_error(state, errno));
}
/* XXX emit prepended header, then clear it. */
cleanup_out_header(state, buf); /* Includes padding */
vstring_free(buf);
if ((reverse_ptr_offset = vstream_ftell(state->dst)) < 0 ) {
msg_warn("%s: vstream_ftell file %s: %m" , myname, cleanup_path);
return (cleanup_milter_error(state, errno));
}
cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
(long ) state->append_hdr_pt_target);
/*
* Pointer flipping : update the old " header append " pointer record value
* with the location of the new header record .
*
* XXX To avoid unnecessary seek operations when the new header immediately
* follows the old append header pointer , write a null pointer or make
* the record reading loop smarter . Making vstream_fseek ( ) smarter does
* not help , because it doesn ' t know if we ' re going to read or write
* after a write + seek sequence .
*/
if (vstream_fseek(state->dst, state->append_hdr_pt_offset, SEEK_SET) < 0 ) {
msg_warn("%s: seek file %s: %m" , myname, cleanup_path);
return (cleanup_milter_error(state, errno));
}
cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
(long ) new_hdr_offset);
/*
* Update the in - memory " header append " pointer record location with the
* location of the reverse pointer record that follows the new header .
* The target of the " header append " pointer record does not change ; it ' s
* always the record that follows the dummy pointer record that was
* written while Postfix received the message .
*/
state->append_hdr_pt_offset = reverse_ptr_offset;
/*
* In case of error while doing record output .
*/
return (CLEANUP_OUT_OK(state) == 0 ? cleanup_milter_error(state, 0 ) :
cleanup_milter_hbc_reply && LEN(cleanup_milter_hbc_reply) ?
STR(cleanup_milter_hbc_reply) : 0 );
/*
* Note : state - > append_hdr_pt_target never changes .
*/
}
/* hidden_header - respect milter header hiding protocol */
static int hidden_header(VSTRING *buf, ARGV *auto_hdrs, int *hide_done)
{
char **cpp;
int mask;
for (cpp = auto_hdrs->argv, mask = 1 ; *cpp; cpp++, mask <<= 1 )
if ((*hide_done & mask) == 0 && strncmp(*cpp, STR(buf), LEN(buf)) == 0 )
return (*hide_done |= mask);
return (0 );
}
/* cleanup_find_header_start - find specific header instance */
static off_t cleanup_find_header_start(CLEANUP_STATE *state, ssize_t index,
const char *header_label,
VSTRING *buf,
int *prec_type,
int allow_ptr_backup)
{
const char *myname = "cleanup_find_header_start" ;
off_t curr_offset; /* offset after found record */
off_t ptr_offset; /* pointer to found record */
VSTRING *ptr_buf = 0 ;
int rec_type = REC_TYPE_ERROR;
int last_type;
ssize_t len;
int hide_done = 0 ;
if (msg_verbose)
msg_info("%s: index %ld name \" %s\"" ,
myname, (long ) index, header_label ? header_label : "(none)" );
/*
* Sanity checks .
*/
if (index < 1 )
msg_panic("%s: bad header index %ld" , myname, (long ) index);
/*
* Skip to the start of the message content , and read records until we
* either find the specified header , or until we hit the end of the
* headers .
*
* The index specifies the header instance : 1 is the first one . The header
* label specifies the header name . A null pointer matches any header .
*
* When the specified header is not found , the result value is - 1 .
*
* When the specified header is found , its first record is stored in the
* caller - provided read buffer , and the result value is the queue file
* offset of that record . The file read position is left at the start of
* the next ( non - filler ) queue file record , which can be the remainder of
* a multi - record header .
*
* When a header is found and allow_ptr_backup is non - zero , then the result
* is either the first record of that header , or it is the pointer record
* that points to the first record of that header . In the latter case ,
* the file read position is undefined . Returning the pointer allows us
* to do some optimizations when inserting text multiple times at the
* same place .
*
* XXX We can ' t use the MIME processor here . It not only buffers up the
* input , it also reads the record that follows a complete header before
* it invokes the header call - back action . This complicates the way that
* we discover header offsets and boundaries . Worse is that the MIME
* processor is unaware that multi - record message headers can have PTR
* records in the middle .
*
* XXX The draw - back of not using the MIME processor is that we have to
* duplicate some of its logic here and in the routine that finds the end
* of the header record . To minimize the duplication we define an ugly
* macro that is used in all code that scans for header boundaries .
*
* XXX Sendmail compatibility ( based on Sendmail 8 . 13 . 6 measurements ) .
*
* - When changing Received : header # 1 , we change the Received : header that
* follows our own one ; a request to change Received : header # 0 is
* silently treated as a request to change Received : header # 1 .
*
* - When changing Date : header # 1 , we change the first Date : header ; a
* request to change Date : header # 0 is silently treated as a request to
* change Date : header # 1 .
*
* Thus , header change requests are relative to the content as received ,
* that is , the content after our own Received : header . They can affect
* only the headers that the MTA actually exposes to mail filter
* applications .
*
* - However , when inserting a header at position 0 , the new header appears
* before our own Received : header , and when inserting at position 1 , the
* new header appears after our own Received : header .
*
* Thus , header insert operations are relative to the content as delivered ,
* that is , the content including our own Received : header .
*
* None of the above is applicable after a Milter inserts a header before
* our own Received : header . From then on , our own Received : header
* becomes just like other headers .
*/
#define CLEANUP_FIND_HEADER_NOTFOUND (-1 )
#define CLEANUP_FIND_HEADER_IOERROR (-2 )
#define CLEANUP_FIND_HEADER_RETURN(offs) do { \
if (ptr_buf) \
vstring_free(ptr_buf); \
return (offs); \
} while (0 )
#define GET_NEXT_TEXT_OR_PTR_RECORD(rec_type, state, buf, curr_offset, quit) \
if ((rec_type = rec_get_raw(state->dst, buf, 0 , REC_FLAG_NONE)) < 0 ) { \
msg_warn("%s: read file %s: %m" , myname, cleanup_path); \
cleanup_milter_set_error(state, errno); \
do { quit; } while (0 ); \
} \
if (msg_verbose > 1 ) \
msg_info("%s: read: %ld: %.*s" , myname, (long ) curr_offset, \
LEN(buf) > 30 ? 30 : (int ) LEN(buf), STR(buf)); \
if (rec_type == REC_TYPE_DTXT) \
continue ; \
if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT \
&& rec_type != REC_TYPE_PTR) \
break ;
/* End of hairy macros. */
if (vstream_fseek(state->dst, state->data_offset, SEEK_SET) < 0 ) {
msg_warn("%s: seek file %s: %m" , myname, cleanup_path);
cleanup_milter_set_error(state, errno);
CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR);
}
for (ptr_offset = 0 , last_type = 0 ; /* void */ ; /* void */ ) {
if ((curr_offset = vstream_ftell(state->dst)) < 0 ) {
msg_warn("%s: vstream_ftell file %s: %m" , myname, cleanup_path);
cleanup_milter_set_error(state, errno);
CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR);
}
/* Don't follow the "append header" pointer. */
if (curr_offset == state->append_hdr_pt_offset)
break ;
/* Caution: this macro terminates the loop at end-of-message. */
/* Don't do complex processing while breaking out of this loop. */
GET_NEXT_TEXT_OR_PTR_RECORD(rec_type, state, buf, curr_offset,
CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR));
/* Caution: don't assume ptr->header. This may be header-ptr->body. */
if (rec_type == REC_TYPE_PTR) {
if (rec_goto(state->dst, STR(buf)) < 0 ) {
msg_warn("%s: read file %s: %m" , myname, cleanup_path);
cleanup_milter_set_error(state, errno);
CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR);
}
/* Save PTR record, in case it points to the start of a header. */
if (allow_ptr_backup) {
ptr_offset = curr_offset;
if (ptr_buf == 0 )
ptr_buf = vstring_alloc(100 );
vstring_strcpy(ptr_buf, STR(buf));
}
/* Don't update last_type; PTR can happen after REC_TYPE_CONT. */
continue ;
}
/* The middle of a multi-record header. */
else if (last_type == REC_TYPE_CONT || IS_SPACE_TAB(STR(buf)[0 ])) {
/* Reset the saved PTR record and update last_type. */
}
/* No more message headers. */
else if ((len = is_header(STR(buf))) == 0 ) {
break ;
}
/* This the start of a message header. */
else if ((header_label == 0
|| (strncasecmp(header_label, STR(buf), len) == 0
&& strlen(header_label) == len
&& !hidden_header(buf, state->auto_hdrs, &hide_done)))
&& --index == 0 ) {
/* If we have a saved PTR record, it points to start of header. */
break ;
}
ptr_offset = 0 ;
last_type = rec_type;
}
/*
* In case of failure , return negative start position .
*/
if (index > 0 ) {
curr_offset = CLEANUP_FIND_HEADER_NOTFOUND;
} else {
/*
* Skip over short - header padding , so that the file read pointer is
* always positioned at the first non - padding record after the header
* record . Insist on padding after short a header record , so that a
* short header record can safely be overwritten by a pointer record .
*/
if (LEN(buf) < REC_TYPE_PTR_PAYL_SIZE) {
VSTRING *rbuf = (ptr_offset ? buf :
(ptr_buf ? ptr_buf :
(ptr_buf = vstring_alloc(100 ))));
int rval;
if ((rval = rec_get_raw(state->dst, rbuf, 0 , REC_FLAG_NONE)) < 0 ) {
cleanup_milter_set_error(state, errno);
CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR);
}
if (rval != REC_TYPE_DTXT)
msg_panic("%s: short header without padding" , myname);
}
/*
* Optionally return a pointer to the message header , instead of the
* start of the message header itself . In that case the file read
* position is undefined ( actually it is at the first non - padding
* record that follows the message header record ) .
*/
if (ptr_offset != 0 ) {
rec_type = REC_TYPE_PTR;
curr_offset = ptr_offset;
vstring_strcpy(buf, STR(ptr_buf));
}
*prec_type = rec_type;
}
if (msg_verbose)
msg_info("%s: index %ld name %s type %d offset %ld" ,
myname, (long ) index, header_label ?
header_label : "(none)" , rec_type, (long ) curr_offset);
CLEANUP_FIND_HEADER_RETURN(curr_offset);
}
/* cleanup_find_header_end - find end of header */
static off_t cleanup_find_header_end(CLEANUP_STATE *state,
VSTRING *rec_buf,
int last_type)
{
const char *myname = "cleanup_find_header_end" ;
off_t read_offset;
int rec_type;
/*
* This routine is called immediately after cleanup_find_header_start ( ) .
* rec_buf is the cleanup_find_header_start ( ) result record ; last_type is
* the corresponding record type : REC_TYPE_PTR or REC_TYPE_NORM ; the file
* read position is at the first non - padding record after the result
* header record .
*/
for (;;) {
if ((read_offset = vstream_ftell(state->dst)) < 0 ) {
msg_warn("%s: read file %s: %m" , myname, cleanup_path);
cleanup_milter_error(state, errno);
return (-1 );
}
/* Don't follow the "append header" pointer. */
if (read_offset == state->append_hdr_pt_offset)
break ;
/* Caution: this macro terminates the loop at end-of-message. */
/* Don't do complex processing while breaking out of this loop. */
GET_NEXT_TEXT_OR_PTR_RECORD(rec_type, state, rec_buf, read_offset,
/* Warning and errno->error mapping are done elsewhere. */
return (-1 ));
if (rec_type == REC_TYPE_PTR) {
if (rec_goto(state->dst, STR(rec_buf)) < 0 ) {
msg_warn("%s: read file %s: %m" , myname, cleanup_path);
cleanup_milter_error(state, errno);
return (-1 );
}
/* Don't update last_type; PTR may follow REC_TYPE_CONT. */
continue ;
}
/* Start of header or message body. */
if (last_type != REC_TYPE_CONT && !IS_SPACE_TAB(STR(rec_buf)[0 ]))
break ;
last_type = rec_type;
}
return (read_offset);
}
/* cleanup_patch_header - patch new header into an existing header */
static const char *cleanup_patch_header(CLEANUP_STATE *state,
const char *new_hdr_name,
const char *hdr_space,
const char *new_hdr_value,
off_t old_rec_offset,
int old_rec_type,
VSTRING *old_rec_buf,
off_t next_offset)
{
const char *myname = "cleanup_patch_header" ;
VSTRING *buf = vstring_alloc(100 );
off_t new_hdr_offset;
#define CLEANUP_PATCH_HEADER_RETURN(ret) do { \
vstring_free(buf); \
return (ret); \
} while (0 )
if (msg_verbose)
msg_info("%s: \" %s\" \" %s\" at %ld" ,
myname, new_hdr_name, new_hdr_value, (long ) old_rec_offset);
/*
* Allocate space after the end of the queue file for the new header and
* optionally save an existing record to make room for a forward pointer
* record . If the saved record was not a PTR record , follow the saved
* record by a reverse pointer record that points to the record after the
* original location of the saved record .
*
* We update the queue file in a safe manner : save the new header and the
* existing records after the end of the queue file , write the reverse
* pointer , and only then overwrite the saved records with the forward
* pointer to the new header .
*
* old_rec_offset , old_rec_type , and old_rec_buf specify the record that we
* are about to overwrite with a pointer record . If the record needs to
* be saved ( i . e . old_rec_type > 0 ) , the buffer contains the data content
* of exactly one PTR or text record .
*
* next_offset specifies the record that follows the to - be - overwritten
* record . It is ignored when the to - be - saved record is a pointer record .
*/
/*
* Return early when Milter header checks request that this header record
* be dropped .
*/
vstring_sprintf(buf, "%s:%s%s" , new_hdr_name, hdr_space, new_hdr_value);
if (cleanup_milter_hbc_checks
&& cleanup_milter_header_checks(state, buf) == 0 )
CLEANUP_PATCH_HEADER_RETURN(0 );
/*
* Write the new header to a new location after the end of the queue
* file .
*/
if ((new_hdr_offset = vstream_fseek(state->dst, (off_t) 0 , SEEK_END)) < 0 ) {
msg_warn("%s: seek file %s: %m" , myname, cleanup_path);
CLEANUP_PATCH_HEADER_RETURN(cleanup_milter_error(state, errno));
}
/* XXX emit prepended header, then clear it. */
cleanup_out_header(state, buf); /* Includes padding */
if (msg_verbose > 1 )
msg_info("%s: %ld: write %.*s" , myname, (long ) new_hdr_offset,
LEN(buf) > 30 ? 30 : (int ) LEN(buf), STR(buf));
/*
* Optionally , save the existing text record or pointer record that will
* be overwritten with the forward pointer . Pad a short saved record to
* ensure that it , too , can be overwritten by a pointer .
*/
if (old_rec_type > 0 ) {
CLEANUP_OUT_BUF(state, old_rec_type, old_rec_buf);
if (LEN(old_rec_buf) < REC_TYPE_PTR_PAYL_SIZE)
rec_pad(state->dst, REC_TYPE_DTXT,
REC_TYPE_PTR_PAYL_SIZE - LEN(old_rec_buf));
if (msg_verbose > 1 )
msg_info("%s: write %.*s" , myname, LEN(old_rec_buf) > 30 ?
30 : (int ) LEN(old_rec_buf), STR(old_rec_buf));
}
/*
* If the saved record wasn ' t a PTR record , write the reverse pointer
* after the saved records . A reverse pointer value of - 1 means we were
* confused about what we were going to save .
*/
if (old_rec_type != REC_TYPE_PTR) {
if (next_offset < 0 )
msg_panic("%s: bad reverse pointer %ld" ,
myname, (long ) next_offset);
cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
(long ) next_offset);
if (msg_verbose > 1 )
msg_info("%s: write PTR %ld" , myname, (long ) next_offset);
}
/*
* Write the forward pointer over the old record . Generally , a pointer
* record will be shorter than a header record , so there will be a gap in
* the queue file before the next record . In other words , we must always
* follow pointer records otherwise we get out of sync with the data .
*/
if (vstream_fseek(state->dst, old_rec_offset, SEEK_SET) < 0 ) {
msg_warn("%s: seek file %s: %m" , myname, cleanup_path);
CLEANUP_PATCH_HEADER_RETURN(cleanup_milter_error(state, errno));
}
cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
(long ) new_hdr_offset);
if (msg_verbose > 1 )
msg_info("%s: %ld: write PTR %ld" , myname, (long ) old_rec_offset,
(long ) new_hdr_offset);
/*
* In case of error while doing record output .
*/
CLEANUP_PATCH_HEADER_RETURN(
CLEANUP_OUT_OK(state) == 0 ? cleanup_milter_error(state, 0 ) :
cleanup_milter_hbc_reply && LEN(cleanup_milter_hbc_reply) ?
STR(cleanup_milter_hbc_reply) : 0 );
/*
* Note : state - > append_hdr_pt_target never changes .
*/
}
/* cleanup_ins_header - insert message header */
static const char *cleanup_ins_header(void *context, ssize_t index,
const char *new_hdr_name,
const char *hdr_space,
const char *new_hdr_value)
{
const char *myname = "cleanup_ins_header" ;
CLEANUP_STATE *state = (CLEANUP_STATE *) context;
VSTRING *old_rec_buf = vstring_alloc(100 );
off_t old_rec_offset;
int old_rec_type;
off_t next_offset;
const char *ret;
#define CLEANUP_INS_HEADER_RETURN(ret) do { \
vstring_free(old_rec_buf); \
return (ret); \
} while (0 )
if (msg_verbose)
msg_info("%s: %ld \" %s\" \" %s\"" ,
myname, (long ) index, new_hdr_name, new_hdr_value);
/*
* Look for a header at the specified position .
*
* The lookup result may be a pointer record . This allows us to make some
* optimization when multiple insert operations happen in the same place .
*
* Index 1 is the top - most header .
*/
#define NO_HEADER_NAME ((char *) 0 )
#define ALLOW_PTR_BACKUP 1
if (index < 1 )
index = 1 ;
old_rec_offset = cleanup_find_header_start(state, index, NO_HEADER_NAME,
old_rec_buf, &old_rec_type,
ALLOW_PTR_BACKUP);
if (old_rec_offset == CLEANUP_FIND_HEADER_IOERROR)
/* Warning and errno->error mapping are done elsewhere. */
CLEANUP_INS_HEADER_RETURN(cleanup_milter_error(state, 0 ));
/*
* If the header does not exist , simply append the header to the linked
* list at the " header append " pointer record .
*/
if (old_rec_offset < 0 )
CLEANUP_INS_HEADER_RETURN(cleanup_add_header(context, new_hdr_name,
hdr_space, new_hdr_value));
/*
* If the header does exist , save both the new and the existing header to
* new storage at the end of the queue file , and link the new storage
* with a forward and reverse pointer ( don ' t write a reverse pointer if
* we are starting with a pointer record ) .
*/
if (old_rec_type == REC_TYPE_PTR) {
next_offset = -1 ;
} else {
if ((next_offset = vstream_ftell(state->dst)) < 0 ) {
msg_warn("%s: read file %s: %m" , myname, cleanup_path);
CLEANUP_INS_HEADER_RETURN(cleanup_milter_error(state, errno));
}
}
ret = cleanup_patch_header(state, new_hdr_name, hdr_space, new_hdr_value,
old_rec_offset, old_rec_type,
old_rec_buf, next_offset);
CLEANUP_INS_HEADER_RETURN(ret);
}
/* cleanup_upd_header - modify or append message header */
static const char *cleanup_upd_header(void *context, ssize_t index,
const char *new_hdr_name,
const char *hdr_space,
const char *new_hdr_value)
{
const char *myname = "cleanup_upd_header" ;
CLEANUP_STATE *state = (CLEANUP_STATE *) context;
VSTRING *rec_buf;
off_t old_rec_offset;
off_t next_offset;
int last_type;
const char *ret;
if (msg_verbose)
msg_info("%s: %ld \" %s\" \" %s\"" ,
myname, (long ) index, new_hdr_name, new_hdr_value);
/*
* Sanity check .
*/
if (*new_hdr_name == 0 )
msg_panic("%s: null header name" , myname);
/*
* Find the header that is being modified .
*
* The lookup result will never be a pointer record .
*
* Index 1 is the first matching header instance .
*
* XXX When a header is updated repeatedly we create jumps to jumps . To
* eliminate this , rewrite the loop below so that we can start with the
* pointer record that points to the header that ' s being edited .
*/
#define DONT_SAVE_RECORD 0
#define NO_PTR_BACKUP 0
#define CLEANUP_UPD_HEADER_RETURN(ret) do { \
vstring_free(rec_buf); \
return (ret); \
} while (0 )
rec_buf = vstring_alloc(100 );
old_rec_offset = cleanup_find_header_start(state, index, new_hdr_name,
rec_buf, &last_type,
NO_PTR_BACKUP);
if (old_rec_offset == CLEANUP_FIND_HEADER_IOERROR)
/* Warning and errno->error mapping are done elsewhere. */
CLEANUP_UPD_HEADER_RETURN(cleanup_milter_error(state, 0 ));
/*
* If no old header is found , simply append the new header to the linked
* list at the " header append " pointer record .
*/
if (old_rec_offset < 0 )
CLEANUP_UPD_HEADER_RETURN(cleanup_add_header(context, new_hdr_name,
hdr_space, new_hdr_value));
/*
* If the old header is found , find the end of the old header , save the
* new header to new storage at the end of the queue file , and link the
* new storage with a forward and reverse pointer .
*/
if ((next_offset = cleanup_find_header_end(state, rec_buf, last_type)) < 0 )
/* Warning and errno->error mapping are done elsewhere. */
CLEANUP_UPD_HEADER_RETURN(cleanup_milter_error(state, 0 ));
ret = cleanup_patch_header(state, new_hdr_name, hdr_space, new_hdr_value,
old_rec_offset, DONT_SAVE_RECORD,
(VSTRING *) 0 , next_offset);
CLEANUP_UPD_HEADER_RETURN(ret);
}
/* cleanup_del_header - delete message header */
static const char *cleanup_del_header(void *context, ssize_t index,
const char *hdr_name)
{
const char *myname = "cleanup_del_header" ;
CLEANUP_STATE *state = (CLEANUP_STATE *) context;
VSTRING *rec_buf;
off_t header_offset;
off_t next_offset;
int last_type;
if (msg_verbose)
msg_info("%s: %ld \" %s\"" , myname, (long ) index, hdr_name);
/*
* Sanity check .
*/
if (*hdr_name == 0 )
msg_panic("%s: null header name" , myname);
/*
* Find the header that is being deleted .
*
* The lookup result will never be a pointer record .
*
* Index 1 is the first matching header instance .
*/
#define CLEANUP_DEL_HEADER_RETURN(ret) do { \
vstring_free(rec_buf); \
return (ret); \
} while (0 )
rec_buf = vstring_alloc(100 );
header_offset = cleanup_find_header_start(state, index, hdr_name, rec_buf,
&last_type, NO_PTR_BACKUP);
if (header_offset == CLEANUP_FIND_HEADER_IOERROR)
/* Warning and errno->error mapping are done elsewhere. */
CLEANUP_DEL_HEADER_RETURN(cleanup_milter_error(state, 0 ));
/*
* Overwrite the beginning of the header record with a pointer to the
* information that follows the header . We can ' t simply overwrite the
* header with cleanup_out_header ( ) and a special record type , because
* there may be a PTR record in the middle of a multi - line header .
*/
if (header_offset > 0 ) {
if ((next_offset = cleanup_find_header_end(state, rec_buf, last_type)) < 0 )
/* Warning and errno->error mapping are done elsewhere. */
CLEANUP_DEL_HEADER_RETURN(cleanup_milter_error(state, 0 ));
/* Mark the header as deleted. */
if (vstream_fseek(state->dst, header_offset, SEEK_SET) < 0 ) {
msg_warn("%s: seek file %s: %m" , myname, cleanup_path);
CLEANUP_DEL_HEADER_RETURN(cleanup_milter_error(state, errno));
}
rec_fprintf(state->dst, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
(long ) next_offset);
}
vstring_free(rec_buf);
/*
* In case of error while doing record output .
*/
return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, 0 ));
}
/* cleanup_chg_from - replace sender address, ignore ESMTP arguments */
static const char *cleanup_chg_from(void *context, const char *ext_from,
const char *esmtp_args)
{
const char *myname = "cleanup_chg_from" ;
CLEANUP_STATE *state = (CLEANUP_STATE *) context;
off_t new_offset;
off_t new_sender_offset;
off_t after_sender_offs;
int addr_count;
TOK822 *tree;
TOK822 *tp;
VSTRING *int_sender_buf;
int dsn_envid = 0 ;
int dsn_ret = 0 ;
if (msg_verbose)
msg_info("%s: \" %s\" \" %s\"" , myname, ext_from, esmtp_args);
/*
* ESMTP support is limited to RET and ENVID , i . e . things that are stored
* together with the sender queue file record .
*/
if (esmtp_args[0 ]) {
ARGV *esmtp_argv;
int i;
const char *arg;
esmtp_argv = argv_split(esmtp_args, " " );
for (i = 0 ; i < esmtp_argv->argc; ++i) {
arg = esmtp_argv->argv[i];
if (strncasecmp(arg, "RET=" , 4 ) == 0 ) {
if ((dsn_ret = dsn_ret_code(arg + 4 )) == 0 ) {
msg_warn("Ignoring bad ESMTP parameter \" %s\" in "
"SMFI_CHGFROM request" , arg);
} else {
state->dsn_ret = dsn_ret;
}
} else if (strncasecmp(arg, "ENVID=" , 6 ) == 0 ) {
if (state->milter_dsn_buf == 0 )
state->milter_dsn_buf = vstring_alloc(20 );
dsn_envid = (xtext_unquote(state->milter_dsn_buf, arg + 6 )
&& allprint(STR(state->milter_dsn_buf)));
if (!dsn_envid) {
msg_warn("Ignoring bad ESMTP parameter \" %s\" in "
"SMFI_CHGFROM request" , arg);
} else {
if (state->dsn_envid)
myfree(state->dsn_envid);
state->dsn_envid = mystrdup(STR(state->milter_dsn_buf));
}
} else {
msg_warn("Ignoring bad ESMTP parameter \" %s\" in "
"SMFI_CHGFROM request" , arg);
}
}
argv_free(esmtp_argv);
}
/*
* The cleanup server remembers the file offset of the current sender
* address record ( offset in sender_pt_offset ) and the file offset of the
* record that follows the sender address ( offset in sender_pt_target ) .
* Short original sender records are padded , so that they can safely be
* overwritten with a pointer record to the new sender address record .
*/
if (state->sender_pt_offset < 0 )
msg_panic("%s: no original sender record offset" , myname);
if (state->sender_pt_target < 0 )
msg_panic("%s: no post-sender record offset" , myname);
/*
* Allocate space after the end of the queue file , and write the new { DSN
* envid , DSN ret , sender address , sender BCC } records , followed by a
* reverse pointer record that points to the record that follows the
* original sender record .
*
* We update the queue file in a safe manner : save the new sender after the
* end of the queue file , write the reverse pointer , and only then
* overwrite the old sender record with the forward pointer to the new
* sender .
*/
if ((new_offset = vstream_fseek(state->dst, (off_t) 0 , SEEK_END)) < 0 ) {
msg_warn("%s: seek file %s: %m" , myname, cleanup_path);
return (cleanup_milter_error(state, errno));
}
/*
* Sender DSN attribute records precede the sender record .
*/
if (dsn_envid)
rec_fprintf(state->dst, REC_TYPE_ATTR, "%s=%s" ,
MAIL_ATTR_DSN_ENVID, STR(state->milter_dsn_buf));
if (dsn_ret)
rec_fprintf(state->dst, REC_TYPE_ATTR, "%s=%d" ,
MAIL_ATTR_DSN_RET, dsn_ret);
if (dsn_envid == 0 && dsn_ret == 0 ) {
new_sender_offset = new_offset;
} else if ((new_sender_offset = vstream_ftell(state->dst)) < 0 ) {
msg_warn("%s: vstream_ftell file %s: %m" , myname, cleanup_path);
return (cleanup_milter_error(state, errno));
}
/*
* Transform the address from external form to internal form . This also
* removes the enclosing < > , if present .
*
* XXX vstring_alloc ( ) rejects zero - length requests .
*/
int_sender_buf = vstring_alloc(strlen(ext_from) + 1 );
tree = tok822_parse(ext_from);
for (addr_count = 0 , tp = tree; tp != 0 ; tp = tp->next) {
if (tp->type == TOK822_ADDR) {
if (addr_count == 0 ) {
tok822_internalize(int_sender_buf, tp->head, TOK822_STR_DEFL);
addr_count += 1 ;
} else {
msg_warn("%s: Milter request to add multi-sender: \" %s\"" ,
state->queue_id, ext_from);
break ;
}
}
}
tok822_free_tree(tree);
after_sender_offs = cleanup_addr_sender(state, STR(int_sender_buf));
vstring_free(int_sender_buf);
cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
(long ) state->sender_pt_target);
state->sender_pt_target = after_sender_offs;
/*
* Overwrite the current sender record with the pointer to the new { DSN
* envid , DSN ret , sender address , sender BCC } records .
*/
if (vstream_fseek(state->dst, state->sender_pt_offset, SEEK_SET) < 0 ) {
msg_warn("%s: seek file %s: %m" , myname, cleanup_path);
return (cleanup_milter_error(state, errno));
}
cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
(long ) new_offset);
/*
* Remember the location of the new current sender record .
*/
state->sender_pt_offset = new_sender_offset;
/*
* In case of error while doing record output .
*/
return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, 0 ));
}
/* cleanup_add_rcpt_par - append recipient address, with ESMTP arguments */
static const char *cleanup_add_rcpt_par(void *context, const char *ext_rcpt,
const char *esmtp_args)
{
const char *myname = "cleanup_add_rcpt_par" ;
CLEANUP_STATE *state = (CLEANUP_STATE *) context;
off_t new_rcpt_offset;
off_t reverse_ptr_offset;
int addr_count;
TOK822 *tree;
TOK822 *tp;
VSTRING *int_rcpt_buf;
VSTRING *orcpt_buf = 0 ;
ARGV *esmtp_argv;
int dsn_notify = 0 ;
const char *dsn_orcpt_info = 0 ;
size_t type_len;
int i;
const char *arg;
const char *arg_val;
if (msg_verbose)
msg_info("%s: \" %s\" \" %s\"" , myname, ext_rcpt, esmtp_args);
/*
* To simplify implementation , the cleanup server writes a dummy
* " recipient append " pointer record after the last recipient . We cache
* both the location and the target of the current " recipient append "
* pointer record .
*/
if (state->append_rcpt_pt_offset < 0 )
msg_panic("%s: no recipient append pointer location" , myname);
if (state->append_rcpt_pt_target < 0 )
msg_panic("%s: no recipient append pointer target" , myname);
/*
* Allocate space after the end of the queue file , and write the
* recipient record , followed by a reverse pointer record that points to
* the target of the old " recipient append " pointer record . This reverse
* pointer record becomes the new " recipient append " pointer record .
*
* We update the queue file in a safe manner : save the new recipient after
* the end of the queue file , write the reverse pointer , and only then
* overwrite the old " recipient append " pointer with the forward pointer
* to the new recipient .
*/
if ((new_rcpt_offset = vstream_fseek(state->dst, (off_t) 0 , SEEK_END)) < 0 ) {
msg_warn("%s: seek file %s: %m" , myname, cleanup_path);
return (cleanup_milter_error(state, errno));
}
/*
* Parse ESMTP parameters . XXX UTF8SMTP don ' t assume ORCPT is xtext .
*/
if (esmtp_args[0 ]) {
esmtp_argv = argv_split(esmtp_args, " " );
for (i = 0 ; i < esmtp_argv->argc; ++i) {
arg = esmtp_argv->argv[i];
if (strncasecmp(arg, "NOTIFY=" , 7 ) == 0 ) { /* RFC 3461 */
if (dsn_notify || (dsn_notify = dsn_notify_mask(arg + 7 )) == 0 )
msg_warn("%s: Bad NOTIFY parameter from Milter or "
"header/body_checks: \" %.100 s\"" ,
state->queue_id, arg);
} else if (strncasecmp(arg, "ORCPT=" , 6 ) == 0 ) { /* RFC 3461 */
if (orcpt_buf == 0 )
orcpt_buf = vstring_alloc(100 );
if (dsn_orcpt_info
|| (type_len = strcspn(arg_val = arg + 6 , ";" )) == 0
|| (arg_val)[type_len] != ';'
|| xtext_unquote_append(vstring_sprintf(orcpt_buf,
"%.*s;" , (int ) type_len,
arg_val),
arg_val + type_len + 1 ) == 0 ) {
msg_warn("%s: Bad ORCPT parameter from Milter or "
"header/body_checks: \" %.100 s\"" ,
state->queue_id, arg);
} else {
dsn_orcpt_info = STR(orcpt_buf);
}
} else {
msg_warn("%s: ignoring ESMTP argument from Milter or "
"header/body_checks: \" %.100 s\"" ,
state->queue_id, arg);
}
}
argv_free(esmtp_argv);
}
/*
* Transform recipient from external form to internal form . This also
* removes the enclosing < > , if present .
*
* XXX vstring_alloc ( ) rejects zero - length requests .
*/
int_rcpt_buf = vstring_alloc(strlen(ext_rcpt) + 1 );
tree = tok822_parse(ext_rcpt);
for (addr_count = 0 , tp = tree; tp != 0 ; tp = tp->next) {
if (tp->type == TOK822_ADDR) {
if (addr_count == 0 ) {
tok822_internalize(int_rcpt_buf, tp->head, TOK822_STR_DEFL);
addr_count += 1 ;
} else {
msg_warn("%s: Milter or header/body_checks request to "
"add multi-recipient: \" %s\"" ,
state->queue_id, ext_rcpt);
break ;
}
}
}
tok822_free_tree(tree);
if (addr_count != 0 )
cleanup_addr_bcc_dsn(state, STR(int_rcpt_buf), dsn_orcpt_info,
dsn_notify ? dsn_notify : DEF_DSN_NOTIFY);
else
msg_warn("%s: ignoring attempt from Milter to add null recipient" ,
state->queue_id);
vstring_free(int_rcpt_buf);
if (orcpt_buf)
vstring_free(orcpt_buf);
/*
* Don ' t update the queue file when we did not write a recipient record
* ( malformed or duplicate BCC recipient ) .
*/
if (vstream_ftell(state->dst) == new_rcpt_offset)
return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, 0 ));
/*
* Follow the recipient with a " reverse " pointer to the old recipient
* append target .
*/
if ((reverse_ptr_offset = vstream_ftell(state->dst)) < 0 ) {
msg_warn("%s: vstream_ftell file %s: %m" , myname, cleanup_path);
return (cleanup_milter_error(state, errno));
}
cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
(long ) state->append_rcpt_pt_target);
/*
* Pointer flipping : update the old " recipient append " pointer record
* value to the location of the new recipient record .
*/
if (vstream_fseek(state->dst, state->append_rcpt_pt_offset, SEEK_SET) < 0 ) {
msg_warn("%s: seek file %s: %m" , myname, cleanup_path);
return (cleanup_milter_error(state, errno));
}
cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
(long ) new_rcpt_offset);
/*
* Update the in - memory " recipient append " pointer record location with
* the location of the reverse pointer record that follows the new
* recipient . The target of the " recipient append " pointer record does
* not change ; it ' s always the record that follows the dummy pointer
* record that was written while Postfix received the message .
*/
state->append_rcpt_pt_offset = reverse_ptr_offset;
/*
* In case of error while doing record output .
*/
return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, 0 ));
}
/* cleanup_add_rcpt - append recipient address */
static const char *cleanup_add_rcpt(void *context, const char *ext_rcpt)
{
return (cleanup_add_rcpt_par(context, ext_rcpt, "" ));
}
/* cleanup_del_rcpt - remove recipient and all its expansions */
static const char *cleanup_del_rcpt(void *context, const char *ext_rcpt)
{
const char *myname = "cleanup_del_rcpt" ;
CLEANUP_STATE *state = (CLEANUP_STATE *) context;
off_t curr_offset;
VSTRING *buf;
char *attr_name;
char *attr_value;
char *dsn_orcpt = 0 ; /* XXX for dup filter cleanup */
int dsn_notify = 0 ; /* XXX for dup filter cleanup */
char *orig_rcpt = 0 ;
char *start;
int rec_type;
int junk;
int count = 0 ;
TOK822 *tree;
TOK822 *tp;
VSTRING *int_rcpt_buf;
int addr_count;
if (msg_verbose)
msg_info("%s: \" %s\"" , myname, ext_rcpt);
/*
* Virtual aliasing and other address rewriting happens after the mail
* filter sees the envelope address . Therefore we must delete all
* recipient records whose Postfix ( not DSN ) original recipient address
* matches the specified address .
*
* As the number of recipients may be very large we can ' t do an efficient
* two - pass implementation ( collect record offsets first , then mark
* records as deleted ) . Instead we mark records as soon as we find them .
* This is less efficient because we do ( seek - write - read ) for each marked
* recipient , instead of ( seek - write ) . It ' s unlikely that VSTREAMs will
* be made smart enough to eliminate unnecessary I / O with small seeks .
*
* XXX When Postfix original recipients are turned off , we have no option
* but to match against the expanded and rewritten recipient address .
*
* XXX Remove the ( dsn_orcpt , dsn_notify , orcpt , recip ) tuple from the
* duplicate recipient filter . This requires that we maintain reference
* counts .
*/
if (vstream_fseek(state->dst, 0 L, SEEK_SET) < 0 ) {
msg_warn("%s: seek file %s: %m" , myname, cleanup_path);
return (cleanup_milter_error(state, errno));
}
#define CLEANUP_DEL_RCPT_RETURN(ret) do { \
if (orig_rcpt != 0 ) \
myfree(orig_rcpt); \
if (dsn_orcpt != 0 ) \
myfree(dsn_orcpt); \
vstring_free(buf); \
vstring_free(int_rcpt_buf); \
return (ret); \
} while (0 )
/*
* Transform recipient from external form to internal form . This also
* removes the enclosing < > , if present .
*
* XXX vstring_alloc ( ) rejects zero - length requests .
*/
int_rcpt_buf = vstring_alloc(strlen(ext_rcpt) + 1 );
tree = tok822_parse(ext_rcpt);
for (addr_count = 0 , tp = tree; tp != 0 ; tp = tp->next) {
if (tp->type == TOK822_ADDR) {
if (addr_count == 0 ) {
tok822_internalize(int_rcpt_buf, tp->head, TOK822_STR_DEFL);
addr_count += 1 ;
} else {
msg_warn("%s: Milter request to drop multi-recipient: \" %s\"" ,
state->queue_id, ext_rcpt);
break ;
}
}
}
tok822_free_tree(tree);
buf = vstring_alloc(100 );
for (;;) {
if (CLEANUP_OUT_OK(state) == 0 )
/* Warning and errno->error mapping are done elsewhere. */
CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, 0 ));
if ((curr_offset = vstream_ftell(state->dst)) < 0 ) {
msg_warn("%s: vstream_ftell file %s: %m" , myname, cleanup_path);
CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno));
}
if ((rec_type = rec_get_raw(state->dst, buf, 0 , REC_FLAG_NONE)) <= 0 ) {
msg_warn("%s: read file %s: %m" , myname, cleanup_path);
CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno));
}
if (rec_type == REC_TYPE_END)
break ;
/* Skip over message content. */
if (rec_type == REC_TYPE_MESG) {
if (vstream_fseek(state->dst, state->xtra_offset, SEEK_SET) < 0 ) {
msg_warn("%s: seek file %s: %m" , myname, cleanup_path);
CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno));
}
continue ;
}
start = STR(buf);
if (rec_type == REC_TYPE_PTR) {
if (rec_goto(state->dst, start) < 0 ) {
msg_warn("%s: seek file %s: %m" , myname, cleanup_path);
CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno));
}
continue ;
}
/* Map attribute names to pseudo record type. */
if (rec_type == REC_TYPE_ATTR) {
if (split_nameval(STR(buf), &attr_name, &attr_value) != 0
|| *attr_value == 0 )
continue ;
if ((junk = rec_attr_map(attr_name)) != 0 ) {
start = attr_value;
rec_type = junk;
}
}
switch (rec_type) {
case REC_TYPE_DSN_ORCPT: /* RCPT TO ORCPT parameter */
if (dsn_orcpt != 0 ) /* can't happen */
myfree(dsn_orcpt);
dsn_orcpt = mystrdup(start);
break ;
case REC_TYPE_DSN_NOTIFY: /* RCPT TO NOTIFY parameter */
if (alldig(start) && (junk = atoi(start)) > 0
&& DSN_NOTIFY_OK(junk))
dsn_notify = junk;
else
dsn_notify = 0 ;
break ;
case REC_TYPE_ORCP: /* unmodified RCPT TO address */
if (orig_rcpt != 0 ) /* can't happen */
myfree(orig_rcpt);
orig_rcpt = mystrdup(start);
break ;
case REC_TYPE_RCPT: /* rewritten RCPT TO address */
if (strcmp(orig_rcpt ? orig_rcpt : start, STR(int_rcpt_buf)) == 0 ) {
if (vstream_fseek(state->dst, curr_offset, SEEK_SET) < 0 ) {
msg_warn("%s: seek file %s: %m" , myname, cleanup_path);
CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno));
}
if (REC_PUT_BUF(state->dst, REC_TYPE_DRCP, buf) < 0 ) {
msg_warn("%s: write queue file: %m" , state->queue_id);
CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno));
}
count++;
}
if (var_enable_orcpt)
/* Matches been_here() call in cleanup_out_recipient(). */
been_here_drop(state->dups, "%s\n%d\n%s\n%s" ,
dsn_orcpt ? dsn_orcpt : "" , dsn_notify,
orig_rcpt ? orig_rcpt : "" , STR(int_rcpt_buf));
/* FALLTHROUGH */
case REC_TYPE_DRCP: /* canceled recipient */
case REC_TYPE_DONE: /* can't happen */
if (orig_rcpt != 0 ) {
myfree(orig_rcpt);
orig_rcpt = 0 ;
}
if (dsn_orcpt != 0 ) {
myfree(dsn_orcpt);
dsn_orcpt = 0 ;
}
dsn_notify = 0 ;
break ;
}
}
/* Matches been_here_fixed() call in cleanup_out_recipient(). */
if (var_enable_orcpt == 0 && count > 0 )
been_here_drop_fixed(state->dups, STR(int_rcpt_buf));
if (msg_verbose)
msg_info("%s: deleted %d records for recipient \" %s\"" ,
myname, count, ext_rcpt);
CLEANUP_DEL_RCPT_RETURN(0 );
}
/* cleanup_repl_body - replace message body */
static const char *cleanup_repl_body(void *context, int cmd, int rec_type,
VSTRING *buf)
{
const char *myname = "cleanup_repl_body" ;
CLEANUP_STATE *state = (CLEANUP_STATE *) context;
static VSTRING empty;
/*
* XXX Sendmail compatibility : milters don ' t see the first body line , so
* don ' t expect they will send one .
*/
switch (cmd) {
case MILTER_BODY_LINE:
if (cleanup_body_edit_write(state, rec_type, buf) < 0 )
return (cleanup_milter_error(state, errno));
break ;
case MILTER_BODY_START:
VSTRING_RESET(&empty);
if (cleanup_body_edit_start(state) < 0
|| cleanup_body_edit_write(state, REC_TYPE_NORM, &empty) < 0 )
return (cleanup_milter_error(state, errno));
break ;
case MILTER_BODY_END:
if (cleanup_body_edit_finish(state) < 0 )
return (cleanup_milter_error(state, errno));
break ;
default :
msg_panic("%s: bad command: %d" , myname, cmd);
}
return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, errno));
}
/* cleanup_milter_eval - expand macro */
static const char *cleanup_milter_eval(const char *name, void *ptr)
{
CLEANUP_STATE *state = (CLEANUP_STATE *) ptr;
/*
* Note : if we use XFORWARD attributes here , then consistency requires
* that we forward all Sendmail macros via XFORWARD .
*/
/*
* System macros .
*/
if (strcmp(name, S8_MAC_DAEMON_NAME) == 0 )
return (var_milt_daemon_name);
if (strcmp(name, S8_MAC_V) == 0 )
return (var_milt_v);
/*
* Connect macros .
*/
#ifndef CLIENT_ATTR_UNKNOWN
#define CLIENT_ATTR_UNKNOWN "unknown"
#define SERVER_ATTR_UNKNOWN "unknown"
#endif
if (strcmp(name, S8_MAC__) == 0 ) {
vstring_sprintf(state->temp1, "%s [%s]" ,
state->reverse_name, state->client_addr);
if (strcasecmp(state->client_name, state->reverse_name) != 0 )
vstring_strcat(state->temp1, " (may be forged)" );
return (STR(state->temp1));
}
if (strcmp(name, S8_MAC_J) == 0 )
return (var_myhostname);
if (strcmp(name, S8_MAC_CLIENT_ADDR) == 0 )
return (state->client_addr);
if (strcmp(name, S8_MAC_CLIENT_NAME) == 0 )
return (state->client_name);
if (strcmp(name, S8_MAC_CLIENT_PORT) == 0 )
return (state->client_port
&& strcmp(state->client_port, CLIENT_ATTR_UNKNOWN) ?
state->client_port : "0" );
if (strcmp(name, S8_MAC_CLIENT_PTR) == 0 )
return (state->reverse_name);
/* XXX S8_MAC_CLIENT_RES needs SMTPD_PEER_CODE_XXX from smtpd. */
if (strcmp(name, S8_MAC_DAEMON_ADDR) == 0 )
return (state->server_addr);
if (strcmp(name, S8_MAC_DAEMON_PORT) == 0 )
return (state->server_port
&& strcmp(state->server_port, SERVER_ATTR_UNKNOWN) ?
state->server_port : "0" );
/*
* MAIL FROM macros .
*/
if (strcmp(name, S8_MAC_I) == 0 )
return (state->queue_id);
#ifdef USE_SASL_AUTH
if (strcmp(name, S8_MAC_AUTH_TYPE) == 0 )
return (nvtable_find(state->attr, MAIL_ATTR_SASL_METHOD));
if (strcmp(name, S8_MAC_AUTH_AUTHEN) == 0 )
return (nvtable_find(state->attr, MAIL_ATTR_SASL_USERNAME));
if (strcmp(name, S8_MAC_AUTH_AUTHOR) == 0 )
return (nvtable_find(state->attr, MAIL_ATTR_SASL_SENDER));
#endif
if (strcmp(name, S8_MAC_MAIL_ADDR) == 0 )
return (state->milter_ext_from ? STR(state->milter_ext_from) : 0 );
/*
* RCPT TO macros .
*/
if (strcmp(name, S8_MAC_RCPT_ADDR) == 0 )
return (state->milter_ext_rcpt ? STR(state->milter_ext_rcpt) : 0 );
return (0 );
}
/* cleanup_milter_receive - receive milter instances */
void cleanup_milter_receive(CLEANUP_STATE *state, int count)
{
if (state->milters)
milter_free(state->milters);
state->milters = milter_receive(state->src, count);
if (state->milters == 0 )
msg_fatal("cleanup_milter_receive: milter receive failed" );
if (count <= 0 )
return ;
milter_macro_callback(state->milters, cleanup_milter_eval, (void *) state);
milter_edit_callback(state->milters,
cleanup_add_header, cleanup_upd_header,
cleanup_ins_header, cleanup_del_header,
cleanup_chg_from, cleanup_add_rcpt,
cleanup_add_rcpt_par, cleanup_del_rcpt,
cleanup_repl_body, (void *) state);
}
/* cleanup_milter_apply - apply Milter response, non-zero if rejecting */
static const char *cleanup_milter_apply(CLEANUP_STATE *state, const char *event,
const char *resp)
{
const char *myname = "cleanup_milter_apply" ;
const char *action;
const char *text;
const char *attr;
const char *ret = 0 ;
if (msg_verbose)
msg_info("%s: %s" , myname, resp);
/*
* Don ' t process our own milter_header / body checks replies . See comments
* in cleanup_milter_hbc_extend ( ) .
*/
if (cleanup_milter_hbc_reply &&
strcmp(resp, STR(cleanup_milter_hbc_reply)) == 0 )
return (0 );
/*
* Don ' t process Milter replies that are redundant because header / body
* checks already decided that we will not receive the message ; or Milter
* replies that would have conflicting effect with the outcome of
* header / body checks ( for example , header_checks " discard " action
* followed by Milter " reject " reply ) . Logging both actions would look
* silly .
*/
if (CLEANUP_MILTER_REJECTING_OR_DISCARDING_MESSAGE(state)) {
if (msg_verbose)
msg_info("%s: ignoring redundant or conflicting milter reply: %s" ,
state->queue_id, resp);
return (0 );
}
/*
* Sanity check .
*/
if (state->client_name == 0 )
msg_panic("%s: missing client info initialization" , myname);
/*
* We don ' t report errors that were already reported by the content
* editing call - back routines . See cleanup_milter_error ( ) above .
*/
if (CLEANUP_OUT_OK(state) == 0 )
return (0 );
switch (resp[0 ]) {
case 'H' :
if (state->flags & CLEANUP_FLAG_HOLD)
return (0 );
state->flags |= CLEANUP_FLAG_HOLD;
action = "milter-hold" ;
text = resp[1 ] ? resp + 1 : "milter triggers HOLD action" ;
break ;
case 'D' :
if (state->flags & CLEANUP_FLAG_DISCARD)
return (0 );
state->flags |= CLEANUP_FLAG_DISCARD;
action = "milter-discard" ;
text = "milter triggers DISCARD action" ;
break ;
case 'S' :
if (state->flags & CLEANUP_STAT_CONT)
return (0 );
/* Shutdown' may be the default action for an I/O error. */
CLEANUP_MILTER_SET_SMTP_REPLY(state, resp);
ret = state->reason;
state->errs |= CLEANUP_STAT_WRITE;
action = "milter-reject" ;
text = resp + 4 ;
break ;
/*
* Override permanent reject with temporary reject . This happens when
* the cleanup server has to bounce ( hard reject ) but is unable to
* store the message ( soft reject ) . After a temporary reject we stop
* inspecting queue file records , so it can ' t be overruled by
* something else .
*
* CLEANUP_STAT_CONT and CLEANUP_STAT_DEFER both update the reason
* attribute , but CLEANUP_STAT_DEFER takes precedence . It terminates
* queue record processing , and prevents bounces from being sent .
*/
case '4' :
CLEANUP_MILTER_SET_SMTP_REPLY(state, resp);
ret = state->reason;
state->errs |= CLEANUP_STAT_DEFER;
action = "milter-reject" ;
text = resp + 4 ;
break ;
case '5' :
CLEANUP_MILTER_SET_SMTP_REPLY(state, resp);
ret = state->reason;
state->errs |= CLEANUP_STAT_CONT;
action = "milter-reject" ;
text = resp + 4 ;
break ;
default :
msg_panic("%s: unexpected mail filter reply: %s" , myname, resp);
}
vstring_sprintf(state->temp1, "%s: %s: %s from %s[%s]: %s;" ,
state->queue_id, action, event, state->client_name,
state->client_addr, text);
if (state->sender)
vstring_sprintf_append(state->temp1, " from=<%s>" ,
info_log_addr_form_sender(state->sender));
if (state->recip)
vstring_sprintf_append(state->temp1, " to=<%s>" ,
info_log_addr_form_recipient(state->recip));
if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_PROTO_NAME)) != 0 )
vstring_sprintf_append(state->temp1, " proto=%s" , attr);
if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_HELO_NAME)) != 0 )
vstring_sprintf_append(state->temp1, " helo=<%s>" , attr);
msg_info("%s" , vstring_str(state->temp1));
return (ret);
}
/* cleanup_milter_client_init - initialize real or ersatz client info */
static void cleanup_milter_client_init(CLEANUP_STATE *state)
{
static const INET_PROTO_INFO *proto_info;
const char *proto_attr;
/*
* Either the cleanup client specifies a name , address and protocol , or
* we have a local submission and pretend localhost / 127 . 0 . 0 . 1 / AF_INET .
*/
#define NO_CLIENT_PORT "0"
state->client_name = nvtable_find(state->attr, MAIL_ATTR_ACT_CLIENT_NAME);
state->reverse_name =
nvtable_find(state->attr, MAIL_ATTR_ACT_REVERSE_CLIENT_NAME);
state->client_addr = nvtable_find(state->attr, MAIL_ATTR_ACT_CLIENT_ADDR);
state->client_port = nvtable_find(state->attr, MAIL_ATTR_ACT_CLIENT_PORT);
proto_attr = nvtable_find(state->attr, MAIL_ATTR_ACT_CLIENT_AF);
state->server_addr = nvtable_find(state->attr, MAIL_ATTR_ACT_SERVER_ADDR);
state->server_port = nvtable_find(state->attr, MAIL_ATTR_ACT_SERVER_PORT);
if (state->client_name == 0 || state->client_addr == 0 || proto_attr == 0
|| !alldig(proto_attr)) {
state->client_name = "localhost" ;
#ifdef AF_INET6
if (proto_info == 0 )
proto_info = inet_proto_info();
if (proto_info->sa_family_list[0 ] == PF_INET6) {
state->client_addr = "::1" ;
state->client_af = AF_INET6;
} else
#endif
{
state->client_addr = "127.0.0.1" ;
state->client_af = AF_INET;
}
state->server_addr = state->client_addr;
} else
state->client_af = atoi(proto_attr);
if (state->reverse_name == 0 )
state->reverse_name = state->client_name;
/* Compatibility with pre-2.5 queue files. */
if (state->client_port == 0 ) {
state->client_port = NO_CLIENT_PORT;
state->server_port = state->client_port;
}
}
/* cleanup_milter_inspect - run message through mail filter */
void cleanup_milter_inspect(CLEANUP_STATE *state, MILTERS *milters)
{
const char *myname = "cleanup_milter" ;
const char *resp;
if (msg_verbose)
msg_info("enter %s" , myname);
/*
* Initialize , in case we ' re called via smtpd ( 8 ) .
*/
if (state->client_name == 0 )
cleanup_milter_client_init(state);
/*
* Prologue : prepare for Milter header / body checks .
*/
if (*var_milt_head_checks)
cleanup_milter_header_checks_reinit(state);
/*
* Process mail filter replies . The reply format is verified by the mail
* filter library .
*/
if ((resp = milter_message(milters, state->handle->stream,
state->data_offset, state->auto_hdrs)) != 0 )
cleanup_milter_apply(state, "END-OF-MESSAGE" , resp);
/*
* Epilogue : finalize Milter header / body checks .
*/
if (*var_milt_head_checks)
cleanup_milter_hbc_finish(state);
if (msg_verbose)
msg_info("leave %s" , myname);
}
/* cleanup_milter_emul_mail - emulate connect/ehlo/mail event */
void cleanup_milter_emul_mail(CLEANUP_STATE *state,
MILTERS *milters,
const char *addr)
{
const char *resp;
const char *helo;
const char *argv[2 ];
/*
* Per - connection initialization .
*/
milter_macro_callback(milters, cleanup_milter_eval, (void *) state);
milter_edit_callback(milters,
cleanup_add_header, cleanup_upd_header,
cleanup_ins_header, cleanup_del_header,
cleanup_chg_from, cleanup_add_rcpt,
cleanup_add_rcpt_par, cleanup_del_rcpt,
cleanup_repl_body, (void *) state);
if (state->client_name == 0 )
cleanup_milter_client_init(state);
/*
* Emulate SMTP events .
*/
if ((resp = milter_conn_event(milters, state->client_name, state->client_addr,
state->client_port, state->client_af)) != 0 ) {
cleanup_milter_apply(state, "CONNECT" , resp);
return ;
}
#define PRETEND_ESMTP 1
if (CLEANUP_MILTER_OK(state)) {
if ((helo = nvtable_find(state->attr, MAIL_ATTR_ACT_HELO_NAME)) == 0 )
helo = state->client_name;
if ((resp = milter_helo_event(milters, helo, PRETEND_ESMTP)) != 0 ) {
cleanup_milter_apply(state, "EHLO" , resp);
return ;
}
}
if (CLEANUP_MILTER_OK(state)) {
if (state->milter_ext_from == 0 )
state->milter_ext_from = vstring_alloc(100 );
/* Sendmail 8.13 does not externalize the null address. */
if (*addr)
quote_821_local(state->milter_ext_from, addr);
else
vstring_strcpy(state->milter_ext_from, addr);
argv[0 ] = STR(state->milter_ext_from);
argv[1 ] = 0 ;
if ((resp = milter_mail_event(milters, argv)) != 0 ) {
cleanup_milter_apply(state, "MAIL" , resp);
return ;
}
}
}
/* cleanup_milter_emul_rcpt - emulate rcpt event */
void cleanup_milter_emul_rcpt(CLEANUP_STATE *state,
MILTERS *milters,
const char *addr)
{
const char *myname = "cleanup_milter_emul_rcpt" ;
const char *resp;
const char *argv[2 ];
/*
* Sanity check .
*/
if (state->client_name == 0 )
msg_panic("%s: missing client info initialization" , myname);
/*
* CLEANUP_STAT_CONT and CLEANUP_STAT_DEFER both update the reason
* attribute , but CLEANUP_STAT_DEFER takes precedence . It terminates
* queue record processing , and prevents bounces from being sent .
*/
if (state->milter_ext_rcpt == 0 )
state->milter_ext_rcpt = vstring_alloc(100 );
/* Sendmail 8.13 does not externalize the null address. */
if (*addr)
quote_821_local(state->milter_ext_rcpt, addr);
else
vstring_strcpy(state->milter_ext_rcpt, addr);
argv[0 ] = STR(state->milter_ext_rcpt);
argv[1 ] = 0 ;
if ((resp = milter_rcpt_event(milters, MILTER_FLAG_NONE, argv)) != 0
&& cleanup_milter_apply(state, "RCPT" , resp) != 0 ) {
msg_warn("%s: milter configuration error: can't reject recipient "
"in non-smtpd(8) submission" , state->queue_id);
msg_warn("%s: message not accepted, try again later" , state->queue_id);
CLEANUP_MILTER_SET_REASON(state, "4.3.5 Server configuration error" );
state->errs |= CLEANUP_STAT_DEFER;
}
}
/* cleanup_milter_emul_data - emulate data event */
void cleanup_milter_emul_data(CLEANUP_STATE *state, MILTERS *milters)
{
const char *myname = "cleanup_milter_emul_data" ;
const char *resp;
/*
* Sanity check .
*/
if (state->client_name == 0 )
msg_panic("%s: missing client info initialization" , myname);
if ((resp = milter_data_event(milters)) != 0 )
cleanup_milter_apply(state, "DATA" , resp);
}
#ifdef TEST
/*
* Queue file editing driver for regression tests . In this case it is OK to
* report fatal errors after I / O errors .
*/
#include <stdio.h>
#include <msg_vstream.h>
#include <vstring_vstream.h>
#include <mail_addr.h>
#include <mail_version.h>
#undef msg_verbose
char *cleanup_path;
VSTRING *cleanup_trace_path;
VSTRING *cleanup_strip_chars;
int cleanup_comm_canon_flags;
MAPS *cleanup_comm_canon_maps;
int cleanup_ext_prop_mask;
ARGV *cleanup_masq_domains;
int cleanup_masq_flags;
MAPS *cleanup_rcpt_bcc_maps;
int cleanup_rcpt_canon_flags;
MAPS *cleanup_rcpt_canon_maps;
MAPS *cleanup_send_bcc_maps;
int cleanup_send_canon_flags;
MAPS *cleanup_send_canon_maps;
int var_dup_filter_limit = DEF_DUP_FILTER_LIMIT;
char *var_empty_addr = DEF_EMPTY_ADDR;
MAPS *cleanup_virt_alias_maps;
char *var_milt_daemon_name = "host.example.com" ;
char *var_milt_v = DEF_MILT_V;
MILTERS *cleanup_milters = (MILTERS *) ((char *) sizeof (*cleanup_milters));
char *var_milt_head_checks = "" ;
/* Dummies to satisfy unused external references. */
int cleanup_masquerade_internal(CLEANUP_STATE *state, VSTRING *addr, ARGV *masq_domains)
{
msg_panic("cleanup_masquerade_internal dummy" );
}
int cleanup_rewrite_internal(const char *context, VSTRING *result,
const char *addr)
{
vstring_strcpy(result, addr);
return (0 );
}
int cleanup_map11_internal(CLEANUP_STATE *state, VSTRING *addr,
MAPS *maps, int propagate)
{
msg_panic("cleanup_map11_internal dummy" );
}
ARGV *cleanup_map1n_internal(CLEANUP_STATE *state, const char *addr,
MAPS *maps, int propagate)
{
msg_panic("cleanup_map1n_internal dummy" );
}
void cleanup_envelope(CLEANUP_STATE *state, int type, const char *buf,
ssize_t len)
{
msg_panic("cleanup_envelope dummy" );
}
static void usage(void )
{
msg_warn("usage:" );
msg_warn(" verbose on|off" );
msg_warn(" open pathname" );
msg_warn(" close" );
msg_warn(" add_header index name [value]" );
msg_warn(" ins_header index name [value]" );
msg_warn(" upd_header index name [value]" );
msg_warn(" del_header index name" );
msg_warn(" chg_from addr parameters" );
msg_warn(" add_rcpt addr" );
msg_warn(" add_rcpt_par addr parameters" );
msg_warn(" del_rcpt addr" );
msg_warn(" replbody pathname" );
msg_warn(" header_checks type:name" );
}
/* flatten_args - unparse partial command line */
static void flatten_args(VSTRING *buf, char **argv)
{
char **cpp;
VSTRING_RESET(buf);
for (cpp = argv; *cpp; cpp++) {
vstring_strcat(buf, *cpp);
if (cpp[1 ])
VSTRING_ADDCH(buf, ' ' );
}
VSTRING_TERMINATE(buf);
}
/* open_queue_file - open an unedited queue file (all-zero dummy PTRs) */
static void open_queue_file(CLEANUP_STATE *state, const char *path)
{
VSTRING *buf = vstring_alloc(100 );
off_t curr_offset;
int rec_type;
long msg_seg_len;
long data_offset;
long rcpt_count;
long qmgr_opts;
const HEADER_OPTS *opts;
if (state->dst != 0 ) {
msg_warn("closing %s" , cleanup_path);
vstream_fclose(state->dst);
state->dst = 0 ;
myfree(cleanup_path);
cleanup_path = 0 ;
}
if ((state->dst = vstream_fopen(path, O_RDWR, 0 )) == 0 ) {
msg_warn("open %s: %m" , path);
} else {
var_drop_hdrs = "" ;
cleanup_path = mystrdup(path);
for (;;) {
if ((curr_offset = vstream_ftell(state->dst)) < 0 )
msg_fatal("file %s: vstream_ftell: %m" , cleanup_path);
if ((rec_type = rec_get_raw(state->dst, buf, 0 , REC_FLAG_NONE)) < 0 )
msg_fatal("file %s: missing SIZE or PTR record" , cleanup_path);
if (rec_type == REC_TYPE_SIZE) {
if (sscanf(STR(buf), "%ld %ld %ld %ld" ,
&msg_seg_len, &data_offset,
&rcpt_count, &qmgr_opts) != 4 )
msg_fatal("file %s: bad SIZE record: %s" ,
cleanup_path, STR(buf));
state->data_offset = data_offset;
state->xtra_offset = data_offset + msg_seg_len;
} else if (rec_type == REC_TYPE_FROM) {
state->sender_pt_offset = curr_offset;
if (LEN(buf) < REC_TYPE_PTR_PAYL_SIZE
&& rec_get_raw(state->dst, buf, 0 , REC_FLAG_NONE) != REC_TYPE_PTR)
msg_fatal("file %s: missing PTR record after short sender" ,
cleanup_path);
if ((state->sender_pt_target = vstream_ftell(state->dst)) < 0 )
msg_fatal("file %s: missing END record" , cleanup_path);
} else if (rec_type == REC_TYPE_PTR) {
if (state->data_offset < 0 )
msg_fatal("file %s: missing SIZE record" , cleanup_path);
if (curr_offset < state->data_offset
|| curr_offset > state->xtra_offset) {
if (state->append_rcpt_pt_offset < 0 ) {
state->append_rcpt_pt_offset = curr_offset;
if (atol(STR(buf)) != 0 )
msg_fatal("file %s: bad dummy recipient PTR record: %s" ,
cleanup_path, STR(buf));
if ((state->append_rcpt_pt_target =
vstream_ftell(state->dst)) < 0 )
msg_fatal("file %s: vstream_ftell: %m" , cleanup_path);
} else if (curr_offset > state->xtra_offset
&& state->append_meta_pt_offset < 0 ) {
state->append_meta_pt_offset = curr_offset;
if (atol(STR(buf)) != 0 )
msg_fatal("file %s: bad dummy meta PTR record: %s" ,
cleanup_path, STR(buf));
if ((state->append_meta_pt_target =
vstream_ftell(state->dst)) < 0 )
msg_fatal("file %s: vstream_ftell: %m" , cleanup_path);
}
} else {
if (state->append_hdr_pt_offset < 0 ) {
state->append_hdr_pt_offset = curr_offset;
if (atol(STR(buf)) != 0 )
msg_fatal("file %s: bad dummy header PTR record: %s" ,
cleanup_path, STR(buf));
if ((state->append_hdr_pt_target =
vstream_ftell(state->dst)) < 0 )
msg_fatal("file %s: vstream_ftell: %m" , cleanup_path);
}
}
} else if (rec_type == REC_TYPE_NORM && state->hop_count == 0
&& (opts = header_opts_find(STR(buf))) != 0
&& opts->type == HDR_RECEIVED) {
state->hop_count += 1 ;
/* XXX Only the first line of the first Received: header. */
argv_add(state->auto_hdrs, STR(buf), ARGV_END);
}
if (state->append_rcpt_pt_offset > 0
&& state->append_hdr_pt_offset > 0
&& state->hop_count > 0
&& (rec_type == REC_TYPE_END
|| state->append_meta_pt_offset > 0 ))
break ;
}
if (msg_verbose) {
msg_info("append_rcpt_pt_offset %ld append_rcpt_pt_target %ld" ,
(long ) state->append_rcpt_pt_offset,
(long ) state->append_rcpt_pt_target);
msg_info("append_hdr_pt_offset %ld append_hdr_pt_target %ld" ,
(long ) state->append_hdr_pt_offset,
(long ) state->append_hdr_pt_target);
}
}
vstring_free(buf);
}
static void close_queue_file(CLEANUP_STATE *state)
{
(void ) vstream_fclose(state->dst);
state->dst = 0 ;
myfree(cleanup_path);
cleanup_path = 0 ;
}
int main(int unused_argc, char **argv)
{
VSTRING *inbuf = vstring_alloc(100 );
VSTRING *arg_buf = vstring_alloc(100 );
char *bufp;
int istty = isatty(vstream_fileno(VSTREAM_IN));
CLEANUP_STATE *state = cleanup_state_alloc((VSTREAM *) 0 );
const char *parens = "{}" ;
state->queue_id = mystrdup("NOQUEUE" );
state->sender = mystrdup("sender" );
state->recip = mystrdup("recipient" );
state->client_name = "client_name" ;
state->client_addr = "client_addr" ;
state->flags |= CLEANUP_FLAG_FILTER_ALL;
msg_vstream_init(argv[0 ], VSTREAM_ERR);
var_line_limit = DEF_LINE_LIMIT;
var_header_limit = DEF_HEADER_LIMIT;
var_enable_orcpt = DEF_ENABLE_ORCPT;
var_info_log_addr_form = DEF_INFO_LOG_ADDR_FORM;
for (;;) {
ARGV *argv;
ssize_t index;
const char *resp = 0 ;
if (istty) {
vstream_printf("- " );
vstream_fflush(VSTREAM_OUT);
}
if (vstring_fgets_nonl(inbuf, VSTREAM_IN) == 0 )
break ;
bufp = vstring_str(inbuf);
if (!istty) {
vstream_printf("> %s\n" , bufp);
vstream_fflush(VSTREAM_OUT);
}
if (*bufp == '#' || *bufp == 0 || allspace(bufp))
continue ;
argv = argv_splitq(bufp, " " , parens);
if (argv->argc == 0 ) {
msg_warn("missing command" );
} else if (strcmp(argv->argv[0 ], "?" ) == 0 ) {
usage();
} else if (strcmp(argv->argv[0 ], "verbose" ) == 0 ) {
if (argv->argc != 2 ) {
msg_warn("bad verbose argument count: %ld" , (long ) argv->argc);
} else if (strcmp(argv->argv[1 ], "on" ) == 0 ) {
msg_verbose = 2 ;
} else if (strcmp(argv->argv[1 ], "off" ) == 0 ) {
msg_verbose = 0 ;
} else {
msg_warn("bad verbose argument" );
}
} else if (strcmp(argv->argv[0 ], "line_length_limit" ) == 0 ) {
if (argv->argc != 2 ) {
msg_warn("bad line_length_limit argument count: %ld" ,
(long ) argv->argc);
} else if (alldig(argv->argv[1 ]) == 0 ) {
msg_warn("bad line_length_limit argument count: %ld" ,
(long ) argv->argc);
} else if ((var_line_limit = atoi(argv->argv[1 ])) < DEF_LINE_LIMIT) {
msg_warn("bad line_length_limit argument" );
}
} else if (strcmp(argv->argv[0 ], "open" ) == 0 ) {
if (state->dst != 0 ) {
msg_info("closing %s" , VSTREAM_PATH(state->dst));
close_queue_file(state);
}
if (argv->argc != 2 ) {
msg_warn("bad open argument count: %ld" , (long ) argv->argc);
} else {
open_queue_file(state, argv->argv[1 ]);
}
} else if (strcmp(argv->argv[0 ], "enable_original_recipient" ) == 0 ) {
if (argv->argc == 1 ) {
msg_info("enable_original_recipient: %d" , var_enable_orcpt);
} else if (argv->argc != 2 ) {
msg_warn("bad enable_original_recipient argument count: %ld" ,
(long ) argv->argc);
} else if (!alldig(argv->argv[1 ])) {
msg_warn("non-numeric enable_original_recipient argument: %s" ,
argv->argv[1 ]);
} else {
var_enable_orcpt = atoi(argv->argv[1 ]);
}
} else if (state->dst == 0 ) {
msg_warn("no open queue file" );
} else if (strcmp(argv->argv[0 ], "close" ) == 0 ) {
if (*var_milt_head_checks) {
cleanup_milter_hbc_finish(state);
myfree(var_milt_head_checks);
var_milt_head_checks = "" ;
cleanup_milter_header_checks_deinit();
}
close_queue_file(state);
} else if (cleanup_milter_hbc_reply && LEN(cleanup_milter_hbc_reply)) {
/* Postfix libmilter would skip further requests. */
msg_info("ignoring: %s %s %s" , argv->argv[0 ],
argv->argc > 1 ? argv->argv[1 ] : "" ,
argv->argc > 2 ? argv->argv[2 ] : "" );
} else if (strcmp(argv->argv[0 ], "add_header" ) == 0 ) {
if (argv->argc < 2 ) {
msg_warn("bad add_header argument count: %ld" ,
(long ) argv->argc);
} else {
flatten_args(arg_buf, argv->argv + 2 );
resp = cleanup_add_header(state, argv->argv[1 ], " " , STR(arg_buf));
}
} else if (strcmp(argv->argv[0 ], "ins_header" ) == 0 ) {
if (argv->argc < 3 ) {
msg_warn("bad ins_header argument count: %ld" ,
(long ) argv->argc);
} else if ((index = atoi(argv->argv[1 ])) < 1 ) {
msg_warn("bad ins_header index value" );
} else {
flatten_args(arg_buf, argv->argv + 3 );
resp = cleanup_ins_header(state, index, argv->argv[2 ], " " , STR(arg_buf));
}
} else if (strcmp(argv->argv[0 ], "upd_header" ) == 0 ) {
if (argv->argc < 3 ) {
msg_warn("bad upd_header argument count: %ld" ,
(long ) argv->argc);
} else if ((index = atoi(argv->argv[1 ])) < 1 ) {
msg_warn("bad upd_header index value" );
} else {
flatten_args(arg_buf, argv->argv + 3 );
resp = cleanup_upd_header(state, index, argv->argv[2 ], " " , STR(arg_buf));
}
} else if (strcmp(argv->argv[0 ], "del_header" ) == 0 ) {
if (argv->argc != 3 ) {
msg_warn("bad del_header argument count: %ld" ,
(long ) argv->argc);
} else if ((index = atoi(argv->argv[1 ])) < 1 ) {
msg_warn("bad del_header index value" );
} else {
cleanup_del_header(state, index, argv->argv[2 ]);
}
} else if (strcmp(argv->argv[0 ], "chg_from" ) == 0 ) {
if (argv->argc != 3 ) {
msg_warn("bad chg_from argument count: %ld" , (long ) argv->argc);
} else {
char *arg = argv->argv[2 ];
const char *err;
if (*arg == parens[0 ]
&& (err = extpar(&arg, parens, EXTPAR_FLAG_NONE)) != 0 ) {
msg_warn("%s in \" %s\"" , err, arg);
} else {
cleanup_chg_from(state, argv->argv[1 ], arg);
}
}
} else if (strcmp(argv->argv[0 ], "add_rcpt" ) == 0 ) {
if (argv->argc != 2 ) {
msg_warn("bad add_rcpt argument count: %ld" , (long ) argv->argc);
} else {
cleanup_add_rcpt(state, argv->argv[1 ]);
}
} else if (strcmp(argv->argv[0 ], "add_rcpt_par" ) == 0 ) {
if (argv->argc != 3 ) {
msg_warn("bad add_rcpt_par argument count: %ld" ,
(long ) argv->argc);
} else {
cleanup_add_rcpt_par(state, argv->argv[1 ], argv->argv[2 ]);
}
} else if (strcmp(argv->argv[0 ], "del_rcpt" ) == 0 ) {
if (argv->argc != 2 ) {
msg_warn("bad del_rcpt argument count: %ld" , (long ) argv->argc);
} else {
cleanup_del_rcpt(state, argv->argv[1 ]);
}
} else if (strcmp(argv->argv[0 ], "replbody" ) == 0 ) {
if (argv->argc != 2 ) {
msg_warn("bad replbody argument count: %ld" , (long ) argv->argc);
} else {
VSTREAM *fp;
VSTRING *buf;
if ((fp = vstream_fopen(argv->argv[1 ], O_RDONLY, 0 )) == 0 ) {
msg_warn("open %s file: %m" , argv->argv[1 ]);
} else {
buf = vstring_alloc(100 );
cleanup_repl_body(state, MILTER_BODY_START,
REC_TYPE_NORM, buf);
while (vstring_get_nonl(buf, fp) != VSTREAM_EOF)
cleanup_repl_body(state, MILTER_BODY_LINE,
REC_TYPE_NORM, buf);
cleanup_repl_body(state, MILTER_BODY_END,
REC_TYPE_NORM, buf);
vstring_free(buf);
vstream_fclose(fp);
}
}
} else if (strcmp(argv->argv[0 ], "header_checks" ) == 0 ) {
if (argv->argc != 2 ) {
msg_warn("bad header_checks argument count: %ld" ,
(long ) argv->argc);
} else if (*var_milt_head_checks) {
msg_warn("can't change header checks" );
} else {
var_milt_head_checks = mystrdup(argv->argv[1 ]);
cleanup_milter_header_checks_init();
}
} else if (strcmp(argv->argv[0 ], "sender_bcc_maps" ) == 0 ) {
if (argv->argc != 2 ) {
msg_warn("bad sender_bcc_maps argument count: %ld" ,
(long ) argv->argc);
} else {
if (cleanup_send_bcc_maps)
maps_free(cleanup_send_bcc_maps);
cleanup_send_bcc_maps =
maps_create("sender_bcc_maps" , argv->argv[1 ],
DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
| DICT_FLAG_UTF8_REQUEST);
state->flags |= CLEANUP_FLAG_BCC_OK;
var_rcpt_delim = "" ;
}
} else {
msg_warn("bad command: %s" , argv->argv[0 ]);
}
argv_free(argv);
if (resp)
cleanup_milter_apply(state, "END-OF-MESSAGE" , resp);
}
vstring_free(inbuf);
vstring_free(arg_buf);
if (state->append_meta_pt_offset >= 0 ) {
if (state->flags)
msg_info("flags = %s" , cleanup_strflags(state->flags));
if (state->errs)
msg_info("errs = %s" , cleanup_strerror(state->errs));
}
cleanup_state_free(state);
if (*var_milt_head_checks)
myfree(var_milt_head_checks);
return (0 );
}
#endif
Messung V0.5 in Prozent C=76 H=94 G=85
¤ Dauer der Verarbeitung: 0.97 Sekunden
(vorverarbeitet am 2026-08-08)
¤
*© Formatika GbR, Deutschland