#include <sys_defs.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#ifdef USE_LIBMILTER_INCLUDES
#include <libmilter/mfapi.h>
#include <libmilter/mfdef.h>
#ifndef SMFIF_CHGBODY
#define SMFIF_CHGBODY SMFIF_MODBODY
#endif
#ifndef SMFIF_CHGHDRS
#define SMFIF_CHGHDRS SMFIF_MODHDRS
#endif
#if defined(SMFIC_UNKNOWN) && !defined(SMFIP_NOUNKNOWN)
#define SMFIP_NOUNKNOWN (1L<<8)
#endif
#if defined(SMFIC_DATA) && !defined(SMFIP_NODATA)
#define SMFIP_NODATA (1L<<9)
#endif
#else
#define SMFIC_ABORT 'A'
#define SMFIC_BODY 'B'
#define SMFIC_CONNECT 'C'
#define SMFIC_MACRO 'D'
#define SMFIC_BODYEOB 'E'
#define SMFIC_HELO 'H'
#define SMFIC_HEADER 'L'
#define SMFIC_MAIL 'M'
#define SMFIC_EOH 'N'
#define SMFIC_OPTNEG 'O'
#define SMFIC_QUIT 'Q'
#define SMFIC_RCPT 'R'
#define SMFIC_DATA 'T'
#define SMFIC_UNKNOWN 'U'
#define SMFIR_ADDRCPT '+'
#define SMFIR_DELRCPT '-'
#define SMFIR_ACCEPT 'a'
#define SMFIR_REPLBODY 'b'
#define SMFIR_CONTINUE 'c'
#define SMFIR_DISCARD 'd'
#define SMFIR_CONN_FAIL 'f'
#define SMFIR_CHGHEADER 'm'
#define SMFIR_PROGRESS 'p'
#define SMFIR_REJECT 'r'
#define SMFIR_TEMPFAIL 't'
#define SMFIR_SHUTDOWN '4'
#define SMFIR_ADDHEADER 'h'
#define SMFIR_INSHEADER 'i'
#define SMFIR_REPLYCODE 'y'
#define SMFIR_QUARANTINE 'q'
#define SMFIP_NOCONNECT (1L<<0)
#define SMFIP_NOHELO (1L<<1)
#define SMFIP_NOMAIL (1L<<2)
#define SMFIP_NORCPT (1L<<3)
#define SMFIP_NOBODY (1L<<4)
#define SMFIP_NOHDRS (1L<<5)
#define SMFIP_NOEOH (1L<<6)
#define SMFIP_NOHREPL (1L<<7)
#define SMFIP_NOUNKNOWN (1L<<8)
#define SMFIP_NODATA (1L<<9)
#define SMFIF_ADDHDRS (1L<<0)
#define SMFIF_CHGBODY (1L<<1)
#define SMFIF_ADDRCPT (1L<<2)
#define SMFIF_DELRCPT (1L<<3)
#define SMFIF_CHGHDRS (1L<<4)
#define SMFIF_QUARANTINE (1L<<5)
#define SMFIA_UNKNOWN 'U'
#define SMFIA_UNIX 'L'
#define SMFIA_INET '4'
#define SMFIA_INET6 '6'
#define MILTER_CHUNK_SIZE 65535
#endif
#include <msg.h>
#include <mymalloc.h>
#include <split_at.h>
#include <connect.h>
#include <argv.h>
#include <name_mask.h>
#include <name_code.h>
#include <stringops.h>
#include <mail_params.h>
#include <mail_proto.h>
#include <rec_type.h>
#include <record.h>
#include <mime_state.h>
#include <is_header.h>
#include <milter.h>
typedef struct {
MILTER m;
int conn_timeout;
int cmd_timeout;
int msg_timeout;
char *protocol;
char *def_action;
int version;
int rq_mask;
int ev_mask;
#ifndef USE_LIBMILTER_INCLUDES
int np_mask;
#endif
VSTRING *buf;
VSTRING *body;
VSTREAM *fp;
int state;
char *def_reply;
} MILTER8;
#define LIBMILTER_AUTO_DISCONNECT
#define MILTER8_STAT_ERROR 1
#define MILTER8_STAT_CLOSED 2
#define MILTER8_STAT_READY 3
#define MILTER8_STAT_ENVELOPE 4
#define MILTER8_STAT_MESSAGE 5
#define MILTER8_STAT_ACCEPT_CON 6
#define MILTER8_STAT_ACCEPT_MSG 7
#define MILTER8_STAT_REJECT_CON 8
#define MILTER8_DATA_END 0
#define MILTER8_DATA_HLONG 1
#define MILTER8_DATA_BUFFER 2
#define MILTER8_DATA_STRING 3
#define MILTER8_DATA_NSHORT 4
#define MILTER8_DATA_ARGV 5
#define MILTER8_DATA_OCTET 6
#define XXX_MAX_DATA (MILTER_CHUNK_SIZE * 2)
#define XXX_TIMEOUT 10
#ifndef USE_LIBMILTER_INCLUDES
#define MILTER8_V1_PROTO_MASK \
(SMFIP_NOCONNECT | SMFIP_NOHELO | SMFIP_NOMAIL | SMFIP_NORCPT | \
SMFIP_NOBODY | SMFIP_NOHDRS | SMFIP_NOEOH)
static NAME_CODE milter8_event_masks[] = {
"2", MILTER8_V1_PROTO_MASK,
"3", MILTER8_V1_PROTO_MASK | SMFIP_NOUNKNOWN,
"4", MILTER8_V1_PROTO_MASK | SMFIP_NOUNKNOWN | SMFIP_NODATA,
"no_header_reply", SMFIP_NOHREPL,
0, -1,
};
static NAME_CODE milter8_versions[] = {
"2", 2,
"3", 3,
"4", 4,
"no_header_reply", 0,
0, -1,
};
#endif
static NAME_CODE smfic_table[] = {
"SMFIC_ABORT", SMFIC_ABORT,
"SMFIC_BODY", SMFIC_BODY,
"SMFIC_CONNECT", SMFIC_CONNECT,
"SMFIC_MACRO", SMFIC_MACRO,
"SMFIC_BODYEOB", SMFIC_BODYEOB,
"SMFIC_HELO", SMFIC_HELO,
"SMFIC_HEADER", SMFIC_HEADER,
"SMFIC_MAIL", SMFIC_MAIL,
"SMFIC_EOH", SMFIC_EOH,
"SMFIC_OPTNEG", SMFIC_OPTNEG,
"SMFIC_QUIT", SMFIC_QUIT,
"SMFIC_RCPT", SMFIC_RCPT,
#ifdef SMFIC_DATA
"SMFIC_DATA", SMFIC_DATA,
#endif
#ifdef SMFIC_UNKNOWN
"SMFIC_UNKNOWN", SMFIC_UNKNOWN,
#endif
0, 0,
};
static NAME_CODE smfir_table[] = {
"SMFIR_ADDRCPT", SMFIR_ADDRCPT,
"SMFIR_DELRCPT", SMFIR_DELRCPT,
"SMFIR_ACCEPT", SMFIR_ACCEPT,
"SMFIR_REPLBODY", SMFIR_REPLBODY,
"SMFIR_CONTINUE", SMFIR_CONTINUE,
"SMFIR_DISCARD", SMFIR_DISCARD,
#ifdef SMFIR_CONN_FAIL
"SMFIR_CONN_FAIL", SMFIR_CONN_FAIL,
#endif
#ifdef SMFIR_CHGHEADER
"SMFIR_CHGHEADER", SMFIR_CHGHEADER,
#endif
"SMFIR_PROGRESS", SMFIR_PROGRESS,
"SMFIR_REJECT", SMFIR_REJECT,
"SMFIR_TEMPFAIL", SMFIR_TEMPFAIL,
#ifdef SMFIR_SHUTDOWN
"SMFIR_SHUTDOWN", SMFIR_SHUTDOWN,
#endif
"SMFIR_ADDHEADER", SMFIR_ADDHEADER,
#ifdef SMFIR_INSHEADER
"SMFIR_INSHEADER", SMFIR_INSHEADER,
#endif
"SMFIR_REPLYCODE", SMFIR_REPLYCODE,
#ifdef SMFIR_QUARANTINE
"SMFIR_QUARANTINE", SMFIR_QUARANTINE,
#endif
0, 0,
};
static NAME_MASK smfip_table[] = {
"SMFIP_NOCONNECT", SMFIP_NOCONNECT,
"SMFIP_NOHELO", SMFIP_NOHELO,
"SMFIP_NOMAIL", SMFIP_NOMAIL,
"SMFIP_NORCPT", SMFIP_NORCPT,
"SMFIP_NOBODY", SMFIP_NOBODY,
"SMFIP_NOHDRS", SMFIP_NOHDRS,
"SMFIP_NOEOH", SMFIP_NOEOH,
#ifdef SMFIP_NOHREPL
"SMFIP_NOHREPL", SMFIP_NOHREPL,
#endif
#ifdef SMFIP_NOUNKNOWN
"SMFIP_NOUNKNOWN", SMFIP_NOUNKNOWN,
#endif
#ifdef SMFIP_NODATA
"SMFIP_NODATA", SMFIP_NODATA,
#endif
0, 0,
};
static NAME_MASK smfif_table[] = {
"SMFIF_ADDHDRS", SMFIF_ADDHDRS,
"SMFIF_CHGBODY", SMFIF_CHGBODY,
"SMFIF_ADDRCPT", SMFIF_ADDRCPT,
"SMFIF_DELRCPT", SMFIF_DELRCPT,
"SMFIF_CHGHDRS", SMFIF_CHGHDRS,
#ifdef SMFIF_QUARANTINE
"SMFIF_QUARANTINE", SMFIF_QUARANTINE,
#endif
0, 0,
};
#define STR(x) vstring_str(x)
#define LEN(x) VSTRING_LEN(x)
static const char *milter8_def_reply(MILTER8 *milter, const char *reply)
{
if (milter->def_reply)
myfree(milter->def_reply);
milter->def_reply = reply ? mystrdup(reply) : 0;
return (milter->def_reply);
}
static int milter8_conf_error(MILTER8 *milter)
{
const char *reply;
if (milter->fp != 0) {
(void) vstream_fclose(milter->fp);
milter->fp = 0;
}
if (strcasecmp(milter->def_action, "accept") == 0) {
reply = 0;
} else {
reply = "451 4.3.5 Server configuration problem - try again later";
}
milter8_def_reply(milter, reply);
return (milter->state = MILTER8_STAT_ERROR);
}
static int milter8_comm_error(MILTER8 *milter)
{
const char *reply;
if (milter->fp != 0) {
(void) vstream_fclose(milter->fp);
milter->fp = 0;
}
if (strcasecmp(milter->def_action, "accept") == 0) {
reply = 0;
} else if (strcasecmp(milter->def_action, "reject") == 0) {
reply = "550 5.5.0 Service unavailable";
} else if (strcasecmp(milter->def_action, "tempfail") == 0) {
reply = "451 4.7.1 Service unavailable - try again later";
} else {
msg_warn("milter %s: unrecognized default action: %s",
milter->m.name, milter->def_action);
reply = "451 4.3.5 Server configuration problem - try again later";
}
milter8_def_reply(milter, reply);
return (milter->state = MILTER8_STAT_ERROR);
}
static void milter8_close_stream(MILTER8 *milter)
{
if (milter->fp != 0) {
(void) vstream_fclose(milter->fp);
milter->fp = 0;
}
milter->state = MILTER8_STAT_CLOSED;
}
static int milter8_read_resp(MILTER8 *milter, int event, unsigned char *command,
ssize_t *data_len)
{
UINT32_TYPE len;
ssize_t pkt_len;
const char *smfic_name;
int cmd;
if ((vstream_fread(milter->fp, (char *) &len, UINT32_SIZE))
!= UINT32_SIZE) {
smfic_name = str_name_code(smfic_table, event);
msg_warn("milter %s: can't read %s reply packet header: %m",
milter->m.name, smfic_name != 0 ?
smfic_name : "(unknown MTA event)");
return (milter8_comm_error(milter));
} else if ((pkt_len = ntohl(len)) < 1) {
msg_warn("milter %s: bad packet length: %ld",
milter->m.name, (long) pkt_len);
return (milter8_comm_error(milter));
} else if (pkt_len > XXX_MAX_DATA) {
msg_warn("milter %s: unreasonable packet length: %ld",
milter->m.name, (long) pkt_len);
return (milter8_comm_error(milter));
}
else if ((cmd = VSTREAM_GETC(milter->fp)) == VSTREAM_EOF) {
msg_warn("milter %s: EOF while reading command code: %m",
milter->m.name);
return (milter8_comm_error(milter));
}
else {
*command = cmd;
*data_len = pkt_len - 1;
return (0);
}
}
static int vmilter8_read_data(MILTER8 *milter, ssize_t data_len, va_list ap)
{
const char *myname = "milter8_read_data";
int arg_type;
int data_left;
UINT32_TYPE net_long;
UINT32_TYPE *host_long_ptr;
VSTRING *buf;
int ch;
for (data_left = data_len; (arg_type = va_arg(ap, int)) > 0; ) {
switch (arg_type) {
case MILTER8_DATA_HLONG:
if (data_left < UINT32_SIZE) {
msg_warn("milter %s: input packet too short for network long",
milter->m.name);
return (milter8_comm_error(milter));
}
host_long_ptr = va_arg(ap, UINT32_TYPE *);
if (vstream_fread(milter->fp, (char *) &net_long, UINT32_SIZE)
!= UINT32_SIZE) {
msg_warn("milter %s: EOF while reading network long: %m",
milter->m.name);
return (milter8_comm_error(milter));
}
data_left -= UINT32_SIZE;
*host_long_ptr = ntohl(net_long);
break;
case MILTER8_DATA_BUFFER:
if (data_left < 0) {
msg_warn("milter %s: no data in input packet", milter->m.name);
return (milter8_comm_error(milter));
}
buf = va_arg(ap, VSTRING *);
VSTRING_RESET(buf);
VSTRING_SPACE(buf, data_left);
if (vstream_fread(milter->fp, (char *) STR(buf), data_left)
!= data_left) {
msg_warn("milter %s: EOF while reading data: %m", milter->m.name);
return (milter8_comm_error(milter));
}
VSTRING_AT_OFFSET(buf, data_left);
data_left = 0;
break;
case MILTER8_DATA_STRING:
if (data_left < 1) {
msg_warn("milter %s: packet too short for string",
milter->m.name);
return (milter8_comm_error(milter));
}
buf = va_arg(ap, VSTRING *);
VSTRING_RESET(buf);
for (;;) {
if ((ch = VSTREAM_GETC(milter->fp)) == VSTREAM_EOF) {
msg_warn("%s: milter %s: EOF while reading string: %m",
myname, milter->m.name);
return (milter8_comm_error(milter));
}
data_left -= 1;
if (ch == 0)
break;
VSTRING_ADDCH(buf, ch);
if (data_left <= 0) {
msg_warn("%s: milter %s: missing string null termimator",
myname, milter->m.name);
return (milter8_comm_error(milter));
}
}
VSTRING_TERMINATE(buf);
break;
default:
msg_panic("%s: unknown argument type: %d", myname, arg_type);
}
}
if (data_left > 0) {
msg_warn("%s: left-over data %ld bytes", myname, (long) data_left);
return (milter8_comm_error(milter));
}
if (data_left < 0)
msg_panic("%s: bad left-over data count %ld",
myname, (long) data_left);
return (0);
}
static int milter8_read_data(MILTER8 *milter, ssize_t data_len,...)
{
va_list ap;
int ret;
va_start(ap, data_len);
ret = vmilter8_read_data(milter, data_len, ap);
va_end(ap);
return (ret);
}
static ssize_t vmilter8_size_data(va_list ap)
{
const char *myname = "vmilter8_size_data";
ssize_t data_len;
int arg_type;
VSTRING *buf;
const char *str;
const char **cpp;
for (data_len = 0; (arg_type = va_arg(ap, int)) > 0; ) {
switch (arg_type) {
case MILTER8_DATA_HLONG:
(void) va_arg(ap, UINT32_TYPE);
data_len += UINT32_SIZE;
break;
case MILTER8_DATA_BUFFER:
buf = va_arg(ap, VSTRING *);
data_len += LEN(buf);
break;
case MILTER8_DATA_STRING:
str = va_arg(ap, char *);
data_len += strlen(str) + 1;
break;
case MILTER8_DATA_ARGV:
for (cpp = va_arg(ap, const char **); *cpp; cpp++)
data_len += strlen(*cpp) + 1;
break;
case MILTER8_DATA_NSHORT:
(void) va_arg(ap, unsigned);
data_len += UINT16_SIZE;
break;
case MILTER8_DATA_OCTET:
(void) va_arg(ap, unsigned);
data_len += 1;
break;
default:
msg_panic("%s: bad argument type: %d", myname, arg_type);
}
}
va_end(ap);
return (data_len);
}
static int vmilter8_write_cmd(MILTER8 *milter, int command, ssize_t data_len,
va_list ap)
{
const char *myname = "vmilter8_write_cmd";
int arg_type;
UINT32_TYPE pkt_len;
UINT32_TYPE host_long;
UINT32_TYPE net_long;
UINT16_TYPE net_short;
VSTRING *buf;
const char *str;
const char **cpp;
char ch;
if ((pkt_len = 1 + data_len) < 1)
msg_panic("%s: bad packet length %d", myname, pkt_len);
pkt_len = htonl(pkt_len);
(void) vstream_fwrite(milter->fp, (char *) &pkt_len, UINT32_SIZE);
(void) VSTREAM_PUTC(command, milter->fp);
while ((arg_type = va_arg(ap, int)) > 0) {
switch (arg_type) {
case MILTER8_DATA_HLONG:
host_long = va_arg(ap, UINT32_TYPE);
net_long = htonl(host_long);
(void) vstream_fwrite(milter->fp, (char *) &net_long, UINT32_SIZE);
break;
case MILTER8_DATA_BUFFER:
buf = va_arg(ap, VSTRING *);
(void) vstream_fwrite(milter->fp, STR(buf), LEN(buf));
break;
case MILTER8_DATA_STRING:
str = va_arg(ap, char *);
(void) vstream_fwrite(milter->fp, str, strlen(str) + 1);
break;
case MILTER8_DATA_OCTET:
ch = va_arg(ap, unsigned);
(void) vstream_fwrite(milter->fp, &ch, 1);
break;
case MILTER8_DATA_ARGV:
for (cpp = va_arg(ap, const char **); *cpp; cpp++)
(void) vstream_fwrite(milter->fp, *cpp, strlen(*cpp) + 1);
break;
case MILTER8_DATA_NSHORT:
net_short = va_arg(ap, unsigned);
(void) vstream_fwrite(milter->fp, (char *) &net_short, UINT16_SIZE);
break;
default:
msg_panic("%s: bad argument type: %d", myname, arg_type);
}
if (vstream_ferror(milter->fp)) {
msg_warn("milter %s: error writing command: %m", milter->m.name);
milter8_comm_error(milter);
break;
}
}
va_end(ap);
return (milter->state == MILTER8_STAT_ERROR);
}
static int milter8_write_cmd(MILTER8 *milter, int command,...)
{
va_list ap;
ssize_t data_len;
int err;
va_start(ap, command);
data_len = vmilter8_size_data(ap);
va_end(ap);
va_start(ap, command);
err = vmilter8_write_cmd(milter, command, data_len, ap);
va_end(ap);
return (err);
}
static const char *milter8_event(MILTER8 *milter, int event,
int skip_event_flag,
int skip_reply,
ARGV *macros,...)
{
const char *myname = "milter8_event";
va_list ap;
ssize_t data_len;
int err;
unsigned char cmd;
ssize_t data_size;
const char *smfic_name;
const char *smfir_name;
MILTERS *parent = milter->m.parent;
UINT32_TYPE index;
const char *edit_resp = 0;
const char *retval = 0;
VSTRING *body_line_buf = 0;
int done = 0;
#define DONT_SKIP_REPLY 0
if (milter->fp == 0 || milter->def_reply != 0) {
msg_warn("%s: attempt to send event %s to milter %s after error",
myname,
(smfic_name = str_name_code(smfic_table, event)) != 0 ?
smfic_name : "(unknown MTA event)", milter->m.name);
return (milter->def_reply);
}
#ifndef USE_LIBMILTER_INCLUDES
if ((skip_event_flag & milter->np_mask) != 0) {
if (msg_verbose)
msg_info("skipping non-protocol event %s for milter %s",
(smfic_name = str_name_code(smfic_table, event)) != 0 ?
smfic_name : "(unknown MTA event)", milter->m.name);
return (milter->def_reply);
}
#endif
if (msg_verbose) {
VSTRING *buf = vstring_alloc(100);
if (macros) {
if (macros->argc > 0) {
char **cpp;
for (cpp = macros->argv; *cpp && cpp[1]; cpp += 2)
vstring_sprintf_append(buf, " %s=%s", *cpp, cpp[1]);
}
}
msg_info("event: %s; macros:%s",
(smfic_name = str_name_code(smfic_table, event)) != 0 ?
smfic_name : "(unknown MTA event)", *STR(buf) ?
STR(buf) : " (none)");
vstring_free(buf);
}
if (macros) {
if (milter8_write_cmd(milter, SMFIC_MACRO,
MILTER8_DATA_OCTET, event,
MILTER8_DATA_ARGV, macros->argv,
MILTER8_DATA_END) != 0)
return (milter->def_reply);
}
if ((skip_event_flag & milter->ev_mask) != 0) {
if (msg_verbose)
msg_info("skipping event %s for milter %s",
(smfic_name = str_name_code(smfic_table, event)) != 0 ?
smfic_name : "(unknown MTA event)", milter->m.name);
return (milter->def_reply);
}
va_start(ap, macros);
data_len = vmilter8_size_data(ap);
va_end(ap);
va_start(ap, macros);
err = vmilter8_write_cmd(milter, event, data_len, ap);
va_end(ap);
if (err != 0)
return (milter->def_reply);
if (skip_reply) {
if (msg_verbose)
msg_info("skipping reply %s for milter %s",
(smfic_name = str_name_code(smfic_table, event)) != 0 ?
smfic_name : "(unknown MTA event)", milter->m.name);
return (milter->def_reply);
}
#define IN_CONNECT_EVENT(e) ((e) == SMFIC_CONNECT || (e) == SMFIC_HELO)
#define MILTER8_EVENT_BREAK(s) { \
retval = (s); \
done = 1; \
continue; \
}
while (done == 0) {
char *cp;
char *rp;
char ch;
if (milter8_read_resp(milter, event, &cmd, &data_size) != 0)
MILTER8_EVENT_BREAK(milter->def_reply);
if (msg_verbose)
msg_info("reply: %s data %ld bytes",
(smfir_name = str_name_code(smfir_table, cmd)) != 0 ?
smfir_name : "unknown", (long) data_size);
switch (cmd) {
case SMFIR_PROGRESS:
if (data_size != 0)
break;
continue;
case SMFIR_CONTINUE:
if (data_size != 0)
break;
MILTER8_EVENT_BREAK(milter->def_reply);
case SMFIR_ACCEPT:
if (data_size != 0)
break;
if (IN_CONNECT_EVENT(event)) {
#ifdef LIBMILTER_AUTO_DISCONNECT
milter8_close_stream(milter);
#endif
milter->state = MILTER8_STAT_ACCEPT_CON;
} else {
milter->state = MILTER8_STAT_ACCEPT_MSG;
}
MILTER8_EVENT_BREAK(milter->def_reply);
case SMFIR_DISCARD:
if (data_size != 0)
break;
if (IN_CONNECT_EVENT(event)) {
msg_warn("milter %s: DISCARD action is not allowed "
"for connect or helo", milter->m.name);
milter8_conf_error(milter);
MILTER8_EVENT_BREAK(milter->def_reply);
} else {
milter->state = MILTER8_STAT_ACCEPT_MSG;
MILTER8_EVENT_BREAK("D");
}
case SMFIR_REJECT:
if (data_size != 0)
break;
if (IN_CONNECT_EVENT(event)) {
#ifdef LIBMILTER_AUTO_DISCONNECT
milter8_close_stream(milter);
#endif
milter->state = MILTER8_STAT_REJECT_CON;
MILTER8_EVENT_BREAK(milter8_def_reply(milter, "550 5.7.1 Command rejected"));
} else {
MILTER8_EVENT_BREAK("550 5.7.1 Command rejected");
}
case SMFIR_TEMPFAIL:
if (data_size != 0)
break;
if (IN_CONNECT_EVENT(event)) {
#ifdef LIBMILTER_AUTO_DISCONNECT
milter8_close_stream(milter);
#endif
milter->state = MILTER8_STAT_REJECT_CON;
MILTER8_EVENT_BREAK(milter8_def_reply(milter,
"451 4.7.1 Service unavailable - try again later"));
} else {
MILTER8_EVENT_BREAK("451 4.7.1 Service unavailable - try again later");
}
#ifdef SMFIR_SHUTDOWN
case SMFIR_SHUTDOWN:
if (data_size != 0)
break;
#ifdef LIBMILTER_AUTO_DISCONNECT
milter8_close_stream(milter);
#endif
milter->state = MILTER8_STAT_REJECT_CON;
MILTER8_EVENT_BREAK(milter8_def_reply(milter, "S"));
#endif
case SMFIR_REPLYCODE:
if (milter8_read_data(milter, data_size,
MILTER8_DATA_BUFFER, milter->buf,
MILTER8_DATA_END) != 0)
MILTER8_EVENT_BREAK(milter->def_reply);
if ((STR(milter->buf)[0] != '4' && STR(milter->buf)[0] != '5')
|| !ISDIGIT(STR(milter->buf)[1])
|| !ISDIGIT(STR(milter->buf)[2])
|| (STR(milter->buf)[3] != ' ' && STR(milter->buf)[3] != '-')
|| STR(milter->buf)[4] != STR(milter->buf)[0]) {
msg_warn("milter %s: malformed reply: %s",
milter->m.name, STR(milter->buf));
milter8_conf_error(milter);
MILTER8_EVENT_BREAK(milter->def_reply);
}
if ((rp = cp = strchr(STR(milter->buf), '%')) != 0) {
for (;;) {
if ((ch = *cp++) == '%')
ch = *cp++;
*rp++ = ch;
if (ch == 0)
break;
}
}
if (IN_CONNECT_EVENT(event)) {
#ifdef LIBMILTER_AUTO_DISCONNECT
milter8_close_stream(milter);
#endif
milter->state = MILTER8_STAT_REJECT_CON;
MILTER8_EVENT_BREAK(milter8_def_reply(milter, STR(milter->buf)));
} else {
MILTER8_EVENT_BREAK(STR(milter->buf));
}
#ifdef SMFIR_QUARANTINE
case SMFIR_QUARANTINE:
if (milter8_read_data(milter, data_size,
MILTER8_DATA_BUFFER, milter->buf,
MILTER8_DATA_END) != 0)
MILTER8_EVENT_BREAK(milter->def_reply);
MILTER8_EVENT_BREAK("H");
#endif
default:
if (event == SMFIC_BODYEOB) {
switch (cmd) {
#ifdef SMFIR_CHGHEADER
case SMFIR_CHGHEADER:
if (milter8_read_data(milter, data_size,
MILTER8_DATA_HLONG, &index,
MILTER8_DATA_STRING, milter->buf,
MILTER8_DATA_STRING, milter->body,
MILTER8_DATA_END) != 0)
MILTER8_EVENT_BREAK(milter->def_reply);
if (index == 0)
index = 1;
if ((ssize_t) index < 1) {
msg_warn("milter %s: bad change header index: %ld",
milter->m.name, (long) index);
milter8_conf_error(milter);
MILTER8_EVENT_BREAK(milter->def_reply);
}
if (LEN(milter->buf) == 0) {
msg_warn("milter %s: null change header name",
milter->m.name);
milter8_conf_error(milter);
MILTER8_EVENT_BREAK(milter->def_reply);
}
if (STR(milter->body)[0])
edit_resp = parent->upd_header(parent->chg_context,
(ssize_t) index,
STR(milter->buf),
STR(milter->body));
else
edit_resp = parent->del_header(parent->chg_context,
(ssize_t) index,
STR(milter->buf));
if (edit_resp)
MILTER8_EVENT_BREAK(edit_resp);
continue;
#endif
case SMFIR_ADDHEADER:
if (milter8_read_data(milter, data_size,
MILTER8_DATA_STRING, milter->buf,
MILTER8_DATA_STRING, milter->body,
MILTER8_DATA_END) != 0)
MILTER8_EVENT_BREAK(milter->def_reply);
edit_resp = parent->add_header(parent->chg_context,
STR(milter->buf),
STR(milter->body));
if (edit_resp)
MILTER8_EVENT_BREAK(edit_resp);
continue;
#ifdef SMFIR_INSHEADER
case SMFIR_INSHEADER:
if (milter8_read_data(milter, data_size,
MILTER8_DATA_HLONG, &index,
MILTER8_DATA_STRING, milter->buf,
MILTER8_DATA_STRING, milter->body,
MILTER8_DATA_END) != 0)
MILTER8_EVENT_BREAK(milter->def_reply);
if ((ssize_t) index + 1 < 1) {
msg_warn("milter %s: bad insert header index: %ld",
milter->m.name, (long) index);
milter8_conf_error(milter);
MILTER8_EVENT_BREAK(milter->def_reply);
}
edit_resp = parent->ins_header(parent->chg_context,
(ssize_t) index + 1,
STR(milter->buf),
STR(milter->body));
if (edit_resp)
MILTER8_EVENT_BREAK(edit_resp);
continue;
#endif
case SMFIR_ADDRCPT:
if (milter8_read_data(milter, data_size,
MILTER8_DATA_STRING, milter->buf,
MILTER8_DATA_END) != 0)
MILTER8_EVENT_BREAK(milter->def_reply);
edit_resp = parent->add_rcpt(parent->chg_context,
STR(milter->buf));
if (edit_resp)
MILTER8_EVENT_BREAK(edit_resp);
continue;
case SMFIR_DELRCPT:
if (milter8_read_data(milter, data_size,
MILTER8_DATA_STRING, milter->buf,
MILTER8_DATA_END) != 0)
MILTER8_EVENT_BREAK(milter->def_reply);
edit_resp = parent->del_rcpt(parent->chg_context,
STR(milter->buf));
if (edit_resp)
MILTER8_EVENT_BREAK(edit_resp);
continue;
case SMFIR_REPLBODY:
if (milter8_read_data(milter, data_size,
MILTER8_DATA_BUFFER, milter->body,
MILTER8_DATA_END) != 0)
MILTER8_EVENT_BREAK(milter->def_reply);
if (body_line_buf == 0) {
body_line_buf = vstring_alloc(var_line_limit);
edit_resp = parent->repl_body(parent->chg_context,
MILTER_BODY_START,
(VSTRING *) 0);
}
for (cp = STR(milter->body); edit_resp == 0
&& cp < vstring_end(milter->body); cp++) {
ch = *(unsigned char *) cp;
if (ch == '\n') {
if (LEN(body_line_buf) > 0
&& vstring_end(body_line_buf)[-1] == '\r')
vstring_truncate(body_line_buf,
LEN(body_line_buf) - 1);
edit_resp = parent->repl_body(parent->chg_context,
MILTER_BODY_LINE,
body_line_buf);
VSTRING_RESET(body_line_buf);
} else {
VSTRING_ADDCH(body_line_buf, ch);
}
}
continue;
}
}
msg_warn("milter %s: unexpected filter response %s after event %s",
milter->m.name,
(smfir_name = str_name_code(smfir_table, cmd)) != 0 ?
smfir_name : "(unknown filter reply)",
(smfic_name = str_name_code(smfic_table, event)) != 0 ?
smfic_name : "(unknown MTA event)");
milter8_comm_error(milter);
MILTER8_EVENT_BREAK(milter->def_reply);
}
msg_warn("milter %s: reply %s was followed by %ld data bytes",
milter->m.name, (smfir_name = str_name_code(smfir_table, cmd)) != 0 ?
smfir_name : "unknown", (long) data_len);
milter8_comm_error(milter);
MILTER8_EVENT_BREAK(milter->def_reply);
}
if (body_line_buf != 0) {
if (edit_resp == 0) {
if (LEN(body_line_buf) > 0)
edit_resp = parent->repl_body(parent->chg_context,
MILTER_BODY_LINE,
body_line_buf);
if (edit_resp == 0)
edit_resp = parent->repl_body(parent->chg_context,
MILTER_BODY_END,
(VSTRING *) 0);
}
vstring_free(body_line_buf);
if (edit_resp && (retval == 0 || strchr("DS4", retval[0]) == 0))
retval = edit_resp;
}
return (retval);
}
static void milter8_connect(MILTER8 *milter)
{
const char *myname = "milter8_connect";
ssize_t data_len;
unsigned char cmd;
char *transport;
char *endpoint;
int (*connect_fn) (const char *, int, int);
int fd;
const UINT32_TYPE my_actions = (SMFIF_ADDHDRS | SMFIF_ADDRCPT
| SMFIF_DELRCPT | SMFIF_CHGHDRS
| SMFIF_CHGBODY
#ifdef SMFIF_QUARANTINE
| SMFIF_QUARANTINE
#endif
);
#ifdef USE_LIBMILTER_INCLUDES
const UINT32_TYPE my_version = SMFI_VERSION;
const UINT32_TYPE my_events = (SMFIP_NOCONNECT | SMFIP_NOHELO
| SMFIP_NOMAIL | SMFIP_NORCPT
| SMFIP_NOBODY | SMFIP_NOHDRS
| SMFIP_NOEOH
#ifdef SMFIP_NOHREPL
| SMFIP_NOHREPL
#endif
#ifdef SMFIP_NOUNKNOWN
| SMFIP_NOUNKNOWN
#endif
#ifdef SMFIP_NODATA
| SMFIP_NODATA
#endif
);
#else
UINT32_TYPE my_version = 0;
UINT32_TYPE my_events = 0;
char *saved_version;
char *cp;
char *name;
#endif
if (milter->fp != 0)
msg_panic("%s: milter %s: socket is not closed",
myname, milter->m.name);
#ifndef USE_LIBMILTER_INCLUDES
cp = saved_version = mystrdup(milter->protocol);
while ((name = mystrtok(&cp, " ,\t\r\n")) != 0) {
int mask;
int vers;
if ((mask = name_code(milter8_event_masks,
NAME_CODE_FLAG_NONE, name)) == -1
|| (vers = name_code(milter8_versions,
NAME_CODE_FLAG_NONE, name)) == -1
|| (vers != 0 && my_version != 0)) {
msg_warn("milter %s: bad protocol information: %s",
milter->m.name, name);
milter8_conf_error(milter);
return;
}
if (vers != 0)
my_version = vers;
my_events |= mask;
}
myfree(saved_version);
if (my_events == 0 || my_version == 0) {
msg_warn("milter %s: no protocol version information", milter->m.name);
milter8_conf_error(milter);
return;
}
milter->np_mask = ~(my_events | SMFIP_NOHREPL);
if (msg_verbose)
msg_info("%s: non-protocol events for protocol version %d: %s",
myname, my_version,
str_name_mask_opt(milter->buf, "non-protocol event mask",
smfip_table, milter->np_mask, NAME_MASK_NUMBER));
#endif
#define FREE_TRANSPORT_AND_BAIL_OUT(milter, milter_error) do { \
myfree(transport); \
milter_error(milter); \
return; \
} while (0);
transport = mystrdup(milter->m.name);
if ((endpoint = split_at(transport, ':')) == 0
|| *endpoint == 0 || *transport == 0) {
msg_warn("Milter service needs transport:endpoint instead of \"%s\"",
milter->m.name);
FREE_TRANSPORT_AND_BAIL_OUT(milter, milter8_conf_error);
}
if (msg_verbose)
msg_info("%s: transport=%s endpoint=%s", myname, transport, endpoint);
if (strcmp(transport, "inet") == 0) {
connect_fn = inet_connect;
} else if (strcmp(transport, "unix") == 0) {
connect_fn = unix_connect;
} else if (strcmp(transport, "local") == 0) {
connect_fn = LOCAL_CONNECT;
} else {
msg_warn("invalid transport name: %s in Milter service: %s",
transport, milter->m.name);
FREE_TRANSPORT_AND_BAIL_OUT(milter, milter8_conf_error);
}
if ((fd = connect_fn(endpoint, BLOCKING, milter->conn_timeout)) < 0) {
msg_warn("connect to Milter service %s: %m", milter->m.name);
FREE_TRANSPORT_AND_BAIL_OUT(milter, milter8_comm_error);
}
myfree(transport);
milter->fp = vstream_fdopen(fd, O_RDWR);
vstream_control(milter->fp,
VSTREAM_CTL_DOUBLE,
VSTREAM_CTL_TIMEOUT, milter->cmd_timeout,
VSTREAM_CTL_END);
if (msg_verbose) {
msg_info("%s: my_version=0x%lx", myname, (long) my_version);
msg_info("%s: my_actions=0x%lx %s", myname, (long) my_actions,
str_name_mask_opt(milter->buf, "request mask",
smfif_table, my_actions, NAME_MASK_NUMBER));
msg_info("%s: my_events=0x%lx %s", myname, (long) my_events,
str_name_mask_opt(milter->buf, "event mask",
smfip_table, my_events, NAME_MASK_NUMBER));
}
errno = 0;
if (milter8_write_cmd(milter, SMFIC_OPTNEG,
MILTER8_DATA_HLONG, my_version,
MILTER8_DATA_HLONG, my_actions,
MILTER8_DATA_HLONG, my_events,
MILTER8_DATA_END) != 0) {
msg_warn("milter %s: write error in initial handshake",
milter->m.name);
}
else if (milter8_read_resp(milter, SMFIC_OPTNEG, &cmd, &data_len) != 0) {
msg_warn("milter %s: read error in initial handshake", milter->m.name);
} else if (cmd != SMFIC_OPTNEG) {
msg_warn("milter %s: unexpected reply \"%c\" in initial handshake",
milter->m.name, cmd);
(void) milter8_comm_error(milter);
} else if (milter8_read_data(milter, data_len,
MILTER8_DATA_HLONG, &milter->version,
MILTER8_DATA_HLONG, &milter->rq_mask,
MILTER8_DATA_HLONG, &milter->ev_mask,
MILTER8_DATA_END) != 0) {
msg_warn("milter %s: read error in initial handshake", milter->m.name);
} else if (milter->version > my_version) {
msg_warn("milter %s: protocol version %d conflict"
" with MTA protocol version %d",
milter->m.name, milter->version, my_version);
(void) milter8_comm_error(milter);
} else if ((milter->rq_mask & my_actions) != milter->rq_mask) {
msg_warn("milter %s: request mask 0x%x conflict"
" with MTA request mask 0x%lx",
milter->m.name, milter->rq_mask, (long) my_actions);
(void) milter8_comm_error(milter);
}
else {
if (msg_verbose) {
if ((milter->ev_mask & my_events) != milter->ev_mask)
msg_info("milter %s: event mask 0x%x includes features not"
" offered in MTA event mask 0x%lx",
milter->m.name, milter->ev_mask, (long) my_events);
msg_info("%s: milter %s version %d",
myname, milter->m.name, milter->version);
msg_info("%s: events %s", myname,
str_name_mask_opt(milter->buf, "event mask",
smfip_table, milter->ev_mask, NAME_MASK_NUMBER));
msg_info("%s: requests %s", myname,
str_name_mask_opt(milter->buf, "request mask",
smfif_table, milter->rq_mask, NAME_MASK_NUMBER));
}
milter->state = MILTER8_STAT_READY;
milter8_def_reply(milter, 0);
}
}
static const char *milter8_conn_event(MILTER *m,
const char *client_name,
const char *client_addr,
const char *client_port,
unsigned addr_family,
ARGV *macros)
{
const char *myname = "milter8_conn_event";
MILTER8 *milter = (MILTER8 *) m;
int port;
#ifdef LIBMILTER_AUTO_DISCONNECT
milter8_connect(milter);
#endif
switch (milter->state) {
case MILTER8_STAT_ERROR:
if (msg_verbose)
msg_info("%s: skip milter %s", myname, milter->m.name);
return (milter->def_reply);
case MILTER8_STAT_READY:
if (msg_verbose)
msg_info("%s: milter %s: connect %s/%s",
myname, milter->m.name, client_name, client_addr);
if (client_port == 0) {
port = 0;
} else if (!alldig(client_port) || (port = atoi(client_port)) < 0
|| port > 65535) {
msg_warn("milter %s: bad client port number %s",
milter->m.name, client_port);
port = 0;
}
milter->state = MILTER8_STAT_ENVELOPE;
switch (addr_family) {
case AF_INET:
return (milter8_event(milter, SMFIC_CONNECT, SMFIP_NOCONNECT,
DONT_SKIP_REPLY, macros,
MILTER8_DATA_STRING, client_name,
MILTER8_DATA_OCTET, SMFIA_INET,
MILTER8_DATA_NSHORT, htons(port),
MILTER8_DATA_STRING, client_addr,
MILTER8_DATA_END));
#ifdef HAS_IPV6
case AF_INET6:
return (milter8_event(milter, SMFIC_CONNECT, SMFIP_NOCONNECT,
DONT_SKIP_REPLY, macros,
MILTER8_DATA_STRING, client_name,
MILTER8_DATA_OCTET, SMFIA_INET6,
MILTER8_DATA_NSHORT, htons(port),
MILTER8_DATA_STRING, client_addr,
MILTER8_DATA_END));
#endif
case AF_UNIX:
return (milter8_event(milter, SMFIC_CONNECT, SMFIP_NOCONNECT,
DONT_SKIP_REPLY, macros,
MILTER8_DATA_STRING, client_name,
MILTER8_DATA_OCTET, SMFIA_UNIX,
MILTER8_DATA_NSHORT, htons(0),
MILTER8_DATA_STRING, client_addr,
MILTER8_DATA_END));
default:
return (milter8_event(milter, SMFIC_CONNECT, SMFIP_NOCONNECT,
DONT_SKIP_REPLY, macros,
MILTER8_DATA_STRING, client_name,
MILTER8_DATA_OCTET, SMFIA_UNKNOWN,
MILTER8_DATA_END));
}
default:
msg_panic("%s: milter %s: bad state %d",
myname, milter->m.name, milter->state);
}
}
static const char *milter8_helo_event(MILTER *m, const char *helo_name,
int unused_esmtp,
ARGV *macros)
{
const char *myname = "milter8_helo_event";
MILTER8 *milter = (MILTER8 *) m;
switch (milter->state) {
case MILTER8_STAT_ERROR:
case MILTER8_STAT_ACCEPT_CON:
case MILTER8_STAT_REJECT_CON:
if (msg_verbose)
msg_info("%s: skip milter %s", myname, milter->m.name);
return (milter->def_reply);
case MILTER8_STAT_ENVELOPE:
case MILTER8_STAT_ACCEPT_MSG:
if (msg_verbose)
msg_info("%s: milter %s: helo %s",
myname, milter->m.name, helo_name);
return (milter8_event(milter, SMFIC_HELO, SMFIP_NOHELO,
DONT_SKIP_REPLY, macros,
MILTER8_DATA_STRING, helo_name,
MILTER8_DATA_END));
default:
msg_panic("%s: milter %s: bad state %d",
myname, milter->m.name, milter->state);
}
}
static const char *milter8_mail_event(MILTER *m, const char **argv,
ARGV *macros)
{
const char *myname = "milter8_mail_event";
MILTER8 *milter = (MILTER8 *) m;
const char **cpp;
switch (milter->state) {
case MILTER8_STAT_ERROR:
case MILTER8_STAT_ACCEPT_CON:
case MILTER8_STAT_REJECT_CON:
if (msg_verbose)
msg_info("%s: skip milter %s", myname, milter->m.name);
return (milter->def_reply);
case MILTER8_STAT_ENVELOPE:
if (msg_verbose) {
VSTRING *buf = vstring_alloc(100);
for (cpp = argv; *cpp; cpp++)
vstring_sprintf_append(buf, " %s", *cpp);
msg_info("%s: milter %s: mail%s",
myname, milter->m.name, STR(buf));
vstring_free(buf);
}
return (milter8_event(milter, SMFIC_MAIL, SMFIP_NOMAIL,
DONT_SKIP_REPLY, macros,
MILTER8_DATA_ARGV, argv,
MILTER8_DATA_END));
default:
msg_panic("%s: milter %s: bad state %d",
myname, milter->m.name, milter->state);
}
}
static const char *milter8_rcpt_event(MILTER *m, const char **argv,
ARGV *macros)
{
const char *myname = "milter8_rcpt_event";
MILTER8 *milter = (MILTER8 *) m;
const char **cpp;
switch (milter->state) {
case MILTER8_STAT_ERROR:
case MILTER8_STAT_ACCEPT_CON:
case MILTER8_STAT_REJECT_CON:
case MILTER8_STAT_ACCEPT_MSG:
if (msg_verbose)
msg_info("%s: skip milter %s", myname, milter->m.name);
return (milter->def_reply);
case MILTER8_STAT_ENVELOPE:
if (msg_verbose) {
VSTRING *buf = vstring_alloc(100);
for (cpp = argv; *cpp; cpp++)
vstring_sprintf_append(buf, " %s", *cpp);
msg_info("%s: milter %s: rcpt%s",
myname, milter->m.name, STR(buf));
vstring_free(buf);
}
return (milter8_event(milter, SMFIC_RCPT, SMFIP_NORCPT,
DONT_SKIP_REPLY, macros,
MILTER8_DATA_ARGV, argv,
MILTER8_DATA_END));
default:
msg_panic("%s: milter %s: bad state %d",
myname, milter->m.name, milter->state);
}
}
#ifdef SMFIC_DATA
static const char *milter8_data_event(MILTER *m, ARGV *macros)
{
const char *myname = "milter8_data_event";
MILTER8 *milter = (MILTER8 *) m;
switch (milter->state) {
case MILTER8_STAT_ERROR:
case MILTER8_STAT_ACCEPT_CON:
case MILTER8_STAT_REJECT_CON:
case MILTER8_STAT_ACCEPT_MSG:
if (msg_verbose)
msg_info("%s: skip milter %s", myname, milter->m.name);
return (milter->def_reply);
case MILTER8_STAT_ENVELOPE:
if (msg_verbose)
msg_info("%s: milter %s: data command", myname, milter->m.name);
return (milter8_event(milter, SMFIC_DATA, SMFIP_NODATA,
DONT_SKIP_REPLY, macros,
MILTER8_DATA_END));
default:
msg_panic("%s: milter %s: bad state %d",
myname, milter->m.name, milter->state);
}
}
#else
#define milter8_data_event 0
#endif
#ifdef SMFIC_UNKNOWN
static const char *milter8_unknown_event(MILTER *m, const char *command,
ARGV *macros)
{
const char *myname = "milter8_unknown_event";
MILTER8 *milter = (MILTER8 *) m;
switch (milter->state) {
case MILTER8_STAT_ERROR:
case MILTER8_STAT_ACCEPT_CON:
case MILTER8_STAT_REJECT_CON:
case MILTER8_STAT_ACCEPT_MSG:
if (msg_verbose)
msg_info("%s: skip milter %s", myname, milter->m.name);
return (milter->def_reply);
case MILTER8_STAT_ENVELOPE:
if (msg_verbose)
msg_info("%s: milter %s: unknown command: %s",
myname, milter->m.name, command);
return (milter8_event(milter, SMFIC_UNKNOWN, SMFIP_NOUNKNOWN,
DONT_SKIP_REPLY, macros,
MILTER8_DATA_STRING, command,
MILTER8_DATA_END));
default:
msg_panic("%s: milter %s: bad state %d",
myname, milter->m.name, milter->state);
}
}
#else
#define milter8_unknown_event 0
#endif
static const char *milter8_other_event(MILTER *m)
{
const char *myname = "milter8_other_event";
MILTER8 *milter = (MILTER8 *) m;
if (msg_verbose)
msg_info("%s: milter %s", myname, milter->m.name);
return (milter->def_reply);
}
static void milter8_abort(MILTER *m)
{
const char *myname = "milter8_abort";
MILTER8 *milter = (MILTER8 *) m;
switch (milter->state) {
case MILTER8_STAT_CLOSED:
case MILTER8_STAT_READY:
return;
case MILTER8_STAT_ERROR:
case MILTER8_STAT_ACCEPT_CON:
case MILTER8_STAT_REJECT_CON:
if (msg_verbose)
msg_info("%s: skip milter %s", myname, milter->m.name);
break;
case MILTER8_STAT_ENVELOPE:
case MILTER8_STAT_MESSAGE:
case MILTER8_STAT_ACCEPT_MSG:
if (msg_verbose)
msg_info("%s: abort milter %s", myname, milter->m.name);
(void) milter8_write_cmd(milter, SMFIC_ABORT, MILTER8_DATA_END);
if (milter->state != MILTER8_STAT_ERROR)
milter->state = MILTER8_STAT_ENVELOPE;
break;
default:
msg_panic("%s: milter %s: bad state %d",
myname, milter->m.name, milter->state);
}
}
static void milter8_disc_event(MILTER *m)
{
const char *myname = "milter8_disc_event";
MILTER8 *milter = (MILTER8 *) m;
switch (milter->state) {
case MILTER8_STAT_CLOSED:
case MILTER8_STAT_READY:
return;
case MILTER8_STAT_ERROR:
#ifdef LIBMILTER_AUTO_DISCONNECT
case MILTER8_STAT_ACCEPT_CON:
case MILTER8_STAT_REJECT_CON:
#endif
if (msg_verbose)
msg_info("%s: skip quit milter %s", myname, milter->m.name);
break;
case MILTER8_STAT_ENVELOPE:
case MILTER8_STAT_MESSAGE:
#ifndef LIBMILTER_AUTO_DISCONNECT
case MILTER8_STAT_ACCEPT_CON:
case MILTER8_STAT_REJECT_CON:
#endif
case MILTER8_STAT_ACCEPT_MSG:
if (msg_verbose)
msg_info("%s: quit milter %s", myname, milter->m.name);
(void) milter8_write_cmd(milter, SMFIC_QUIT, MILTER8_DATA_END);
break;
}
#ifdef LIBMILTER_AUTO_DISCONNECT
milter8_close_stream(milter);
#else
if (milter->state != MILTER8_STAT_ERROR)
milter->state = MILTER8_STAT_READY;
#endif
milter8_def_reply(milter, 0);
}
typedef struct {
MILTER8 *milter;
ARGV *macros;
int first_header;
int first_body;
const char *resp;
} MILTER_MSG_CONTEXT;
static void milter8_header(void *ptr, int unused_header_class,
HEADER_OPTS *header_info,
VSTRING *buf, off_t unused_offset)
{
const char *myname = "milter8_header";
MILTER_MSG_CONTEXT *msg_ctx = (MILTER_MSG_CONTEXT *) ptr;
MILTER8 *milter = msg_ctx->milter;
char *cp;
int skip_reply;
#define MILTER8_MESSAGE_DONE(milter, msg_ctx) \
((milter)->state != MILTER8_STAT_MESSAGE || (msg_ctx)->resp != 0)
if (MILTER8_MESSAGE_DONE(milter, msg_ctx))
return;
if (msg_ctx->first_header) {
msg_ctx->first_header = 0;
return;
}
if (msg_verbose > 1)
msg_info("%s: header milter %s: %.100s",
myname, milter->m.name, STR(buf));
cp = STR(buf) + (header_info ? strlen(header_info->name) :
is_header(STR(buf)));
while (*cp == ' ' || *cp == '\t')
*cp++ = 0;
if (*cp != ':')
msg_panic("%s: header label not followed by ':'", myname);
*cp++ = 0;
if (*cp == ' ')
cp++;
#ifdef SMFIP_NOHREPL
skip_reply = ((milter->ev_mask & SMFIP_NOHREPL) != 0);
#else
skip_reply = DONT_SKIP_REPLY;
#endif
msg_ctx->resp =
milter8_event(milter, SMFIC_HEADER, SMFIP_NOHDRS,
skip_reply, msg_ctx->macros,
MILTER8_DATA_STRING, STR(buf),
MILTER8_DATA_STRING, cp,
MILTER8_DATA_END);
}
static void milter8_eoh(void *ptr)
{
const char *myname = "milter8_eoh";
MILTER_MSG_CONTEXT *msg_ctx = (MILTER_MSG_CONTEXT *) ptr;
MILTER8 *milter = msg_ctx->milter;
if (MILTER8_MESSAGE_DONE(milter, msg_ctx))
return;
if (msg_verbose)
msg_info("%s: eoh milter %s", myname, milter->m.name);
msg_ctx->resp =
milter8_event(milter, SMFIC_EOH, SMFIP_NOEOH,
DONT_SKIP_REPLY, msg_ctx->macros,
MILTER8_DATA_END);
}
static void milter8_body(void *ptr, int rec_type,
const char *buf, ssize_t len,
off_t offset)
{
const char *myname = "milter8_body";
MILTER_MSG_CONTEXT *msg_ctx = (MILTER_MSG_CONTEXT *) ptr;
MILTER8 *milter = msg_ctx->milter;
ssize_t todo = len;
const char *bp = buf;
ssize_t space;
ssize_t count;
if (MILTER8_MESSAGE_DONE(milter, msg_ctx))
return;
if (msg_ctx->first_body) {
msg_ctx->first_body = 0;
return;
}
if (msg_verbose > 1)
msg_info("%s: body milter %s: %.100s", myname, milter->m.name, buf);
if (rec_type == REC_TYPE_NORM && todo == 0) {
bp = "\r\n";
todo = 2;
rec_type = REC_TYPE_EOF;
}
while (todo > 0) {
space = MILTER_CHUNK_SIZE - LEN(milter->body);
if (space <= 0)
msg_panic("%s: bad buffer size: %ld",
myname, (long) LEN(milter->body));
count = (todo > space ? space : todo);
vstring_memcat(milter->body, bp, count);
bp += count;
todo -= count;
if (LEN(milter->body) == MILTER_CHUNK_SIZE) {
msg_ctx->resp =
milter8_event(milter, SMFIC_BODY, SMFIP_NOBODY,
DONT_SKIP_REPLY, msg_ctx->macros,
MILTER8_DATA_BUFFER, milter->body,
MILTER8_DATA_END);
if (MILTER8_MESSAGE_DONE(milter, msg_ctx))
break;
VSTRING_RESET(milter->body);
}
if (rec_type == REC_TYPE_NORM && todo == 0) {
bp = "\r\n";
todo = 2;
rec_type = REC_TYPE_EOF;
}
}
}
static void milter8_eob(void *ptr)
{
const char *myname = "milter8_eob";
MILTER_MSG_CONTEXT *msg_ctx = (MILTER_MSG_CONTEXT *) ptr;
MILTER8 *milter = msg_ctx->milter;
if (MILTER8_MESSAGE_DONE(milter, msg_ctx))
return;
if (msg_verbose)
msg_info("%s: eob milter %s", myname, milter->m.name);
if (LEN(milter->body) > 0) {
msg_ctx->resp =
milter8_event(milter, SMFIC_BODY, SMFIP_NOBODY,
DONT_SKIP_REPLY, msg_ctx->macros,
MILTER8_DATA_BUFFER, milter->body,
MILTER8_DATA_END);
if (MILTER8_MESSAGE_DONE(milter, msg_ctx))
return;
}
msg_ctx->resp =
milter8_event(msg_ctx->milter, SMFIC_BODYEOB, 0,
DONT_SKIP_REPLY, msg_ctx->macros,
MILTER8_DATA_END);
}
static const char *milter8_message(MILTER *m, VSTREAM *qfile,
off_t data_offset,
ARGV *macros)
{
const char *myname = "milter8_message";
MILTER8 *milter = (MILTER8 *) m;
MIME_STATE *mime_state;
int rec_type;
MIME_STATE_DETAIL *detail;
int mime_errs = 0;
MILTER_MSG_CONTEXT msg_ctx;
VSTRING *buf;
switch (milter->state) {
case MILTER8_STAT_ERROR:
case MILTER8_STAT_ACCEPT_CON:
case MILTER8_STAT_REJECT_CON:
case MILTER8_STAT_ACCEPT_MSG:
if (msg_verbose)
msg_info("%s: skip message to milter %s", myname, milter->m.name);
return (milter->def_reply);
case MILTER8_STAT_ENVELOPE:
if (msg_verbose)
msg_info("%s: message to milter %s", myname, milter->m.name);
if (vstream_fseek(qfile, data_offset, SEEK_SET) < 0) {
msg_warn("%s: vstream_fseek %s: %m", myname, VSTREAM_PATH(qfile));
return ("450 4.3.0 Queue file write error");
}
msg_ctx.milter = milter;
msg_ctx.macros = macros;
msg_ctx.first_header = 1;
msg_ctx.first_body = 1;
msg_ctx.resp = 0;
mime_state =
mime_state_alloc(MIME_OPT_DISABLE_MIME,
(milter->ev_mask & SMFIP_NOHDRS) ?
(MIME_STATE_HEAD_OUT) 0 : milter8_header,
(milter->ev_mask & SMFIP_NOEOH) ?
(MIME_STATE_ANY_END) 0 : milter8_eoh,
(milter->ev_mask & SMFIP_NOBODY) ?
(MIME_STATE_BODY_OUT) 0 : milter8_body,
milter8_eob,
(MIME_STATE_ERR_PRINT) 0,
(void *) &msg_ctx);
buf = vstring_alloc(100);
milter->state = MILTER8_STAT_MESSAGE;
VSTRING_RESET(milter->body);
vstream_control(milter->fp,
VSTREAM_CTL_DOUBLE,
VSTREAM_CTL_TIMEOUT, milter->msg_timeout,
VSTREAM_CTL_END);
for (;;) {
if ((rec_type = rec_get(qfile, buf, 0)) < 0) {
msg_warn("%s: error reading %s: %m",
myname, VSTREAM_PATH(qfile));
msg_ctx.resp = "450 4.3.0 Queue file write error";
break;
}
mime_errs = mime_state_update(mime_state, rec_type,
STR(buf), LEN(buf));
if (mime_errs) {
detail = mime_state_detail(mime_errs);
msg_warn("%s: MIME problem %s in %s",
myname, detail->text, VSTREAM_PATH(qfile));
msg_ctx.resp = "450 4.3.0 Queue file write error";
break;
}
if (MILTER8_MESSAGE_DONE(milter, &msg_ctx))
break;
if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT)
break;
}
mime_state_free(mime_state);
vstring_free(buf);
if (milter->fp)
vstream_control(milter->fp,
VSTREAM_CTL_DOUBLE,
VSTREAM_CTL_TIMEOUT, milter->cmd_timeout,
VSTREAM_CTL_END);
if (milter->state == MILTER8_STAT_MESSAGE
|| milter->state == MILTER8_STAT_ACCEPT_MSG)
milter->state = MILTER8_STAT_ENVELOPE;
return (msg_ctx.resp);
default:
msg_panic("%s: milter %s: bad state %d",
myname, milter->m.name, milter->state);
}
}
#define MAIL_ATTR_MILT_NAME "milter_name"
#define MAIL_ATTR_MILT_VERS "milter_version"
#define MAIL_ATTR_MILT_ACTS "milter_actions"
#define MAIL_ATTR_MILT_EVTS "milter_events"
#define MAIL_ATTR_MILT_NPTS "milter_non_events"
#define MAIL_ATTR_MILT_STAT "milter_state"
#define MAIL_ATTR_MILT_CONN "milter_conn_timeout"
#define MAIL_ATTR_MILT_CMD "milter_cmd_timeout"
#define MAIL_ATTR_MILT_MSG "milter_msg_timeout"
#define MAIL_ATTR_MILT_ACT "milter_action"
static int milter8_active(MILTER *m)
{
MILTER8 *milter = (MILTER8 *) m;
return (milter->fp != 0
&& (milter->state == MILTER8_STAT_ENVELOPE
|| milter->state == MILTER8_STAT_READY));
}
static int milter8_send(MILTER *m, VSTREAM *stream)
{
const char *myname = "milter8_send";
MILTER8 *milter = (MILTER8 *) m;
if (msg_verbose)
msg_info("%s: milter %s", myname, milter->m.name);
if (attr_print(stream, ATTR_FLAG_NONE,
ATTR_TYPE_STR, MAIL_ATTR_MILT_NAME, milter->m.name,
ATTR_TYPE_INT, MAIL_ATTR_MILT_VERS, milter->version,
ATTR_TYPE_INT, MAIL_ATTR_MILT_ACTS, milter->rq_mask,
ATTR_TYPE_INT, MAIL_ATTR_MILT_EVTS, milter->ev_mask,
#ifndef USE_LIBMILTER_INCLUDES
ATTR_TYPE_INT, MAIL_ATTR_MILT_NPTS, milter->np_mask,
#endif
ATTR_TYPE_INT, MAIL_ATTR_MILT_STAT, milter->state,
ATTR_TYPE_INT, MAIL_ATTR_MILT_CONN, milter->conn_timeout,
ATTR_TYPE_INT, MAIL_ATTR_MILT_CMD, milter->cmd_timeout,
ATTR_TYPE_INT, MAIL_ATTR_MILT_MSG, milter->msg_timeout,
ATTR_TYPE_STR, MAIL_ATTR_MILT_ACT, milter->def_action,
ATTR_TYPE_END) != 0
|| vstream_fflush(stream) != 0) {
return (-1);
#ifdef CANT_WRITE_BEFORE_SENDING_FD
} else if (attr_scan(stream, ATTR_FLAG_STRICT,
ATTR_TYPE_STR, MAIL_ATTR_DUMMY, milter->buf,
ATTR_TYPE_END) != 1) {
return (-1);
#endif
} else if (LOCAL_SEND_FD(vstream_fileno(stream),
vstream_fileno(milter->fp)) < 0) {
return (-1);
#ifdef MUST_READ_AFTER_SENDING_FD
} else if (attr_scan(stream, ATTR_FLAG_STRICT,
ATTR_TYPE_STR, MAIL_ATTR_DUMMY, milter->buf,
ATTR_TYPE_END) != 1) {
return (-1);
#endif
} else {
return (0);
}
}
static MILTER8 *milter8_alloc(const char *, int, int, int, const char *,
const char *, MILTERS *);
MILTER *milter8_receive(VSTREAM *stream, MILTERS *parent)
{
const char *myname = "milter8_receive";
static VSTRING *name_buf;
static VSTRING *act_buf;
MILTER8 *milter;
int version;
int rq_mask;
int ev_mask;
int np_mask;
int state;
int conn_timeout;
int cmd_timeout;
int msg_timeout;
int fd;
if (name_buf == 0) {
name_buf = vstring_alloc(10);
act_buf = vstring_alloc(10);
}
if (attr_scan(stream, ATTR_FLAG_STRICT,
ATTR_TYPE_STR, MAIL_ATTR_MILT_NAME, name_buf,
ATTR_TYPE_INT, MAIL_ATTR_MILT_VERS, &version,
ATTR_TYPE_INT, MAIL_ATTR_MILT_ACTS, &rq_mask,
ATTR_TYPE_INT, MAIL_ATTR_MILT_EVTS, &ev_mask,
#ifndef USE_LIBMILTER_INCLUDES
ATTR_TYPE_INT, MAIL_ATTR_MILT_NPTS, &np_mask,
#endif
ATTR_TYPE_INT, MAIL_ATTR_MILT_STAT, &state,
ATTR_TYPE_INT, MAIL_ATTR_MILT_CONN, &conn_timeout,
ATTR_TYPE_INT, MAIL_ATTR_MILT_CMD, &cmd_timeout,
ATTR_TYPE_INT, MAIL_ATTR_MILT_MSG, &msg_timeout,
ATTR_TYPE_STR, MAIL_ATTR_MILT_ACT, act_buf,
ATTR_TYPE_END) < 9) {
return (0);
#ifdef CANT_WRITE_BEFORE_SENDING_FD
} else if (attr_print(stream, ATTR_FLAG_NONE,
ATTR_TYPE_STR, MAIL_ATTR_DUMMY, "",
ATTR_TYPE_END) != 0
|| vstream_fflush(stream) != 0) {
return (0);
#endif
} else if ((fd = LOCAL_RECV_FD(vstream_fileno(stream))) < 0) {
return (0);
#ifdef MUST_READ_AFTER_SENDING_FD
} else if (attr_print(stream, ATTR_FLAG_NONE,
ATTR_TYPE_STR, MAIL_ATTR_DUMMY, "",
ATTR_TYPE_END) != 0) {
return (0);
#endif
} else {
#define NO_PROTOCOL ((char *) 0)
if (msg_verbose)
msg_info("%s: milter %s", myname, STR(name_buf));
milter = milter8_alloc(STR(name_buf), conn_timeout, cmd_timeout,
msg_timeout, NO_PROTOCOL, STR(act_buf), parent);
milter->fp = vstream_fdopen(fd, O_RDWR);
vstream_control(milter->fp, VSTREAM_CTL_DOUBLE, VSTREAM_CTL_END);
milter->version = version;
milter->rq_mask = rq_mask;
milter->ev_mask = ev_mask;
#ifndef USE_LIBMILTER_INCLUDES
milter->np_mask = np_mask;
#endif
milter->state = state;
return (&milter->m);
}
}
static void milter8_free(MILTER *m)
{
MILTER8 *milter = (MILTER8 *) m;
if (msg_verbose)
msg_info("free milter %s", milter->m.name);
if (milter->fp)
(void) vstream_fclose(milter->fp);
myfree(milter->m.name);
vstring_free(milter->buf);
vstring_free(milter->body);
if (milter->protocol)
myfree(milter->protocol);
myfree(milter->def_action);
if (milter->def_reply)
myfree(milter->def_reply);
myfree((char *) milter);
}
static MILTER8 *milter8_alloc(const char *name, int conn_timeout,
int cmd_timeout, int msg_timeout,
const char *protocol,
const char *def_action,
MILTERS *parent)
{
MILTER8 *milter;
milter = (MILTER8 *) mymalloc(sizeof(*milter));
milter->m.name = mystrdup(name);
milter->m.next = 0;
milter->m.parent = parent;
milter->m.conn_event = milter8_conn_event;
milter->m.helo_event = milter8_helo_event;
milter->m.mail_event = milter8_mail_event;
milter->m.rcpt_event = milter8_rcpt_event;
milter->m.data_event = milter8_data_event;
milter->m.message = milter8_message;
milter->m.unknown_event = milter8_unknown_event;
milter->m.other_event = milter8_other_event;
milter->m.abort = milter8_abort;
milter->m.disc_event = milter8_disc_event;
milter->m.active = milter8_active;
milter->m.send = milter8_send;
milter->m.free = milter8_free;
milter->fp = 0;
milter->buf = vstring_alloc(100);
milter->body = vstring_alloc(100);
milter->version = 0;
milter->rq_mask = 0;
milter->ev_mask = 0;
milter->state = MILTER8_STAT_CLOSED;
milter->conn_timeout = conn_timeout;
milter->cmd_timeout = cmd_timeout;
milter->msg_timeout = msg_timeout;
milter->protocol = (protocol ? mystrdup(protocol) : 0);
milter->def_action = mystrdup(def_action);
milter->def_reply = 0;
return (milter);
}
MILTER *milter8_create(const char *name, int conn_timeout, int cmd_timeout,
int msg_timeout, const char *protocol,
const char *def_action, MILTERS *parent)
{
MILTER8 *milter;
milter = milter8_alloc(name, conn_timeout, cmd_timeout, msg_timeout,
protocol, def_action, parent);
#ifndef LIBMILTER_AUTO_DISCONNECT
milter8_connect(milter);
#endif
return (&milter->m);
}