/* This file is part of gacopyz.
Copyright (C) 2007 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
typedef struct gacopyz_macro_def *gacopyz_macro_def_t;
struct gacopyz_macro_def {
char *name;
char *value;
};
enum gacopyz_srv_state {
srv_init, /* Initialized but not connected */
srv_connected, /* Initialized and connected to the client */
srv_ready, /* Ready for processing requests */
srv_msgproc, /* Processing a message */
srv_done, /* Finished processing a message */
srv_closed, /* Finished and closed */
srv_disabled, /* Disabled for any further requests */
srv_error, /* Some error ocurred */
};
struct gacopyz_srv {
char *id; /* Server identifier (not used yet) */
char *portspec; /* Port spec */
struct gacopyz_iod iod;
enum gacopyz_srv_state state;
unsigned long flags;
gacopyz_macro_def_t def;
size_t ndefs;
size_t maxdefs;
gacopyz_uint32_t source_addr;
int onerror;
void (*memerror) (gacopyz_srv_t, const char *, unsigned int);
int version;
int acts;
int proto;
char *buf;
size_t bufsize;
char *resp_ptr;
size_t resp_size;
};
void
default_memerror (gacopyz_srv_t srv, const char *file, unsigned int line)
{
gacopyz_io_log(&srv->iod, SMI_LOG_FATAL,
_("%s:%lu: Not enough memory"),
file, line);
abort();
}
#define GACOPYZ_ASSERT(e) \
if (!(e)) { \
fprintf(stderr, "%s:%d: GACOPYZ usage error: assertion failed: %s\n", \
__FILE__, __LINE__, #e); \
abort(); \
}
#define GACOPYZ_ALLOC(srv, expr) do \
if (!(expr)) { \
srv->memerror(srv, __FILE__, __LINE__); \
if (!(expr)) \
default_memerror(srv, __FILE__, __LINE__); \
} while (0)
static struct gacopyz_macro_def *
find_def(gacopyz_srv_t srv, const char *name, size_t len)
{
size_t i;
for (i = 0; i < srv->ndefs; i++)
if (strncmp(srv->def[i].name, name, len) == 0)
return &srv->def[i];
return NULL;
}
static void
add_def(gacopyz_srv_t srv, const char *name, size_t len,
const char *value, int overwrite)
{
struct gacopyz_macro_def *def;
if (def = find_def(srv, name, len)) {
if (overwrite)
free(def->value);
} else {
if (srv->ndefs == srv->maxdefs) {
size_t maxdefs = srv->maxdefs + 64;
GACOPYZ_ALLOC(srv,
def = realloc(srv->def,
sizeof(srv->def[0]) * maxdefs));
srv->maxdefs = maxdefs;
srv->def = def;
}
def = srv->def + srv->ndefs++;
GACOPYZ_ALLOC(srv, def->name = malloc(len + 1));
memcpy(def->name, name, len);
def->name[len] = 0;
}
GACOPYZ_ALLOC(srv, def->value = strdup(value));
}
void
del_def(gacopyz_srv_t srv, const char *name, size_t len)
{
struct gacopyz_macro_def *def = find_def(srv, name, len);
size_t n;
if (!def)
return;
free(def->name);
free(def->value);
srv->ndefs--;
memmove(def, def + 1,
sizeof(srv->def[0]) *(srv->ndefs - (def - srv->def)));
}
static void
srv_ensure_space(gacopyz_srv_t srv, size_t size)
{
if (size > srv->bufsize) {
char *p;
GACOPYZ_ALLOC(srv, p = realloc(srv->buf, size));
srv->buf = p;
srv->bufsize = size;
}
}
size_t
macro_size(gacopyz_srv_t srv)
{
size_t i;
size_t size = 0;
struct gacopyz_macro_def *def;
for (i = 0, def = srv->def; i < srv->ndefs; i++, def++) {
size_t len = strlen(def->name);
size += len;
if (len > 1 && def->name[0] != '{')
size += 2; /* for {} */
size += 1 + strlen (def->value) + 1;
}
return size;
}
void
srv_format_macros(gacopyz_srv_t srv, unsigned char cmd, size_t *psize)
{
size_t i;
char *p;
size_t size = macro_size(srv) + 1;
struct gacopyz_macro_def *def;
srv_ensure_space(srv, size);
p = srv->buf;
*p++ = cmd;
for (i = 0, def = srv->def; i < srv->ndefs; i++, def++) {
size_t len = strlen(def->name);
if (len > 1 && def->name[0] != '{') {
*p++ = '{';
memcpy(p, def->name, len);
p += len;
*p++ = '}';
*p++ = 0;
} else {
len++;
memcpy(p, def->name, len);
p += len;
}
len = strlen(def->value) + 1;
memcpy(p, def->value, len);
p += len;
}
*psize = size;
}
#define GETNAMELEN(name, len) \
len = strlen(name); \
if (name[0] == '{' && name[len-1] == '}') { \
name++; \
len -= 2; \
}
int
gacopyz_srv_find_macro(gacopyz_srv_t srv, const char *name, const char **pval)
{
struct gacopyz_macro_def *def;
size_t len;
GACOPYZ_ASSERT(srv);
GETNAMELEN(name, len);
def = find_def(srv, name, len);
if (def) {
*pval = def->value;
return MI_SUCCESS;
}
return MI_FAILURE;
}
void
gacopyz_srv_define_macro0(gacopyz_srv_t srv,
const char *name, const char *value,
int overwrite)
{
size_t len;
GACOPYZ_ASSERT(srv);
GETNAMELEN(name, len);
add_def(srv, name, len, value, overwrite);
}
void
gacopyz_srv_define_macro(gacopyz_srv_t srv,
const char *name, const char *value)
{
gacopyz_srv_define_macro0(srv, name, value, 1);
}
void
gacopyz_srv_del_macro(gacopyz_srv_t srv, const char *name)
{
size_t len;
GACOPYZ_ASSERT(srv);
GETNAMELEN(name, len);
del_def(srv, name, len);
}
void
gacopyz_srv_clear_macros(gacopyz_srv_t srv)
{
size_t i;
GACOPYZ_ASSERT(srv);
for (i = 0; i < srv->ndefs; i++) {
free(srv->def[i].name);
free(srv->def[i].value);
}
srv->ndefs = 0;
}
void
gacopyz_srv_iterate_macros(gacopyz_srv_t srv,
int (*func)(const char *name, const char *value,
void *data),
void *data)
{
size_t i;
GACOPYZ_ASSERT(srv);
for (i = 0; i < srv->ndefs; i++)
if (func(srv->def[i].name, srv->def[i].value, data))
break;
}
void
gacopyz_srv_count_macros(gacopyz_srv_t srv, size_t *count)
{
GACOPYZ_ASSERT(srv);
*count = srv->ndefs;
}
struct timeval default_gacopyz_timeout[GACOPYZ_TO_COUNT] = {
{ GACOPYZ_WRITE_TIMEOUT, 0 },
{ GACOPYZ_READ_TIMEOUT, 0 },
{ GACOPYZ_EOM_TIMEOUT, 0 },
{ GACOPYZ_CONNECT_TIMEOUT, 0 }
};
int
gacopyz_srv_create(gacopyz_srv_t *p, const char *name,
const char *portspec, unsigned logmask)
{
int i;
gacopyz_srv_t srv = calloc(1, sizeof(*srv));
if (!srv)
return MI_FAILURE;
srv->id = strdup(name);
srv->portspec = strdup(portspec);
srv->iod.sd = -1;
srv->iod.logmask = logmask;
memcpy (srv->iod.timeout, default_gacopyz_timeout,
sizeof srv->iod.timeout);
srv->state = srv_init;
srv->source_addr = INADDR_ANY;
srv->onerror = SMFIR_TEMPFAIL;
srv->version = SMFI_VERSION;
srv->proto = SMFI_V2_PROT;
srv->acts = SMFI_V2_ACTS;
srv->memerror = default_memerror;
*p = srv;
return MI_SUCCESS;
}
static int
parse_X_spec (const char *spec, char *xspec, char **id, char **port,
struct timeval to[], int *onerr, int debug)
{
char *p, *q;
int havef = 0, havet = 0;
p = strtok(xspec, ",");
if (!p) {
if (debug)
gacopyz_logmsg(SMI_LOG_DEBUG,
_("%s: not a valid X spec"),
spec);
return MI_FAILURE;
}
if (p[1] != '=') {
*id = p;
p = strtok(NULL, ",");
if (!p) {
if (debug)
gacopyz_logmsg(SMI_LOG_DEBUG,
_("%s: not a valid X spec"),
spec);
return MI_FAILURE;
}
}
do {
while (*p && isspace(*p))
p++;
if (!*p)
break;
if (p[1] != '=') {
if (debug)
gacopyz_logmsg(SMI_LOG_DEBUG,
_("%s: not a valid X spec, "
"missing = near %s"),
spec, p);
return MI_FAILURE;
}
switch (p[0]) {
case 'S':
if (*port) {
if (debug)
gacopyz_logmsg(SMI_LOG_DEBUG,
_("%s: not a valid X spec, "
"duplicate field %s"),
spec, p);
return MI_FAILURE;
}
*port = p + 2;
break;
case 'F':
if (havef) {
if (debug)
gacopyz_logmsg(SMI_LOG_DEBUG,
_("%s: not a valid X spec, "
"duplicate field %s"),
spec, p);
return MI_FAILURE;
}
havef = 1;
if (strlen (p) != 3) {
if (debug)
gacopyz_logmsg(SMI_LOG_DEBUG,
_("%s: not a valid X spec, "
"invalid field %s"),
spec, p);
return MI_FAILURE;
}
switch (p[2]) {
case 'R':
*onerr = SMFIR_REJECT;
break;
case 'T':
*onerr = SMFIR_TEMPFAIL;
break;
default:
if (debug)
gacopyz_logmsg(SMI_LOG_DEBUG,
_("%s: not a valid X spec, "
"invalid field %s"),
spec, p);
return MI_FAILURE;
}
break;
case 'T':
if (havet) {
if (debug)
gacopyz_logmsg(SMI_LOG_DEBUG,
_("%s: not a valid X spec, "
"duplicate field %s"),
spec, p);
return MI_FAILURE;
}
havet = 1;
for (q = p + 2; *q; q++) {
int n;
unsigned long t;
char *ep;
switch (*q) {
case 'C':
n = GACOPYZ_TO_CONNECT;
break;
case 'S':
n = GACOPYZ_TO_WRITE;
break;
case 'R':
n = GACOPYZ_TO_READ;
break;
case 'E':
n = GACOPYZ_TO_EOM;
break;
default:
if (debug)
gacopyz_logmsg(SMI_LOG_DEBUG,
_("%s: not a valid X spec, "
"invalid T field near %s"),
spec, q);
return MI_FAILURE;
}
if (*++q != ':') {
if (debug)
gacopyz_logmsg(SMI_LOG_DEBUG,
_("%s: not a valid X spec, "
"invalid T field near %s"),
spec, q);
return MI_FAILURE;
}
t = strtoul(++q, &ep, 0);
switch (*ep) {
case 0:
case ';':
break;
case 's':
ep++;
break;
case 'm':
t *= 60;
ep++;
break;
default:
if (debug)
gacopyz_logmsg(SMI_LOG_DEBUG,
_("%s: not a valid X spec, "
"invalid T field near %s"),
spec, q);
return MI_FAILURE;
}
to[n].tv_sec = t;
to[n].tv_usec = 0;
q = ep;
if (*q == 0)
break;
else if (*q != ';') {
if (debug)
gacopyz_logmsg(SMI_LOG_DEBUG,
_("%s: not a valid X spec, "
"invalid delimiter near %s"),
spec, q);
return MI_FAILURE;
}
}
}
} while (p = strtok(NULL, ","));
return MI_SUCCESS;
}
int
gacopyz_srv_create_X(gacopyz_srv_t *p, const char *spec, unsigned logmask)
{
int rc;
char *xspec = strdup(spec);
char *id = "default";
char *port = NULL;
struct timeval to[GACOPYZ_TO_COUNT];
int onerr = SMFIR_CONTINUE;
memcpy(to, default_gacopyz_timeout, sizeof to);
if (parse_X_spec(spec, xspec, &id, &port, to, &onerr,
logmask & SMI_LOG_MASK(SMI_LOG_DEBUG)) == MI_FAILURE) {
free(xspec);
return MI_FAILURE;
}
if (!port) {
if (logmask & SMI_LOG_MASK(SMI_LOG_DEBUG))
gacopyz_logmsg(SMI_LOG_DEBUG,
_("%s: not a valid X spec, "
"missing port"),
spec);
free(xspec);
return MI_FAILURE;
}
rc = gacopyz_srv_create(p, id, port, logmask);
free(xspec);
if (rc)
return MI_FAILURE;
gacopyz_srv_set_all_timeouts(*p, to);
gacopyz_srv_onerror(*p, onerr);
return MI_SUCCESS;
}
int
gacopyz_srv_onerror(gacopyz_srv_t srv, int code)
{
GACOPYZ_ASSERT(srv);
switch (code) {
case SMFIR_CONTINUE:
case SMFIR_DISCARD:
case SMFIR_REJECT:
case SMFIR_TEMPFAIL:
case SMFIR_SHUTDOWN:
srv->onerror = code;
return MI_SUCCESS;
}
return MI_FAILURE;
}
void
gacopyz_srv_set_memerror(gacopyz_srv_t srv,
void (*memerror)(gacopyz_srv_t, const char *,
unsigned int))
{
GACOPYZ_ASSERT(srv);
srv->memerror = memerror;
}
void
gacopyz_srv_set_all_timeouts(gacopyz_srv_t srv, struct timeval *tvp)
{
GACOPYZ_ASSERT(srv);
memcpy(srv->iod.timeout, tvp, sizeof srv->iod.timeout);
}
int
gacopyz_srv_set_timeout(gacopyz_srv_t srv, unsigned n, struct timeval *tvp)
{
GACOPYZ_ASSERT(srv);
if (n >= GACOPYZ_TO_COUNT)
return MI_FAILURE;
srv->iod.timeout[n] = *tvp;
return MI_SUCCESS;
}
int
gacopyz_srv_set_timeout_sec(gacopyz_srv_t srv, unsigned n, time_t t)
{
struct timeval tv;
tv.tv_usec = 0;
tv.tv_sec = t;
return gacopyz_srv_set_timeout(srv, n, &tv);
}
int
gacopyz_srv_set_source(gacopyz_srv_t srv, unsigned long addr)
{
GACOPYZ_ASSERT(srv);
srv->source_addr = htonl(addr);
return MI_SUCCESS;
}
int
gacopyz_srv_set_version(gacopyz_srv_t srv, int version)
{
GACOPYZ_ASSERT(srv);
srv->version = version;
return MI_SUCCESS;
}
int
gacopyz_srv_set_protocol(gacopyz_srv_t srv, int proto)
{
GACOPYZ_ASSERT(srv);
srv->proto = proto;
return MI_SUCCESS;
}
int
gacopyz_srv_set_actions(gacopyz_srv_t srv, int acts)
{
GACOPYZ_ASSERT(srv);
srv->acts = acts;
return MI_SUCCESS;
}
void
gacopyz_srv_destroy(gacopyz_srv_t *p)
{
gacopyz_srv_t srv = *p;
gacopyz_srv_clear_macros(srv);
free(srv->portspec);
free(srv->id);
free(srv->def);
free(srv->buf);
free(srv);
*p = 0;
}
static int
srv_connect(gacopyz_srv_t srv, char *proto, char *port, char *path)
{
union {
struct sockaddr sa;
struct sockaddr_in s_in;
struct sockaddr_un s_un;
} addr;
int socklen;
int fd = -1;
if (!proto
|| strcmp(proto, "unix") == 0 || strcmp(proto, "local") == 0) {
struct stat st;
if (port) {
gacopyz_io_log(&srv->iod, SMI_LOG_ERR,
_("Invalid connection type: %s; "
"port is meaningless for UNIX sockets"),
srv->portspec);
return -1;
}
if (strlen(path) > sizeof addr.s_un.sun_path) {
errno = EINVAL;
gacopyz_io_log(&srv->iod, SMI_LOG_ERR,
_("%s: UNIX socket name too long"),
path);
return -1;
}
addr.sa.sa_family = PF_UNIX;
socklen = sizeof(addr.s_un);
strcpy(addr.s_un.sun_path, path);
if (stat(path, &st)) {
gacopyz_io_log(&srv->iod, SMI_LOG_ERR,
_("%s: cannot stat socket: %s"),
path, strerror(errno));
return -1;
} else {
/* FIXME: Check permissions? */
if (!S_ISSOCK(st.st_mode)) {
gacopyz_io_log(&srv->iod, SMI_LOG_ERR,
_("%s: not a socket"),
path);
return -1;
}
}
} else if (strcmp(proto, "inet") == 0) {
short pnum;
long num;
char *p;
addr.sa.sa_family = PF_INET;
socklen = sizeof(addr.s_in);
if (!port) {
gacopyz_io_log(&srv->iod, SMI_LOG_ERR,
_("Invalid connection type: %s; "
"missing port number"),
srv->portspec);
return -1;
}
num = pnum = strtol(port, &p, 0);
if (*p == 0) {
if (num != pnum) {
gacopyz_io_log(&srv->iod, SMI_LOG_ERR,
_("Invalid connection type: %s; "
"bad port number"),
srv->portspec);
return -1;
}
pnum = htons(pnum);
} else {
struct servent *sp = getservbyname(path, "tcp");
if (!sp) {
gacopyz_io_log(&srv->iod, SMI_LOG_ERR,
_("Invalid connection type: %s; "
"unknown port name"),
srv->portspec);
return -1;
}
pnum = sp->s_port;
}
if (!path)
addr.s_in.sin_addr.s_addr = INADDR_ANY;
else {
struct hostent *hp = gethostbyname(path);
if (!hp) {
gacopyz_io_log(&srv->iod, SMI_LOG_ERR,
_("Unknown host name %s"),
path);
return -1;
}
addr.sa.sa_family = hp->h_addrtype;
switch (hp->h_addrtype) {
case AF_INET:
memmove(&addr.s_in.sin_addr, hp->h_addr, 4);
addr.s_in.sin_port = pnum;
break;
default:
gacopyz_io_log(&srv->iod, SMI_LOG_ERR,
_("Invalid connection type: %s; "
"unsupported address family"),
srv->portspec);
return -1;
}
}
} else {
gacopyz_io_log(&srv->iod, SMI_LOG_ERR,
_("Unsupported protocol: %s"),
proto);
return -1;
}
fd = socket(addr.sa.sa_family, SOCK_STREAM, 0);
if (fd == -1) {
gacopyz_io_log(&srv->iod, SMI_LOG_ERR,
_("Unable to create new socket: %s"),
strerror(errno));
return -1;
}
if (addr.sa.sa_family == PF_INET) {
struct sockaddr_in s;
s.sin_family = AF_INET;
s.sin_addr.s_addr = srv->source_addr;
s.sin_port = 0;
if (bind (fd, (struct sockaddr*) &s, sizeof(s)) < 0) {
gacopyz_io_log(&srv->iod, SMI_LOG_ERR,
_("Cannot bind to local address: %s"),
strerror(errno));
close(fd);
return -1;
}
}
if (connect(fd, &addr.sa, socklen)) {
gacopyz_io_log(&srv->iod, SMI_LOG_ERR,
_("Cannot connect to %s: %s"),
srv->portspec, strerror(errno));
close(fd);
return -1;
}
return fd;
}
int
gacopyz_srv_connect(gacopyz_srv_t srv)
{
char *proto;
char *port;
char *path;
int rc;
GACOPYZ_ASSERT(srv);
gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG,
_("Conecting to %s"), srv->portspec);
GACOPYZ_ALLOC(srv,
gacopyz_parse_connection(srv->portspec,
&proto, &port, &path)==0);
srv->iod.sd = srv_connect(srv, proto, port, path);
free(proto);
free(port);
free(path);
if (srv->iod.sd == -1)
return MI_FAILURE;
srv->state = srv_connected;
return MI_SUCCESS;
}
int
gacopyz_srv_negotiate(gacopyz_srv_t srv)
{
gacopyz_uint32_t ibuf[3];
size_t size;
unsigned char cmd;
GACOPYZ_ASSERT(srv);
ibuf[0] = htonl(srv->version);
ibuf[1] = htonl(srv->acts);
ibuf[2] = htonl(srv->proto);
if (gacopyz_send_command(&srv->iod, SMFIC_OPTNEG, ibuf, sizeof ibuf))
return MI_FAILURE;
if (gacopyz_read_command(&srv->iod, &cmd, &size,
&srv->buf, &srv->bufsize))
return MI_FAILURE;
if (cmd != SMFIC_OPTNEG) {
gacopyz_io_log(&srv->iod, SMI_LOG_ERR,
_("gacopyz_srv_negotiate: got %c instead of %c"),
cmd, SMFIC_OPTNEG);
return MI_FAILURE;
}
if (size < sizeof ibuf) {
gacopyz_io_log(&srv->iod, SMI_LOG_ERR,
_("gacopyz_srv_negotiate: not enough data returned"));
return MI_FAILURE;
}
ibuf[0] = ntohl(((gacopyz_uint32_t*)srv->buf)[0]);
ibuf[1] = ntohl(((gacopyz_uint32_t*)srv->buf)[1]);
ibuf[2] = ntohl(((gacopyz_uint32_t*)srv->buf)[2]);
/* Check version */
if (ibuf[0] == 1 || ibuf[0] > SMFI_VERSION) {
gacopyz_io_log(&srv->iod, SMI_LOG_ERR,
_("Client requested unsupported milter version: "
"%d"), ibuf[0]);
return MI_FAILURE;
}
/* Check action capabilities */
if ((ibuf[1] & SMFI_DEFAULT_ACTS) != ibuf[1]) {
gacopyz_io_log(&srv->iod, SMI_LOG_ERR,
_("Filter abilities %#x do not match MTA "
"milter abilities %#lx"),
ibuf[1], SMFI_DEFAULT_ACTS);
return MI_FAILURE;
}
/* Check protocol */
if ((ibuf[2] & SMFI_DEFAULT_PROT) != ibuf[2]) {
gacopyz_io_log(&srv->iod, SMI_LOG_ERR,
_("Protocol abilities %#x do not match MTA "
"milter abilities %#lx"),
ibuf[1], SMFI_DEFAULT_PROT);
return MI_FAILURE;
}
srv->version = ibuf[0];
srv->acts = ibuf[1];
srv->proto = ibuf[2];
srv->state = srv_ready;
return MI_SUCCESS;
}
static struct command_assoc {
unsigned char cmd;
int pflag;
const char *action;
const char *code;
} command_tab[] = {
{ SMFIC_CONNECT, SMFIP_NOCONNECT, "connect", "554" },
{ SMFIC_HELO, SMFIP_NOHELO, "helo", "550" },
{ SMFIC_MAIL, SMFIP_NOMAIL, "mail", "550 5.7.1" },
{ SMFIC_RCPT, SMFIP_NORCPT, "rcpt", "550 5.7.1" },
{ SMFIC_HEADER, SMFIP_NOHDRS, "header", "550 5.7.1" },
{ SMFIC_BODY, SMFIP_NOBODY, "body", "554 5.7.1" },
{ SMFIC_EOH, SMFIP_NOEOH, "eoh", "550 5.7.1" },
{ SMFIC_UNKNOWN, SMFIP_NOUNKNOWN, "unknown", "550 5.7.1" },
{ SMFIC_DATA, SMFIP_NODATA, "data", "550 5.7.1" },
{ 0, 0, "default", "550 5.7.1" }
};
static struct command_assoc *
find_cmd_assoc(unsigned char cmd)
{
struct command_assoc *cp;
for (cp = command_tab; cp->cmd; cp++)
if (cp->cmd == cmd)
break;
return cp;
}
#define is_rfc822_reject_code(s) \
(((s)[0] == '4' || (s)[0] == '5') \
&& isascii((s)[1]) && isdigit((s)[1]) \
&& isascii((s)[2]) && isdigit((s)[2]))
#define DEFREPLY "Command rejected"
static void
verify_reply(gacopyz_srv_t srv, size_t size, const char *reject_code)
{
if (size < 3 || !is_rfc822_reject_code(srv->buf)) {
srv_ensure_space(srv,
strlen(reject_code) + 1
+ sizeof(DEFREPLY));
strcpy(srv->buf, reject_code);
strcat(srv->buf, " ");
strcat(srv->buf, DEFREPLY);
}
}
int
gacopyz_srv_send_macros(gacopyz_srv_t srv, unsigned char cmd)
{
size_t size;
srv_format_macros(srv, cmd, &size);
if (gacopyz_send_command(&srv->iod, SMFIC_MACRO,
srv->buf, size))
return MI_FAILURE;
return MI_SUCCESS;
}
int
gacopyz_srv_send_command(gacopyz_srv_t srv, unsigned char cmd,
const void *data, size_t datasize)
{
unsigned char rcmd;
size_t size;
struct command_assoc *cp;
gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG,
_("gacopyz_srv_send_command: size=%lu, cmd=%c"),
datasize, cmd);
cp = find_cmd_assoc(cmd);
srv->resp_ptr = NULL;
srv->resp_size = 0;
if (cp->pflag && (srv->proto & cp->pflag)) {
if (cmd == SMFIC_MAIL)
srv->state = srv_msgproc;
return SMFIR_CONTINUE;
}
if (gacopyz_srv_send_macros(srv, cmd))
return srv->onerror;
if (gacopyz_send_command(&srv->iod, cmd, data, datasize))
return srv->onerror;
if (cmd == SMFIC_HEADER && (srv->proto & SMFIP_NOHREPL))
return SMFIR_CONTINUE;
if (gacopyz_read_command(&srv->iod, &rcmd, &size,
&srv->buf, &srv->bufsize))
return srv->onerror;
switch (rcmd) {
case SMFIR_REPLYCODE:
verify_reply(srv, size, cp->code);
gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG,
"action=%s, reject=%s",
cp->action, srv->buf);
srv->resp_ptr = srv->buf;
srv->resp_size = size;
srv->state = srv_disabled;
break;
case SMFIR_REJECT:
gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG,
"action=%s, reject",
cp->action);
break;
case SMFIR_DISCARD:
gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG,
"action=%s, discard",
cp->action);
srv->state = srv_disabled;
break;
case SMFIR_TEMPFAIL:
gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG,
"action=%s, tempfail",
cp->action);
break;
case SMFIR_ACCEPT:
gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG,
"action=%s, accept",
cp->action);
srv->state = srv_disabled;
break;
case SMFIR_CONTINUE:
gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG,
"action=%s, continue",
cp->action);
if (cmd == SMFIC_MAIL)
srv->state = srv_msgproc;
break;
default:
gacopyz_io_log(&srv->iod, SMI_LOG_ERR,
"action=%s, bogus response=%c",
cp->action, rcmd);
/*FIXME: Change to error state */
srv->state = srv_error;
rcmd = srv->onerror;
break;
}
return rcmd;
}
void
gacopyz_srv_reply (gacopyz_srv_t srv, char **msg, size_t *size)
{
*msg = srv->resp_ptr;
*size = srv->resp_size;
}
int
gacopyz_srv_abort(gacopyz_srv_t srv)
{
GACOPYZ_ASSERT(srv);
gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG, "gacopyz_srv_abort");
return gacopyz_send_command(&srv->iod, SMFIC_ABORT, NULL, 0);
}
int
gacopyz_srv_quit(gacopyz_srv_t srv)
{
GACOPYZ_ASSERT(srv);
gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG, "gacopyz_srv_quit");
return gacopyz_send_command(&srv->iod, SMFIC_QUIT, NULL, 0);
}
int
gacopyz_srv_helo(gacopyz_srv_t srv, const char *domain)
{
int rc;
GACOPYZ_ASSERT(srv);
if (srv->proto & SMFIP_NOHELO)
rc = SMFIR_CONTINUE;
else
rc = gacopyz_srv_send_command(srv, SMFIC_HELO,
domain, strlen(domain) + 1);
return rc;
}
static void
format_argv(gacopyz_srv_t srv, char **argv, char **buf, size_t *psize)
{
size_t len;
char **ap;
char *p;
for (len = 1, ap = argv; *ap; ap++)
len += strlen(*ap) + 1;
GACOPYZ_ALLOC(srv, *buf = malloc(len));
for (p = *buf, ap = argv; *ap; ap++) {
size_t len = strlen(*ap) + 1;
memcpy(p, *ap, len);
p += len;
}
*p = 0;
*psize = len;
}
int
gacopyz_srv_envfrom(gacopyz_srv_t srv, char **argv)
{
int rc;
GACOPYZ_ASSERT(srv);
if (srv->state != srv_ready) {
gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG,
_("%s: ignoring request; called in wrong state (%d)"),
"gacopyz_srv_envfrom",
srv->state);
rc = SMFIR_CONTINUE;
} else {
size_t size;
char *buf;
format_argv(srv, argv, &buf, &size);
rc = gacopyz_srv_send_command(srv, SMFIC_MAIL, buf, size);
free (buf);
}
return rc;
}
int
gacopyz_srv_envrcpt(gacopyz_srv_t srv, char **argv)
{
int rc;
GACOPYZ_ASSERT(srv);
if (srv->state != srv_msgproc) {
gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG,
_("%s: ignoring request; called in wrong state (%d)"),
"gacopyz_srv_envrcpt",
srv->state);
rc = SMFIR_CONTINUE;
} else {
size_t size;
char *buf;
format_argv(srv, argv, &buf, &size);
rc = gacopyz_srv_send_command(srv, SMFIC_RCPT, buf, size);
free (buf);
}
return rc;
}
int
gacopyz_srv_header(gacopyz_srv_t srv, char *name, char *value)
{
int rc;
GACOPYZ_ASSERT(srv && name && value);
if (srv->state != srv_msgproc) {
gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG,
_("%s: ignoring request; called in wrong state (%d)"),
"gacopyz_srv_header",
srv->state);
rc = SMFIR_CONTINUE;
} else {
size_t nlen, vlen, size;
char *buf;
nlen = strlen (name);
vlen = strlen (value);
size = nlen + vlen + 2;
GACOPYZ_ALLOC(srv, buf = malloc (size));
memcpy (buf, name, nlen + 1);
memcpy (buf + nlen + 1, value, vlen + 1);
rc = gacopyz_srv_send_command(srv, SMFIC_HEADER, buf, size);
free (buf);
}
return rc;
}
int
gacopyz_srv_eoh(gacopyz_srv_t srv)
{
int rc;
GACOPYZ_ASSERT(srv);
if (srv->state != srv_msgproc) {
gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG,
_("%s: ignoring request; called in wrong state (%d)"),
"gacopyz_srv_eoh",
srv->state);
rc = SMFIR_CONTINUE;
} else
rc = gacopyz_srv_send_command(srv, SMFIC_EOH, NULL, 0);
return rc;
}
int
gacopyz_srv_body(gacopyz_srv_t srv, unsigned char *str, size_t size)
{
int rc;
GACOPYZ_ASSERT(srv);
if (srv->state != srv_msgproc) {
gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG,
_("%s: ignoring request; called in wrong state (%d)"),
"gacopyz_srv_body",
srv->state);
rc = SMFIR_CONTINUE;
} else
rc = gacopyz_srv_send_command(srv, SMFIC_BODY, str, size);
return rc;
}
int
gacopyz_srv_eom(gacopyz_srv_t srv, unsigned char *chunk, size_t size)
{
GACOPYZ_ASSERT(srv);
if (srv->state != srv_msgproc) {
gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG,
_("%s: ignoring request; called in wrong state (%d)"),
"gacopyz_srv_body",
srv->state);
srv->state = srv_done;
return SMFIR_CONTINUE;
}
return gacopyz_srv_send_command(srv, SMFIC_BODYEOB, chunk, size);
}
int
gacopyz_srv_close(gacopyz_srv_t srv)
{
GACOPYZ_ASSERT(srv);
close (srv->iod.sd);
srv->state = srv_init;
return MI_SUCCESS;
}
int
gacopyz_srv_data(gacopyz_srv_t srv)
{
GACOPYZ_ASSERT(srv);
return gacopyz_srv_send_command(srv, SMFIC_DATA, NULL, 0);
}