/* This file is part of gacopyz.
Copyright (C) 2006-2020 Sergey Poznyakoff
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see . */
#include
#define TRACE(ctx,cmd,size,buf) do { \
if (GACOPYZ_CTX_LOG_MATCH(ctx, SMI_LOG_DEBUG)) { \
gacopyz_log(SMI_LOG_DEBUG, _("send header: size=%lu, cmd=%c"),\
size, cmd); \
if (GACOPYZ_CTX_LOG_MATCH(ctx, SMI_LOG_PROTO)) \
gacopyz_logdump(SMI_LOG_PROTO, \
_("send data"), buf, size); \
else \
gacopyz_log(SMI_LOG_DEBUG, _("send data")); \
} \
} while (0)
int
gacopyz_init(gacopyz_conn_t *pconn, struct smfiDesc *desc)
{
size_t len;
char *name;
gacopyz_conn_t conn;
name = desc->xxfi_name ? desc->xxfi_name : "Unknown";
len = strlen(name);
if (desc->xxfi_version != SMFI_VERSION) {
gacopyz_log(SMI_LOG_ERR,
_("smfi_register: %s: version mismatch; "
"application %d != implementation %d"),
name, desc->xxfi_version, SMFI_VERSION);
return MI_FAILURE;
}
conn = malloc(sizeof(*conn) + len + 1);
if (!conn) {
gacopyz_log(SMI_LOG_ERR,
"smfi_register: %s: %s", name,
strerror(errno));
return MI_FAILURE;
}
memset(conn, 0, sizeof(*conn));
conn->sd = -1;
conn->master_timeout.tv_usec = 0;
conn->master_timeout.tv_sec = 5;
conn->desc.ctx_timeout.tv_usec = 0;
conn->desc.ctx_timeout.tv_sec = GACOPYZ_TIMEOUT;
if (!conn->desc.logmask)
conn->desc.logmask = SMI_DEFAULT_LOG_MASK;
conn->desc = *desc;
conn->desc.xxfi_name = (char*)(conn + 1);
strcpy(conn->desc.xxfi_name, name);
*pconn = conn;
return MI_SUCCESS;
}
void
gacopyz_free(gacopyz_conn_t conn)
{
if (conn) {
free(conn->pidtab);
free(conn);
}
}
static int
copy_part(const char *cstr, const char *p, char **pbuf)
{
size_t len = p - cstr;
char *buf = malloc(len + 1);
if (!buf) {
free(buf);
return MI_FAILURE;
}
memcpy(buf, cstr, len);
buf[len] = 0;
*pbuf = buf;
return 0;
}
static int
parse_url(const char *proto, const char *cstr, char **pport, char **ppath)
{
const char *p;
if (cstr[0] == '[') {
p = strchr(cstr + 1, ']');
if (p) {
if (copy_part(cstr + 1, p, ppath))
return MI_FAILURE;
if (*++p == ':') {
*pport = strdup(p + 1);
return *pport == NULL ? MI_FAILURE : 0;
} else if (p == NULL) {
*pport = NULL;
return 0;
}
free(*ppath);
/* Retry parsing as a usual address:port */
}
}
p = strchr(cstr, ':');
if (!p) {
*pport = NULL;
*ppath = strdup(cstr);
return *ppath == NULL ? MI_FAILURE : 0;
} else if (copy_part(cstr, p, ppath))
return MI_FAILURE;
else
cstr = p + 1;
*pport = strdup(cstr);
return *pport == NULL ? MI_FAILURE : 0;
}
int
gacopyz_parse_connection(const char *cstr,
char **pproto, char **pport, char **ppath)
{
const char *p;
size_t len;
p = strchr(cstr, ':');
if (!p)
*pproto = NULL;
else {
char *proto;
if (copy_part(cstr, p, &proto))
return MI_FAILURE;
cstr = p + 1;
if (cstr[0] == '/' && cstr[1] == '/') {
int rc = parse_url(proto, cstr + 2, pport, ppath);
if (rc)
free(proto);
else
*pproto = proto;
return rc;
}
*pproto = proto;
}
p = strchr(cstr, '@');
if (!p)
*pport = NULL;
else if (copy_part(cstr, p, pport))
return MI_FAILURE;
else
cstr = p + 1;
len = strlen (cstr);
if (cstr[0] == '[' && cstr[len-1] == ']') {
if (copy_part(cstr + 1, cstr + len - 1, ppath)) {
free(*pproto);
free(*pport);
return MI_FAILURE;
}
} else
*ppath = strdup(cstr);
if (!*ppath) {
free(*pproto);
free(*pport);
free(*ppath);
return MI_FAILURE;
}
return MI_SUCCESS;
}
static int
parse_connection(gacopyz_conn_t conn,
const char *cstr,
char **pproto, char **pport, char **ppath)
{
int rc = gacopyz_parse_connection(cstr, pproto, pport, ppath);
if (rc && GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
"parse_connection: %s", strerror(ENOMEM));
return rc;
}
static void
cleanup_unix_socket(gacopyz_conn_t conn, void *data)
{
if (unlink(data) && errno != ENOENT)
if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: %s: cannot unlink: %s"),
conn->desc.xxfi_name, (char*)data,
strerror(errno));
free(data);
}
static int
do_connect(gacopyz_conn_t conn,
const char *cstr, char *proto, char *port, char *path,
int backlog, int rmsocket)
{
milter_sockaddr_t addr;
int socklen;
int fd, flags;
int yes = 1, rc;
mode_t old_mask;
if (!proto
|| strcmp(proto, "unix") == 0 || strcmp(proto, "local") == 0) {
struct stat st;
if (port) {
if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: invalid connection type: %s; "
"port is meaningless for UNIX sockets"),
conn->desc.xxfi_name, cstr);
return -1;
}
if (strlen(path) > sizeof addr.sunix.sun_path) {
errno = EINVAL;
if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: %s: UNIX socket name too long"),
conn->desc.xxfi_name, path);
return -1;
}
addr.sa.sa_family = PF_UNIX;
socklen = sizeof(addr.sunix);
strcpy(addr.sunix.sun_path, path);
if (stat(path, &st)) {
if (errno == ENOENT) {
conn->cleanup = cleanup_unix_socket;
conn->cleanup_data = strdup(path);
} else {
if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: %s: cannot stat socket: %s"),
conn->desc.xxfi_name, path,
strerror(errno));
return -1;
}
} else {
/* FIXME: Check permissions? */
if (!S_ISSOCK(st.st_mode)) {
if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: %s: not a socket"),
conn->desc.xxfi_name,
path);
return -1;
}
if (rmsocket && unlink(path)) {
if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: %s: cannot unlink: %s"),
conn->desc.xxfi_name, path,
strerror(errno));
return -1;
}
}
} else if (strcmp(proto, "inet") == 0) {
short pnum;
long num;
char *p;
addr.sa.sa_family = PF_INET;
socklen = sizeof(addr.sin);
if (!port) {
if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: invalid connection type: %s; "
"missing port number"),
conn->desc.xxfi_name, cstr);
return -1;
}
num = pnum = strtol(port, &p, 0);
if (*p == 0) {
if (num != pnum) {
if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: invalid connection type: "
"%s; bad port number"),
conn->desc.xxfi_name,
cstr);
return -1;
}
pnum = htons(pnum);
} else {
struct servent *sp = getservbyname(port, "tcp");
if (!sp) {
if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: invalid connection type: "
"%s; unknown port name"),
conn->desc.xxfi_name,
cstr);
return -1;
}
pnum = sp->s_port;
}
if (!path)
addr.sin.sin_addr.s_addr = INADDR_ANY;
else {
struct hostent *hp = gethostbyname(path);
if (!hp) {
if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: unknown host name %s"),
conn->desc.xxfi_name,
path);
return -1;
}
addr.sa.sa_family = hp->h_addrtype;
switch (hp->h_addrtype) {
case AF_INET:
memmove(&addr.sin.sin_addr, hp->h_addr, 4);
addr.sin.sin_port = pnum;
break;
default:
if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: invalid connection type: "
"%s; unsupported address family"),
conn->desc.xxfi_name,
cstr);
return -1;
}
}
#ifdef GACOPYZ_IPV6
} else if (strcmp(proto, "inet6") == 0) {
struct addrinfo hints;
struct addrinfo *res;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET6;
hints.ai_socktype = SOCK_STREAM;
if (!path)
hints.ai_flags |= AI_PASSIVE;
rc = getaddrinfo(path, port, &hints, &res);
switch (rc) {
case 0:
break;
case EAI_SYSTEM:
gacopyz_log(SMI_LOG_ERR,
_("%s:%s: cannot parse address: %s"),
path, port, strerror(errno));
return -1;
case EAI_BADFLAGS:
case EAI_SOCKTYPE:
gacopyz_log(SMI_LOG_ERR,
_("%s:%d: internal error converting %s:%s"),
__FILE__, __LINE__, path, port);
return -1;
case EAI_MEMORY:
gacopyz_log(SMI_LOG_ERR, "not enogh memory");
return -1;
default:
gacopyz_log(SMI_LOG_ERR,
"%s:%s: %s",
path, port, gai_strerror(rc));
return -1;
}
if (res->ai_addrlen > sizeof(addr)) {
gacopyz_log(SMI_LOG_ERR,
_("%s:%s: address length too big (%lu)"),
path, port,
(unsigned long) res->ai_addrlen);
freeaddrinfo(res);
return -1;
}
if (res->ai_next) {
char host[NI_MAXHOST], serv[NI_MAXSERV];
rc = getnameinfo(res->ai_addr, res->ai_addrlen,
host, sizeof host,
serv, sizeof serv,
NI_NUMERICHOST|NI_NUMERICSERV);
gacopyz_log(SMI_LOG_WARN,
_("%s:%s resolves to several addresses; "
"using %s:%s"),
path, port, host, serv);
}
memcpy(&addr, res->ai_addr, res->ai_addrlen);
freeaddrinfo(res);
#endif
} else {
if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: unsupported protocol: %s"),
conn->desc.xxfi_name, proto);
return -1;
}
fd = socket(addr.sa.sa_family, SOCK_STREAM, 0);
if (fd == -1) {
if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: unable to create new socket: %s"),
conn->desc.xxfi_name, strerror(errno));
return -1;
}
if ((flags = fcntl(fd, F_GETFD, 0)) == -1 ||
fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) {
if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: cannot set close-on-exec: %s"),
conn->desc.xxfi_name, strerror(errno));
close(fd);
return -1;
}
if (addr.sa.sa_family != PF_UNIX
&& setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *) &yes,
sizeof(yes)) == -1) {
if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: set reuseaddr failed (%s)"),
conn->desc.xxfi_name, strerror(errno));
close(fd);
return -1;
}
old_mask = umask(0117);
rc = bind(fd, &addr.sa, socklen);
umask(old_mask);
if (rc < 0) {
if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: cannot bind to port %s: %s"),
conn->desc.xxfi_name, cstr,
strerror(errno));
close(fd);
return -1;
}
if (listen(fd, backlog)) {
if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: cannot listen on port %s: %s"),
conn->desc.xxfi_name, cstr,
strerror(errno));
close(fd);
return -1;
}
return fd;
}
int
gacopyz_open(gacopyz_conn_t conn, const char *cstr, int backlog, int rmsocket)
{
char *proto;
char *port;
char *path;
if (!conn) {
gacopyz_log(SMI_LOG_ERR,
_("empty or missing socket information"));
errno = EINVAL;
return MI_FAILURE;
}
if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_DEBUG))
gacopyz_log(SMI_LOG_DEBUG,
_("%s: opening listen socket on %s"),
conn->desc.xxfi_name, cstr);
if (parse_connection(conn, cstr, &proto, &port, &path)) {
errno = ENOENT;
return MI_FAILURE;
}
conn->sd = do_connect(conn, cstr, proto, port, path, backlog,
rmsocket);
free(proto);
free(port);
free(path);
return conn->sd == -1 ? MI_FAILURE : MI_SUCCESS;
}
int
gacopyz_get_logmask(gacopyz_conn_t conn, int *mask)
{
if (!conn || !mask)
return MI_FAILURE;
*mask = conn->desc.logmask;
return MI_SUCCESS;
}
int
gacopyz_set_logmask(gacopyz_conn_t conn, int mask)
{
if (!conn)
return MI_FAILURE;
conn->desc.logmask = mask;
return MI_SUCCESS;
}
int
gacopyz_set_foreground(gacopyz_conn_t conn, int fg)
{
if (!conn)
return MI_FAILURE;
conn->foreground = fg;
return MI_SUCCESS;
}
int
gacopyz_connect(gacopyz_conn_t *pconn, struct smfiDesc *desc,
const char *cstr, int backlog, int rmsocket)
{
gacopyz_conn_t conn;
if (gacopyz_init(&conn, desc))
return MI_FAILURE;
if (gacopyz_open(conn, cstr, backlog, rmsocket) == -1) {
gacopyz_free(conn);
return MI_FAILURE;
}
*pconn = conn;
return MI_SUCCESS;
}
int
gacopyz_get_fd(gacopyz_conn_t conn)
{
if (!conn)
return -1;
return conn->sd;
}
int
gacopyz_set_master_timeout(gacopyz_conn_t conn, struct timeval *timeout)
{
if (!conn)
return MI_FAILURE;
conn->master_timeout = *timeout;
return MI_SUCCESS;
}
int
gacopyz_get_master_timeout(gacopyz_conn_t conn, struct timeval *timeout)
{
if (!conn)
return MI_FAILURE;
*timeout = conn->master_timeout;
return MI_SUCCESS;
}
int
gacopyz_set_ctx_timeout(gacopyz_conn_t conn, struct timeval *timeout)
{
if (!conn)
return MI_FAILURE;
conn->desc.ctx_timeout = *timeout;
return MI_SUCCESS;
}
int
gacopyz_get_ctx_timeout(gacopyz_conn_t conn, struct timeval *timeout)
{
if (!conn)
return MI_FAILURE;
*timeout = conn->desc.ctx_timeout;
return MI_SUCCESS;
}
#include "trans.h"
int
trans_ok(enum state from, enum state to)
{
while (1) {
if (transtab[from][to])
return 1;
if (++from == st_skip)
return 0;
if (!transtab[from][st_skip])
return 0;
}
}
/* Fix up the transition table */
static void
trans_fixup(SMFICTX *ctx)
{
transtab[st_conn][st_skip] = !!(ctx->pflags & SMFIP_NOCONNECT);
transtab[st_helo][st_skip] = !!(ctx->pflags & SMFIP_NOHELO);
transtab[st_mail][st_skip] = !!(ctx->pflags & SMFIP_NOMAIL);
transtab[st_rcpt][st_skip] = !!(ctx->pflags & SMFIP_NORCPT);
transtab[st_hdrs][st_skip] = !!(ctx->pflags & SMFIP_NOHDRS);
transtab[st_eohs][st_skip] = !!(ctx->pflags & SMFIP_NOEOH);
transtab[st_body][st_skip] = !!(ctx->pflags & SMFIP_NOBODY);
transtab[st_data][st_skip] = !!(ctx->pflags & SMFIP_NODATA);
transtab[st_unkn][st_skip] = !!(ctx->pflags & SMFIP_NOUNKNOWN);
}
static int
ctx_read(SMFICTX *ctx, char *buf, size_t size)
{
int rc = MI_SUCCESS;
while (size) {
fd_set rset;
fd_set xset;
int res;
struct timeval to;
FD_ZERO(&rset);
FD_ZERO(&xset);
FD_SET(ctx->sd, &rset);
FD_SET(ctx->sd, &xset);
to = ctx->desc->ctx_timeout;
res = select(ctx->sd + 1, &rset, NULL, &xset, &to);
if (res == 0) {
if (GACOPYZ_CTX_LOG_MATCH(ctx, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: connection timed out"),
"ctx_read");
errno = ETIMEDOUT;
rc = MI_FAILURE;
break;
} else if (res < 0) {
if (errno == EINTR)
continue;
rc = MI_FAILURE;
break;
} else if (rc > 0) {
if (FD_ISSET(ctx->sd, &xset)) {
if (GACOPYZ_CTX_LOG_MATCH(ctx, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: exception on control fd"),
"ctx_read");
rc = MI_FAILURE;
break;
}
/* Otherwise, FD_ISSET(ctx->sd, &rset) is true */
}
res = read(ctx->sd, buf, size);
if (res == -1) {
if (GACOPYZ_CTX_LOG_MATCH(ctx, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: read failed: %s"),
"ctx_read",
strerror(errno));
rc = MI_FAILURE;
break;
} else if (res == 0) {
if (GACOPYZ_CTX_LOG_MATCH(ctx, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: end of file"),
"ctx_read");
rc = MI_FAILURE;
break;
}
buf += res;
size -= res;
}
return rc;
}
static int
ctx_write(SMFICTX *ctx, const char *buf, size_t size)
{
int rc = MI_SUCCESS;
while (size) {
fd_set wset;
fd_set xset;
int res;
struct timeval to;
FD_ZERO(&wset);
FD_ZERO(&xset);
FD_SET(ctx->sd, &wset);
FD_SET(ctx->sd, &xset);
to = ctx->desc->ctx_timeout;
res = select(ctx->sd + 1, NULL, &wset, &xset, &to);
if (res == 0) {
if (GACOPYZ_CTX_LOG_MATCH(ctx, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: connection timed out"),
"ctx_write");
errno = ETIMEDOUT;
rc = MI_FAILURE;
break;
} else if (res < 0) {
if (errno == EINTR)
continue;
rc = MI_FAILURE;
break;
} else if (rc > 0) {
if (FD_ISSET(ctx->sd, &xset)) {
if (GACOPYZ_CTX_LOG_MATCH(ctx, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: exception on control fd"),
"ctx_write");
rc = MI_FAILURE;
break;
}
/* Otherwise, FD_ISSET(ctx->sd, &wset) is true */
}
res = write(ctx->sd, buf, size);
if (res == -1) {
if (GACOPYZ_CTX_LOG_MATCH(ctx, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: write failed: %s"),
"ctx_write",
strerror(errno));
rc = MI_FAILURE;
break;
} else if (res == 0) {
if (GACOPYZ_CTX_LOG_MATCH(ctx, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: wrote 0 bytes"),
"ctx_write");
rc = MI_FAILURE;
break;
}
buf += res;
size -= res;
}
return rc;
}
union header {
struct {
gacopyz_uint32_t size;
unsigned char cmd;
} hdr;
char buf[5];
};
static int
get_command(SMFICTX *ctx, unsigned char *cmd, size_t *pcount,
char **pbuf, size_t *psize)
{
union header header;
size_t size;
int rc;
if ((rc = ctx_read(ctx, header.buf, sizeof header.buf)) != MI_SUCCESS)
return rc;
size = ntohl(header.hdr.size) - 1;
if (size + 1 > *psize) {
char *p = realloc(*pbuf, size + 1);
if (!p) {
if (GACOPYZ_CTX_LOG_MATCH(ctx, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR, "%s",
strerror(errno));
return MI_FAILURE;
}
*pbuf = p;
*psize = size + 1;
}
if (GACOPYZ_CTX_LOG_MATCH(ctx, SMI_LOG_DEBUG))
gacopyz_log(SMI_LOG_DEBUG,
_("read header: size=%lu, cmd=%c"),
size, header.hdr.cmd);
if ((rc = ctx_read(ctx, *pbuf, size)) != MI_SUCCESS)
return rc;
(*pbuf)[size] = 0;
if (GACOPYZ_CTX_LOG_MATCH(ctx, SMI_LOG_PROTO))
gacopyz_logdump(SMI_LOG_PROTO, _("read data"), *pbuf, size);
else if (GACOPYZ_CTX_LOG_MATCH(ctx, SMI_LOG_DEBUG))
gacopyz_log(SMI_LOG_DEBUG, _("read data"));
*pcount = size;
*cmd = header.hdr.cmd;
return MI_SUCCESS;
}
#define _GACOPYZ_R_NOREPLY 0
static unsigned char
convert_sfsistat(sfsistat stat)
{
switch (stat) {
case SMFIS_CONTINUE:
return SMFIR_CONTINUE;
case SMFIS_REJECT:
return SMFIR_REJECT;
case SMFIS_DISCARD:
return SMFIR_DISCARD;
case SMFIS_ACCEPT:
return SMFIR_ACCEPT;
case SMFIS_TEMPFAIL:
return SMFIR_TEMPFAIL;
case SMFIS_NOREPLY:
return _GACOPYZ_R_NOREPLY;
case SMFIS_SKIP:
return SMFIR_SKIP;
default:
break;
}
return SMFIR_TEMPFAIL; /* Just in case */
}
#define OPTLEN 3*sizeof(gacopyz_uint32_t)
static int
make_optneg_buf(SMFICTX *ctx, gacopyz_uint32_t *vbuf,
size_t *psize, char **pbuf)
{
char *buf;
size_t bufsize = 0;
int i;
for (i = 0; i < gacopyz_stage_max; i++) {
if (ctx->req_macros[i])
bufsize += strlen(ctx->req_macros[i]) + 1 +
sizeof(gacopyz_uint32_t);
}
if (bufsize == 0)
buf = (char*) vbuf;
else {
bufsize += OPTLEN;
buf = malloc(bufsize);
if (!buf)
return MI_FAILURE;
vbuf = (gacopyz_uint32_t*)buf;
}
vbuf[0] = htonl(ctx->version);
vbuf[1] = htonl(ctx->aflags);
vbuf[2] = htonl(ctx->pflags);
if (bufsize) {
/*char *endp = buf + bufsize;*/
*psize = bufsize;
*pbuf = buf;
buf += OPTLEN;
for (i = 0; i < gacopyz_stage_max; i++) {
if (ctx->req_macros[i]) {
gacopyz_uint32_t v;
size_t len;
v = htonl(i);
memcpy(buf, &v, sizeof(v));
buf += sizeof(v);
len = strlen(ctx->req_macros[i]) + 1;
memcpy(buf, ctx->req_macros[i], len);
buf += len;
}
}
}
return MI_SUCCESS;
}
static int
send_reply(SMFICTX *ctx, unsigned char cmd)
{
int rc;
union header header;
char *buf = NULL;
size_t bufsize = 0;
char *alloc_mem = NULL;
gacopyz_uint32_t v[3];
unsigned long nrmask = state_nr_mask[ctx->state];
if (nrmask && (ctx->pflags & nrmask) && cmd != _GACOPYZ_R_NOREPLY) {
if (GACOPYZ_CTX_LOG_MATCH(ctx, SMI_LOG_WARN))
gacopyz_log(SMI_LOG_WARN,
_("%s: milter claimed not to reply in state %d, but it did"),
ctx->desc->xxfi_name,
ctx->state);
cmd = _GACOPYZ_R_NOREPLY;
}
switch (cmd) {
case _GACOPYZ_R_NOREPLY:
if (nrmask && (ctx->pflags & nrmask)) {
if (ctx->mta_pflags & nrmask)
return 0;
else
cmd = SMFIR_CONTINUE;
}
break;
case SMFIR_CONTINUE:
break;
case SMFIR_TEMPFAIL:
if (ctx->reply && *ctx->reply == '4') {
cmd = SMFIR_REPLYCODE;
buf = ctx->reply;
bufsize = strlen(ctx->reply) + 1;
}
break;
case SMFIR_REJECT:
if (ctx->reply && *ctx->reply == '5') {
cmd = SMFIR_REPLYCODE;
buf = ctx->reply;
bufsize = strlen(ctx->reply) + 1;
}
break;
case SMFIR_DISCARD:
break;
case SMFIR_ACCEPT:
break;
case SMFIC_OPTNEG:
buf = (char*) v;
bufsize = sizeof v;
if (make_optneg_buf(ctx, v, &bufsize, &alloc_mem)
!= MI_SUCCESS)
return MI_FAILURE;
if (alloc_mem)
buf = alloc_mem;
break;
default: /* Ignore */
break;
}
TRACE(ctx, cmd, bufsize, buf);
header.hdr.size = htonl(bufsize + 1);
header.hdr.cmd = cmd;
rc = ctx_write(ctx, header.buf, sizeof header.buf);
if (rc == MI_SUCCESS) {
if (bufsize)
rc = ctx_write(ctx, buf, bufsize);
}
if (alloc_mem)
free(alloc_mem);
return rc;
}
static void
macro_assoc_free(macro_assoc_t *p)
{
free(p->argv);
free(p->buffer);
p->argv = NULL;
p->buffer = NULL;
}
static void
clear_macros(SMFICTX *ctx, int i)
{
for (; i < gacopyz_stage_max; i++)
macro_assoc_free(&ctx->macros[i]);
}
enum state_arg_type {
arg_no_args, /* no arguments */
arg_one_string, /* one string */
arg_two_strings, /* two strings */
arg_ints, /* three integers */
arg_argv, /* NULL-terminated, \0 separated list of
arguments */
arg_argvc /* 1 byte command + arg_argv */
};
union state_arg {
struct {
char *ptr;
size_t len;
} string;
char *strings[2];
gacopyz_uint32_t ints[3];
struct {
char cmd;
char **v;
char *buffer;
} argv;
};
typedef enum {
sret_fail,
sret_abort,
sret_noreply,
sret_reply,
sret_reject
} state_ret_type;
typedef state_ret_type (*state_handler_fn) (SMFICTX *,
union state_arg *,
unsigned char *);
struct state_disp {
int cmd;
char *name;
enum state_arg_type arg_type;
state_handler_fn fn;
enum state next;
int flags;
int macro_ind;
};
static state_ret_type
shan_abort(SMFICTX *ctx, union state_arg *arg, unsigned char *cmd)
{
if (ctx->desc->xxfi_abort)
ctx->desc->xxfi_abort(ctx);
return sret_noreply;
}
static state_ret_type
shan_macro(SMFICTX *ctx, union state_arg *arg, unsigned char *cmd)
{
enum gacopyz_stage ind;
char **p;
if (!arg->argv.v)
return sret_noreply;
switch (arg->argv.cmd) {
case SMFIC_CONNECT:
ind = gacopyz_stage_conn;
break;
case SMFIC_HELO:
ind = gacopyz_stage_helo;
break;
case SMFIC_MAIL:
ind = gacopyz_stage_mail;
break;
case SMFIC_RCPT:
ind = gacopyz_stage_rcpt;
break;
case SMFIC_DATA:
ind = gacopyz_stage_data;
break;
case SMFIC_BODYEOB:
ind = gacopyz_stage_eom;
break;
case SMFIC_EOH:
ind = gacopyz_stage_eoh;
break;
default:
return sret_fail;
}
macro_assoc_free(&ctx->macros[ind]);
ctx->macros[ind].argv = arg->argv.v;
arg->argv.v = NULL;
ctx->macros[ind].buffer = arg->argv.buffer;
arg->argv.buffer = NULL;
for (p = ctx->macros[ind].argv; *p; p += 2) {
int len;
if ((*p)[0] == '{')
++*p;
len = strlen(*p);
if (len > 0 && (*p)[len - 1] == '}')
(*p)[len - 1] = 0;
}
return sret_noreply;
}
static state_ret_type
shan_body(SMFICTX *ctx, union state_arg *arg, unsigned char *cmd)
{
if (ctx->desc->xxfi_body) {
*cmd = convert_sfsistat(
ctx->desc->xxfi_body(ctx,
(unsigned char*) arg->string.ptr,
arg->string.len));
} else
*cmd = SMFIR_CONTINUE;
return sret_reply;
}
static state_ret_type
shan_connect(SMFICTX *ctx, union state_arg *arg, unsigned char *cmd)
{
char *s;
size_t len;
int family;
milter_sockaddr_t sockaddr;
unsigned short port = 0;
*cmd = SMFIR_CONTINUE;
if (!ctx->desc->xxfi_connect)
return sret_reply;
memset(&sockaddr, 0, sizeof sockaddr);
s = arg->strings[1];
family = *s++;
if (family != SMFIA_UNKNOWN) {
port = *(unsigned short*) s;
s += sizeof port;
len = strlen(s);
if (len > 0 && s[len])
return sret_abort;
switch (family) {
case SMFIA_UNIX:
if (len >= sizeof sockaddr.sunix.sun_path) {
if (GACOPYZ_CTX_LOG_MATCH(ctx, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
"%s: shan_connect: %s: %lu",
ctx->desc->xxfi_name,
_("path too long"),
(unsigned long) len);
return sret_abort;
}
strcpy(sockaddr.sunix.sun_path, s);
sockaddr.sunix.sun_family = AF_UNIX;
break;
case SMFIA_INET:
if (inet_aton(s,
(struct in_addr *) &sockaddr.sin.sin_addr) != 1) {
if (GACOPYZ_CTX_LOG_MATCH(ctx, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: shan_connect: inet_aton(%s) failed"),
ctx->desc->xxfi_name,
s);
return sret_abort;
}
sockaddr.sa.sa_family = AF_INET;
sockaddr.sin.sin_port = port;
break;
case SMFIA_INET6: {
#ifdef GACOPYZ_IPV6
int rc;
struct addrinfo hints;
struct addrinfo *res;
char service[64];
if (len > GACOPYZ_IPV6PREFIX_LEN &&
memcmp (s, GACOPYZ_IPV6PREFIX_STR,
GACOPYZ_IPV6PREFIX_LEN) == 0) {
s += GACOPYZ_IPV6PREFIX_LEN;
len -= GACOPYZ_IPV6PREFIX_LEN;
}
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET6;
hints.ai_socktype = SOCK_STREAM;
snprintf(service, sizeof service, "%hu", ntohs(port));
rc = getaddrinfo(s, service, &hints, &res);
switch (rc) {
case 0:
break;
case EAI_SYSTEM:
gacopyz_log(SMI_LOG_ERR,
_("%s:%s: cannot parse "
"address: %s"),
s, service, strerror(errno));
return sret_abort;
case EAI_BADFLAGS:
case EAI_SOCKTYPE:
gacopyz_log(SMI_LOG_ERR,
_("%s:%d: internal error "
"converting %s:%s"),
__FILE__, __LINE__, s, service);
return sret_abort;
case EAI_MEMORY:
gacopyz_log(SMI_LOG_ERR, "not enogh memory");
return sret_abort;
default:
gacopyz_log(SMI_LOG_ERR,
"%s:%s: %s",
s, service, gai_strerror(rc));
return sret_abort;
}
if (res->ai_addrlen > sizeof(sockaddr)) {
gacopyz_log(SMI_LOG_ERR,
_("%s:%s: address length too "
"big (%lu)"),
s, service,
(unsigned long) res->ai_addrlen);
freeaddrinfo(res);
return sret_abort;
}
memcpy(&sockaddr, res->ai_addr, res->ai_addrlen);
freeaddrinfo(res);
break;
#endif
}
default:
if (GACOPYZ_CTX_LOG_MATCH(ctx, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: shan_connect: unknown family %d"),
ctx->desc->xxfi_name,
family);
return sret_abort;
}
}
*cmd = convert_sfsistat(
ctx->desc->xxfi_connect(ctx,
arg->strings[0],
family != SMFIA_UNKNOWN ?
&sockaddr : NULL));
return sret_reply;
}
static state_ret_type
shan_endm(SMFICTX *ctx, union state_arg *arg, unsigned char *cmd)
{
if (arg->string.len && ctx->desc->xxfi_body) {
sfsistat r =
ctx->desc->xxfi_body(ctx,
(unsigned char*) arg->string.ptr,
arg->string.len);
if (r != SMFIS_CONTINUE
&& send_reply(ctx, convert_sfsistat(r)) != MI_SUCCESS)
return sret_abort;
}
if (ctx->desc->xxfi_eom)
*cmd = convert_sfsistat(ctx->desc->xxfi_eom(ctx));
else
*cmd = SMFIR_CONTINUE;
return sret_reply;
}
static state_ret_type
shan_helo(SMFICTX *ctx, union state_arg *arg, unsigned char *cmd)
{
if (ctx->desc->xxfi_helo)
*cmd = convert_sfsistat(
ctx->desc->xxfi_helo(ctx, arg->string.ptr));
else
*cmd = SMFIR_CONTINUE;
return sret_reply;
}
static state_ret_type
shan_header(SMFICTX *ctx, union state_arg *arg, unsigned char *cmd)
{
if (ctx->desc->xxfi_header)
*cmd = convert_sfsistat(
ctx->desc->xxfi_header(ctx,
arg->strings[0],
arg->strings[1]));
else
*cmd = SMFIR_CONTINUE;
return sret_reply;
}
static state_ret_type
shan_mail(SMFICTX *ctx, union state_arg *arg, unsigned char *cmd)
{
if (ctx->desc->xxfi_envfrom) {
*cmd = convert_sfsistat(
ctx->desc->xxfi_envfrom(ctx, arg->argv.v));
} else
*cmd = SMFIR_CONTINUE;
return sret_reply;
}
#define ALL_NR_FLAGS \
(SMFIP_NR_CONN|\
SMFIP_NR_HELO|\
SMFIP_NR_MAIL|\
SMFIP_NR_RCPT|\
SMFIP_NR_DATA|\
SMFIP_NR_UNKN|\
SMFIP_NR_HDR|\
SMFIP_NR_EOH|\
SMFIP_NR_BODY)
static state_ret_type
shan_optneg(SMFICTX *ctx, union state_arg *arg, unsigned char *cmd)
{
gacopyz_uint32_t val;
unsigned long mta_aflags, mta_pflags;
val = arg->ints[0];
if (val < SMFI_PROT_VERSION_MIN) {
if (GACOPYZ_CTX_LOG_MATCH(ctx, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("protocol version too old: %lu"),
val);
return sret_abort;
}
if (val > SMFI_PROT_VERSION)
val = SMFI_PROT_VERSION;
ctx->version = val;
val = arg->ints[1];
if (!val)
val = SMFI_V1_ACTS;
mta_aflags = val;
val = arg->ints[2];
if (!val)
val = SMFI_V1_PROT;
ctx->mta_pflags = mta_pflags = val;
ctx->aflags = ctx->desc->xxfi_flags;
if (ctx->version > SMFI_PROT_VERSION_MIN
&& ctx->desc->xxfi_negotiate) {
int res;
unsigned long m_aflags = mta_aflags, m_pflags = ctx->pflags;
unsigned long m_f2 = 0, m_f3 = 0; /* reserved */
if (mta_pflags & SMFIP_SKIP)
m_pflags |= SMFIP_SKIP;
res = ctx->desc->xxfi_negotiate(ctx,
mta_aflags,
mta_pflags|ALL_NR_FLAGS,
0, 0,
&m_aflags, &m_pflags,
&m_f2, &m_f3);
switch (res) {
case SMFIS_ALL_OPTS:
ctx->aflags = mta_aflags;
/* ctx->pflags not changed */
if (mta_pflags & SMFIP_SKIP)
ctx->pflags |= SMFIP_SKIP;
break;
case SMFIS_CONTINUE:
ctx->aflags = m_aflags;
ctx->pflags = m_pflags;
break;
default:
if (GACOPYZ_CTX_LOG_MATCH(ctx, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("xxfi_negotiate returned %d (protocol options=0x%lx, actions=0x%lx)"),
res, mta_pflags, mta_aflags);
return sret_abort;
}
if ((mta_pflags & ctx->pflags) != ctx->pflags) {
unsigned i;
for (i = 0; i < 32; i++) {
unsigned long bit = 1 << i;
if ((mta_pflags & bit) != bit
&& (bit & ALL_NR_FLAGS) == bit)
ctx->pflags &= ~bit;
}
}
}
if ((mta_aflags & ctx->aflags) != ctx->aflags) {
if (GACOPYZ_CTX_LOG_MATCH(ctx, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: shan_optneg: MTA flags %#lx do not match "
"actions requirements %#lx"),
ctx->desc->xxfi_name,
mta_aflags,
ctx->aflags);
return sret_abort;
}
if ((mta_pflags & ctx->pflags) != ctx->pflags) {
/* Disable protocol steps not supported by older MTAs */
if ((ctx->pflags & SMFIP_NODATA)
&& !(mta_pflags & SMFIP_NODATA))
ctx->pflags &= ~SMFIP_NODATA;
if ((ctx->pflags & SMFIP_NOUNKNOWN)
&& !(mta_pflags & SMFIP_NOUNKNOWN))
ctx->pflags &= ~SMFIP_NOUNKNOWN;
}
if ((mta_pflags & ctx->pflags) != ctx->pflags) {
if (GACOPYZ_CTX_LOG_MATCH(ctx, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("%s: shan_optneg: MTA flags %#lx do not match "
"protocol requirements %#lx"),
ctx->desc->xxfi_name,
mta_pflags,
ctx->pflags);
return sret_abort;
}
if (GACOPYZ_DESC_LOG_MATCH(ctx->desc, SMI_LOG_DEBUG))
gacopyz_log(SMI_LOG_DEBUG,
"version=%#lx, mta_aflags=%#lx, mta_pflags=%#lx"
" aflags=%#lx, pflags=%#lx",
ctx->version,
mta_aflags, mta_pflags,
ctx->aflags, ctx->pflags);
trans_fixup(ctx);
*cmd = SMFIC_OPTNEG;
return sret_reply;
}
static state_ret_type
shan_eoh(SMFICTX *ctx, union state_arg *arg, unsigned char *cmd)
{
if (ctx->desc->xxfi_eoh)
*cmd = convert_sfsistat(ctx->desc->xxfi_eoh(ctx));
else
*cmd = SMFIR_CONTINUE;
return sret_reply;
}
static state_ret_type
shan_quit(SMFICTX *ctx, union state_arg *arg, unsigned char *cmd)
{
return sret_noreply;
}
static state_ret_type
shan_data(SMFICTX *ctx, union state_arg *arg, unsigned char *cmd)
{
if (ctx->desc->xxfi_data)
*cmd = convert_sfsistat(ctx->desc->xxfi_data(ctx));
else
*cmd = SMFIR_CONTINUE;
return sret_reply;
}
static state_ret_type
shan_rcpt(SMFICTX *ctx, union state_arg *arg, unsigned char *cmd)
{
if (ctx->desc->xxfi_envrcpt) {
*cmd = convert_sfsistat(
ctx->desc->xxfi_envrcpt(ctx, arg->argv.v));
} else
*cmd = SMFIR_CONTINUE;
return sret_reply;
}
static state_ret_type
shan_unkn(SMFICTX *ctx, union state_arg *arg, unsigned char *cmd)
{
if (ctx->desc->xxfi_unknown) {
*cmd = convert_sfsistat(
ctx->desc->xxfi_unknown(ctx, arg->string.ptr));
} else
*cmd = SMFIR_CONTINUE;
return sret_reply;
}
#define CT_CNT 0
#define CT_END 0x1
#define CT_IGN 0x2
#define CT_CLR 0x4
static struct state_disp disp[] = {
{ SMFIC_ABORT, "abort",
arg_no_args, shan_abort, st_abrt, CT_CNT, gacopyz_stage_none },
{ SMFIC_MACRO, "macro",
arg_argvc, shan_macro, st_none, CT_CNT, gacopyz_stage_none },
{ SMFIC_BODY, "body",
arg_one_string, shan_body, st_body, CT_CNT, gacopyz_stage_none },
{ SMFIC_CONNECT, "connect",
arg_two_strings, shan_connect, st_conn, CT_CLR, gacopyz_stage_conn },
{ SMFIC_BODYEOB, "endm",
arg_one_string, shan_endm, st_endm, CT_CNT, gacopyz_stage_eom },
{ SMFIC_HELO, "helo",
arg_one_string, shan_helo, st_helo, CT_CLR, gacopyz_stage_helo },
{ SMFIC_HEADER, "header",
arg_two_strings, shan_header, st_hdrs, CT_CNT, gacopyz_stage_none },
{ SMFIC_MAIL, "mail",
arg_argv, shan_mail, st_mail, CT_CLR, gacopyz_stage_mail },
{ SMFIC_OPTNEG, "optneg",
arg_ints, shan_optneg, st_opts, CT_CNT, gacopyz_stage_none },
{ SMFIC_EOH, "eoh",
arg_no_args, shan_eoh, st_eohs, CT_CNT, gacopyz_stage_none },
{ SMFIC_QUIT, "quit",
arg_no_args, shan_quit, st_quit, CT_END, gacopyz_stage_none },
{ SMFIC_DATA, "data",
arg_no_args, shan_data, st_data, CT_CNT, gacopyz_stage_none },
{ SMFIC_RCPT, "rcpt",
arg_argv, shan_rcpt, st_rcpt, CT_IGN|CT_CLR, gacopyz_stage_rcpt },
{ SMFIC_UNKNOWN, "unknown",
arg_one_string, shan_unkn, st_unkn, CT_IGN|CT_CLR, gacopyz_stage_none }
};
struct state_disp *
find_disp(int cmd, int allow_unkn)
{
struct state_disp *def = NULL;
struct state_disp *sd;
for (sd = disp; sd < disp + sizeof(disp)/sizeof(disp[0]); sd++) {
if (sd->cmd == cmd)
return sd;
if (allow_unkn && sd->cmd == SMFIC_UNKNOWN)
def = sd;
}
return def;
}
static int
parse_state_arg(union state_arg *arg, enum state_arg_type type,
char *buffer, size_t size)
{
switch (type) {
case arg_no_args: /* no arguments */
return MI_SUCCESS;
case arg_one_string: /* one string */
arg->string.ptr = buffer;
arg->string.len = size;
return MI_SUCCESS;
case arg_two_strings: /* two strings */
{
int len = strlen(buffer);
arg->strings[0] = buffer;
arg->strings[1] = buffer + len + 1;
return MI_SUCCESS;
}
case arg_ints: /* three integers */
{
if (size < GACOPYZ_OPTLEN)
return MI_FAILURE;
arg->ints[0] = ntohl(((gacopyz_uint32_t*)buffer)[0]);
arg->ints[1] = ntohl(((gacopyz_uint32_t*)buffer)[1]);
arg->ints[2] = ntohl(((gacopyz_uint32_t*)buffer)[2]);
return MI_SUCCESS;
}
case arg_argv: /* NULL-terminated, \0 separated list of strings */
case arg_argvc:
{
size_t i, count = 0;
char *p;
if (type == arg_argvc) {
arg->argv.cmd = *buffer;
buffer++;
size--;
}
arg->argv.buffer = malloc(size);
if (!arg->argv.buffer)
return MI_FAILURE;
memcpy(arg->argv.buffer, buffer, size);
for (p = arg->argv.buffer; p < arg->argv.buffer + size; p++)
if (*p == 0)
count++;
if (count == 0) {
arg->argv.v = NULL;
return MI_SUCCESS;
}
arg->argv.v = calloc(count + 1, sizeof arg->argv.v[0]);
if (!arg->argv.v) {
free(arg->argv.buffer);
return MI_FAILURE;
}
for (i = 0, p = arg->argv.buffer; i < count;
i++, p += strlen(p) + 1)
arg->argv.v[i] = p;
arg->argv.v[i] = NULL;
return MI_SUCCESS;
}
}
return MI_FAILURE;
}
static void
arg_free(union state_arg *arg, enum state_arg_type type)
{
if (type == arg_argv) {
free(arg->argv.v);
free(arg->argv.buffer);
}
}
/* in a mail transaction? must be before eom according to spec. */
#define ST_IN_MAIL(st) ((st) >= st_mail && (st) < st_endm)
#define STATE_NAME(st) ((st == st_none) ? "st_none" : state_name[st])
static void
report_command(enum state state, enum state next_state,
int cmd, char *buffer, size_t size)
{
gacopyz_log(SMI_LOG_DEBUG,
_("state %s is unreachable from %s"),
STATE_NAME(next_state), STATE_NAME(state));
gacopyz_log(SMI_LOG_DEBUG,
isascii(cmd) && isprint(cmd)
? _("ignoring milter command: %c")
: _("ignoring milter command: \\%03o"),
(unsigned char) cmd);
gacopyz_logdump(SMI_LOG_DEBUG, _("buffer"), buffer, size);
}
static void
ctx_free(SMFICTX *ctx)
{
int i;
free(ctx->reply);
clear_macros(ctx, 0);
for (i = 0; i < gacopyz_stage_max; i++)
free(ctx->req_macros[i]);
}
int
gacopyz_context_loop(int fd, struct smfiDesc const *desc,
milter_sockaddr_t *addr, socklen_t addrlen,
void *closure)
{
int rc;
char *buffer = NULL;
size_t bufsize = 0;
size_t size;
unsigned char cmd;
SMFICTX ctx;
int allow_unkn = desc->xxfi_unknown != NULL;
enum state state = st_init;
int stop = 0;
if (desc->xxfi_start)
desc->xxfi_start();
memset(&ctx, 0, sizeof ctx);
ctx.desc = desc;
ctx.sd = fd;
ctx.addr = *addr;
ctx.addrlen = addrlen;
ctx.closure = closure;
if (!desc->xxfi_connect)
ctx.pflags |= SMFIP_NOCONNECT;
if (!desc->xxfi_helo)
ctx.pflags |= SMFIP_NOHELO;
if (!desc->xxfi_envfrom)
ctx.pflags |= SMFIP_NOMAIL;
if (!desc->xxfi_envrcpt)
ctx.pflags |= SMFIP_NORCPT;
if (!desc->xxfi_header)
ctx.pflags |= SMFIP_NOHDRS;
if (!desc->xxfi_eoh)
ctx.pflags |= SMFIP_NOEOH;
if (!desc->xxfi_body)
ctx.pflags |= SMFIP_NOBODY;
if (!desc->xxfi_data)
ctx.pflags |= SMFIP_NODATA;
if (!desc->xxfi_unknown)
ctx.pflags |= SMFIP_NOUNKNOWN;
trans_fixup(&ctx);
if (GACOPYZ_DESC_LOG_MATCH(desc, SMI_LOG_DEBUG))
gacopyz_log(SMI_LOG_DEBUG, _("start of context loop"));
while (!stop
&& (rc = get_command(&ctx, &cmd, &size, &buffer, &bufsize))
== MI_SUCCESS) {
enum state next_state;
state_ret_type ret;
union state_arg arg;
struct state_disp *sd = find_disp(cmd, allow_unkn);
if (sd == NULL) {
if (GACOPYZ_DESC_LOG_MATCH(desc, SMI_LOG_ERR)) {
gacopyz_log(SMI_LOG_ERR,
isascii(cmd) && isprint(cmd)
? _("unknown command: %c")
: _("unknown command: \\%03o"),
(unsigned char) cmd);
gacopyz_logdump(SMI_LOG_ERR,
_("buffer"),
buffer, size);
}
rc = MI_FAILURE;
break;
}
next_state = sd->next;
stop = sd->flags & CT_END;
if (GACOPYZ_DESC_LOG_MATCH(desc, SMI_LOG_DEBUG))
gacopyz_log(SMI_LOG_DEBUG,
_("state %s, next state %s"),
STATE_NAME(state),
next_state == st_none ?
STATE_NAME(state) :
STATE_NAME(next_state));
if (next_state != st_none && !trans_ok(state, next_state)) {
if (GACOPYZ_DESC_LOG_MATCH(desc, SMI_LOG_DEBUG))
gacopyz_log(SMI_LOG_DEBUG,
_("transition from state %s to %s is "
"prohibited"),
STATE_NAME(state),
STATE_NAME(next_state));
if (ST_IN_MAIL(state) && desc->xxfi_abort)
desc->xxfi_abort(&ctx);
/* Retry starting from HELO */
state = st_helo;
if (!trans_ok(state, next_state)) {
if (GACOPYZ_DESC_LOG_MATCH(desc,
SMI_LOG_DEBUG))
report_command(state, next_state,
cmd, buffer, size);
continue; /* Ignore the command */
}
}
if (next_state != st_none) {
state = next_state;
ctx.state = state;
}
if (parse_state_arg(&arg, sd->arg_type, buffer, size)) {
if (GACOPYZ_DESC_LOG_MATCH(desc, SMI_LOG_ERR)) {
gacopyz_log(SMI_LOG_ERR,
_("argument parsing failed"));
gacopyz_logdump(SMI_LOG_ERR,
_("buffer"),
buffer, size);
}
rc = MI_FAILURE;
break;
}
if (ctx.reply) {
free(ctx.reply);
ctx.reply = NULL;
}
if (sd->flags & CT_CLR)
clear_macros(&ctx, sd->macro_ind + 1);
ret = sd->fn(&ctx, &arg, &cmd);
arg_free(&arg, sd->arg_type);
switch (ret) {
case sret_noreply:
break;
case sret_reply:
rc = send_reply(&ctx, cmd);
break;
case sret_reject:
if (!(sd->flags & CT_IGN))
state = st_helo;
break;
case sret_fail:
if (GACOPYZ_DESC_LOG_MATCH(desc, SMI_LOG_DEBUG))
gacopyz_log(SMI_LOG_DEBUG,
_("%s handler returned failure"),
sd->name);
break;
case sret_abort:
if (GACOPYZ_DESC_LOG_MATCH(desc, SMI_LOG_DEBUG))
gacopyz_log(SMI_LOG_DEBUG,
_("%s handler returned abort"),
sd->name);
rc = MI_FAILURE;
}
if (rc != MI_SUCCESS)
break;
}
if (GACOPYZ_DESC_LOG_MATCH(desc, SMI_LOG_DEBUG))
gacopyz_log(SMI_LOG_DEBUG,
_("end of context loop: %d"), rc);
if (rc != MI_SUCCESS && ST_IN_MAIL(state)
&& desc->xxfi_abort)
desc->xxfi_abort(&ctx);
free(buffer);
if (desc->xxfi_close)
desc->xxfi_close(&ctx);
ctx_free(&ctx);
if (desc->xxfi_finish)
desc->xxfi_finish();
return rc;
}
int
gacopyz_handle_connection(gacopyz_conn_t conn)
{
milter_sockaddr_t addr;
socklen_t addrlen = sizeof addr;
int fd;
int rc;
fd = accept(conn->sd, &addr.sa, &addrlen);
if (fd == -1) {
if (errno == EINTR)
return MI_SUCCESS;
if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("accept failed: %s"), strerror(errno));
return MI_FAILURE;
}
if (conn->desc.xxfi_accept
&& conn->desc.xxfi_accept(conn, fd, &addr, addrlen)) {
if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_DEBUG))
gacopyz_log(SMI_LOG_DEBUG,
_("connection refused by xxfi_accept"));
close (fd);
return MI_SUCCESS;
}
if (!conn->foreground) {
pid_t pid = fork();
if (pid == -1) {
if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_ERR))
gacopyz_log(SMI_LOG_ERR,
_("fork() failed: %s"),
strerror(errno));
return MI_FAILURE;
}
if (pid > 0) {
gacopyz_register_child(conn, pid);
close(fd);
return MI_SUCCESS;
}
/* Child: */
/* Reset signal handlers */
signal(SIGCHLD, SIG_IGN);
signal(SIGPIPE, SIG_DFL);
signal(SIGTERM, SIG_DFL);
signal(SIGHUP, SIG_DFL);
/* Close listen descriptor */
close(conn->sd);
conn->sd = -1;
}
switch (addr.sa.sa_family) {
case AF_UNIX: {
char *path;
if (addrlen == sizeof (addr.sa.sa_family))
path = "[unnamed]";
else
path = addr.sunix.sun_path;
if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_INFO))
gacopyz_log(SMI_LOG_INFO,
_("connect from socket %s"),
path);
break;
}
case AF_INET:
if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_INFO))
gacopyz_log(SMI_LOG_INFO, _("connect from %s:%u"),
inet_ntoa(addr.sin.sin_addr),
(unsigned) ntohs(addr.sin.sin_port));
break;
default:
if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_INFO))
gacopyz_log(SMI_LOG_INFO,
_("connection from unsupported family: %d"),
addr.sa.sa_family);
}
rc = gacopyz_context_loop(fd, &conn->desc, &addr, addrlen, NULL);
close(fd);
if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_INFO))
gacopyz_log(SMI_LOG_INFO, _("finishing connection"));
if (!conn->foreground) {
exit(0);
}
return rc;
}
int
gacopyz_run(gacopyz_conn_t conn)
{
while (!conn->stop) {
fd_set rfd;
struct timeval timeout = conn->master_timeout;
int rc;
gacopyz_cleanup_children(conn);
FD_ZERO(&rfd);
FD_SET(conn->sd, &rfd);
rc = select(conn->sd + 1, &rfd, NULL, NULL, &timeout);
if (rc < 0) {
if (errno == EINTR)
continue;
return MI_FAILURE;
} else if (rc == 0) {
if (conn->desc.xxfi_idle)
conn->desc.xxfi_idle(conn);
} else
gacopyz_handle_connection(conn);
}
gacopyz_cleanup_conn(conn);
return MI_SUCCESS;
}
int
gacopyz_stop(gacopyz_conn_t conn)
{
if (!conn)
return MI_FAILURE;
conn->stop = 1;
return MI_SUCCESS;
}
#define ISWS(c) ((c) == ' ' || (c) == '\t')
const char *
gacopyz_safe_header_value(const char *input, char **poutput)
{
char *output;
const char *p;
size_t len;
size_t count;
int tab, newline;
for (count = 0, p = input; *p && (p = strchr(p, '\n')); ) {
if (p == input || (p[1] && !ISWS(p[1])))
count++;
p++;
}
if (!count) {
*poutput = NULL;
return input;
}
output = malloc(strlen(input) + count + 1);
*poutput = output;
if (!output)
return NULL;
newline = tab = 0;
while (*input) {
len = strcspn(input, "\n");
if (len == 0 && input[1] == 0)
break;
if (newline)
*output++ = '\n';
else
newline = 1;
if (tab)
*output++ = '\t';
tab = !ISWS(input[len+1]);
if (len) {
memcpy(output, input, len);
input += len;
output += len;
}
if (*input)
input++;
}
*output = 0;
return *poutput;
}
static int
gacopyz_header_command0(SMFICTX *ctx, unsigned char cmd,
int idx, const char *headerf,
const char *headerv)
{
char *buf;
size_t bufsize;
union header *hptr;
char *cptr;
size_t lenf, lenv;
int rc;
if (!headerf || !*headerf || !headerv)
return MI_FAILURE;
lenf = strlen(headerf) + 1;
lenv = strlen(headerv) + 1;
bufsize = lenf + lenv;
if (idx >= 0)
bufsize += sizeof(gacopyz_uint32_t);
buf = malloc(sizeof hptr->buf + bufsize);
if (!buf)
return MI_FAILURE;
hptr = (union header *) buf;
cptr = buf + sizeof hptr->buf;
if (idx >= 0) {
*(gacopyz_uint32_t*)cptr = htonl(idx);
cptr += sizeof(gacopyz_uint32_t);
}
memcpy(cptr, headerf, lenf);
cptr += lenf;
memcpy(cptr, headerv, lenv);
cptr += lenv;
TRACE(ctx, cmd, sizeof hptr->buf + bufsize, buf);
hptr->hdr.cmd = cmd;
hptr->hdr.size = htonl(bufsize + 1);
rc = ctx_write(ctx, buf, sizeof hptr->buf + bufsize);
free(buf);
return rc;
}
static int
gacopyz_header_command(SMFICTX *ctx, unsigned char cmd,
int idx, const char *headerf,
const char *headerv)
{
int rc;
char *header_tmp;
if (!headerf || !*headerf || !headerv)
return MI_FAILURE;
headerv = gacopyz_safe_header_value(headerv, &header_tmp);
if (!headerv)
return MI_FAILURE;
rc = gacopyz_header_command0(ctx, cmd, idx, headerf, headerv);
free(header_tmp);
return rc;
}
static int
gacopyz_rcpt_command(SMFICTX *ctx, unsigned char cmd, const char *rcpt)
{
char *buf;
size_t bufsize;
union header *hptr;
char *cptr;
int rc;
if (!rcpt || !*rcpt)
return MI_FAILURE;
bufsize = strlen(rcpt) + 1;
buf = malloc(sizeof hptr->buf + bufsize);
if (!buf)
return MI_FAILURE;
hptr = (union header *) buf;
cptr = buf + sizeof hptr->buf;
strcpy(cptr, rcpt);
TRACE(ctx, cmd, bufsize, buf);
hptr->hdr.cmd = cmd;
hptr->hdr.size = htonl(bufsize + 1);
rc = ctx_write(ctx, buf, sizeof hptr->buf + bufsize);
free(buf);
return rc;
}
static int
gacopyz_argn_command(SMFICTX *ctx, unsigned char cmd, int argc, char const **argv)
{
char *buf;
size_t bufsize;
union header *hptr;
char *cptr;
int i, rc;
if (argc == 0)
return MI_FAILURE;
bufsize = 0;
for (i = 0; i < argc; i++)
bufsize += strlen(argv[i]) + 1;
buf = malloc(sizeof hptr->buf + bufsize);
if (!buf)
return MI_FAILURE;
hptr = (union header *) buf;
cptr = buf + sizeof hptr->buf;
for (i = 0; i < argc; i++) {
char const *q;
for (q = argv[i]; *cptr++ = *q++; );
}
TRACE(ctx, cmd, sizeof hptr->buf + bufsize, buf);
hptr->hdr.cmd = cmd;
hptr->hdr.size = htonl(bufsize + 1);
rc = ctx_write(ctx, buf, sizeof hptr->buf + bufsize);
free(buf);
return rc;
}
static int
ok_to_send(SMFICTX *ctx, int cmd)
{
if (!ctx)
return 0;
if (cmd && !(ctx->aflags & cmd))
return 0;
return ctx->state == st_endm;
}
int
gacopyz_add_header(SMFICTX *ctx, const char *headerf, const char *headerv)
{
if (!ok_to_send(ctx, SMFIF_ADDHDRS))
return MI_FAILURE;
return gacopyz_header_command(ctx, SMFIR_ADDHEADER, -1,
headerf, headerv);
}
int
gacopyz_insert_header(SMFICTX *ctx, int index, const char *headerf,
const char *headerv)
{
if (!ok_to_send(ctx, SMFIF_ADDHDRS) || index < 0)
return MI_FAILURE;
return gacopyz_header_command(ctx, SMFIR_INSHEADER, index,
headerf, headerv);
}
int
gacopyz_change_header(SMFICTX *ctx, int index, const char *headerf,
const char *headerv)
{
if (!ok_to_send(ctx, SMFIF_CHGHDRS) || index < 0)
return MI_FAILURE;
return gacopyz_header_command(ctx, SMFIR_CHGHEADER, index,
headerf, headerv ? headerv : "");
}
int
gacopyz_add_rcpt(SMFICTX *ctx, const char *rcpt)
{
if (!ok_to_send(ctx, SMFIF_ADDRCPT))
return MI_FAILURE;
return gacopyz_rcpt_command(ctx, SMFIR_ADDRCPT, rcpt);
}
int
gacopyz_del_rcpt(SMFICTX *ctx, const char *rcpt)
{
if (!ok_to_send(ctx, SMFIF_DELRCPT))
return MI_FAILURE;
return gacopyz_rcpt_command(ctx, SMFIR_DELRCPT, rcpt);
}
#define GACOPYZ_CHUNK_SIZE 65535
int
gacopyz_replace_body_fn(SMFICTX *ctx,
void *bodyp,
ssize_t (*cpf)(char*,void*,size_t))
{
union header *hptr;
char buf[sizeof hptr->buf + GACOPYZ_CHUNK_SIZE];
if (!bodyp || !cpf)
return MI_FAILURE;
if (!ok_to_send(ctx, SMFIF_CHGBODY))
return MI_FAILURE;
hptr = (union header *)buf;
hptr->hdr.cmd = SMFIR_REPLBODY;
while (1) {
ssize_t wrs = cpf(buf + sizeof hptr->buf, bodyp,
GACOPYZ_CHUNK_SIZE);
if (wrs == 0)
break;
if (wrs < 0)
return MI_FAILURE;
hptr->hdr.size = htonl(wrs + 1);
TRACE(ctx, hptr->hdr.cmd, wrs, buf);
if (ctx_write(ctx, buf, sizeof hptr->buf + wrs))
return MI_FAILURE;
}
return MI_SUCCESS;
}
struct bodyptr {
const unsigned char *bodyp;
size_t bodylen;
};
static ssize_t
cpf_mem(char *dst, void *src, size_t size)
{
struct bodyptr *bp = src;
if (size > bp->bodylen) {
size = bp->bodylen;
if (size == 0)
return size;
}
memcpy(dst, bp->bodyp, size);
bp->bodyp += size;
bp->bodylen -= size;
return size;
}
int
gacopyz_replace_body(SMFICTX *ctx, const unsigned char *bodyp, size_t bodylen)
{
struct bodyptr bp;
bp.bodyp = bodyp;
bp.bodylen = bodylen;
return gacopyz_replace_body_fn(ctx, &bp, cpf_mem);
}
struct bodyfd {
int fd;
};
static ssize_t
cpf_fd(char *dst, void *src, size_t size)
{
struct bodyfd *bp = src;
return read(bp->fd, dst, size);
}
int
gacopyz_replace_body_fd(SMFICTX *ctx, int fd)
{
struct bodyfd bp;
bp.fd = fd;
return gacopyz_replace_body_fn(ctx, &bp, cpf_fd);
}
int
gacopyz_progress(SMFICTX *ctx)
{
union header hdr;
hdr.hdr.cmd = SMFIR_PROGRESS;
hdr.hdr.size = htonl(1);
TRACE(ctx, hdr.hdr.cmd, 0, NULL);
return ctx_write(ctx, hdr.buf, sizeof hdr.buf);
}
int
gacopyz_quarantine(SMFICTX *ctx, const char *reason)
{
if (reason == NULL || *reason == '\0')
return MI_FAILURE;
if (!ok_to_send(ctx, SMFIF_QUARANTINE))
return MI_FAILURE;
return gacopyz_rcpt_command(ctx, SMFIR_QUARANTINE, reason);
}
int
gacopyz_add_rcpt_par(SMFICTX *ctx, const char *rcpt, const char *args)
{
int n;
char const *argv[2];
if (!rcpt || !*rcpt)
return MI_FAILURE;
if (!ok_to_send(ctx, SMFIF_ADDRCPT_PAR))
return MI_FAILURE;
argv[0] = rcpt;
if (args) {
argv[1] = args;
n = 2;
} else
n = 1;
return gacopyz_argn_command(ctx, SMFIR_ADDRCPT_PAR, n, argv);
}
int
gacopyz_chgfrom(SMFICTX *ctx, const char *from, const char *args)
{
int n;
char const *argv[2];
if (!from || !*from)
return MI_FAILURE;
if (!ok_to_send(ctx, SMFIF_CHGFROM))
return MI_FAILURE;
argv[0] = from;
if (args) {
argv[1] = args;
n = 2;
} else
n = 1;
return gacopyz_argn_command(ctx, SMFIR_CHGFROM, n, argv);
}
int
gacopyz_setsymlist(SMFICTX *ctx, enum gacopyz_stage ind, const char *macros)
{
if (ind < 0 || ind >= gacopyz_stage_max)
return MI_FAILURE;
if (ctx->req_macros[ind])
free(ctx->req_macros[ind]);
if (macros == NULL)
ctx->req_macros[ind] = NULL;
else {
ctx->req_macros[ind] = strdup(macros);
if (!ctx->req_macros[ind])
return MI_FAILURE;
}
return MI_SUCCESS;
}