diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2006-11-03 12:58:18 +0000 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2006-11-03 12:58:18 +0000 |
commit | e093c2f4719787936463ae2a38ccf1259d6ca644 (patch) | |
tree | 3fe3393607ef7999ab846e06ded76c8fb2196828 /gacopyz | |
parent | a45d85f2b8ecf06f2ad23136f58619c138b57606 (diff) | |
download | mailfromd-e093c2f4719787936463ae2a38ccf1259d6ca644.tar.gz mailfromd-e093c2f4719787936463ae2a38ccf1259d6ca644.tar.bz2 |
See /libmilter for the previous history
git-svn-id: file:///svnroot/mailfromd/trunk@747 7a8a7f39-df28-0410-adc6-e0d955640f24
Diffstat (limited to 'gacopyz')
-rw-r--r-- | gacopyz/Makefile.am | 43 | ||||
-rw-r--r-- | gacopyz/context.c | 285 | ||||
-rw-r--r-- | gacopyz/dummy.c | 114 | ||||
-rw-r--r-- | gacopyz/gacopyz.c | 1559 | ||||
-rw-r--r-- | gacopyz/gacopyz.h | 295 | ||||
-rw-r--r-- | gacopyz/gacopyz_priv.h | 66 | ||||
-rw-r--r-- | gacopyz/log.c | 121 | ||||
-rw-r--r-- | gacopyz/mfapi.h | 4 | ||||
-rw-r--r-- | gacopyz/proc.c | 154 | ||||
-rw-r--r-- | gacopyz/smfi.c | 186 | ||||
-rw-r--r-- | gacopyz/trans.awk | 75 | ||||
-rw-r--r-- | gacopyz/trans.tab | 93 |
12 files changed, 2995 insertions, 0 deletions
diff --git a/gacopyz/Makefile.am b/gacopyz/Makefile.am new file mode 100644 index 00000000..12e1cba6 --- /dev/null +++ b/gacopyz/Makefile.am @@ -0,0 +1,43 @@ +# This file is part of mailfrom filter. +# Copyright (C) 2006 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 2, 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, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301 USA + +noinst_LIBRARIES = @BUILD_LIBGACOPYZ_A@ +noinst_PROGRAMS = @BUILD_DUMMY@ +EXTRA_LIBRARIES=libgacopyz.a +EXTRA_PROGRAMS=dummy + +libgacopyz_a_SOURCES = \ + smfi.c\ + gacopyz.c\ + gacopyz.h\ + gacopyz_priv.h\ + context.c\ + log.c\ + mfapi.h\ + proc.c\ + trans.h + +dummy_SOURCES = dummy.c +dummy_LDADD = ./libgacopyz.a + +EXTRA_DIST=trans.tab trans.awk +BUILT_SOURCES=trans.h + +trans.h: ${top_srcdir}/libmilter/trans.tab ${top_srcdir}/libmilter/trans.awk + $(AWK) -f ${top_srcdir}/libmilter/trans.awk \ + ${top_srcdir}/libmilter/trans.tab > trans.h
\ No newline at end of file diff --git a/gacopyz/context.c b/gacopyz/context.c new file mode 100644 index 00000000..95e511a9 --- /dev/null +++ b/gacopyz/context.c @@ -0,0 +1,285 @@ +/* This file is part of gacopyz. + Copyright (C) 2006 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 2, 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, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301 USA */ + +#include <gacopyz_priv.h> + +int +smfi_setpriv (SMFICTX *ctx, void *data) +{ + if (!ctx) + return MI_FAILURE; + ctx->privdata = data; + return MI_SUCCESS; +} + +void * +smfi_getpriv (SMFICTX *ctx) +{ + if (!ctx) + return NULL; + return ctx->privdata; +} + +char * +gacopyz_getsymval(SMFICTX *ctx, char *name) +{ + int i; + int len; + + if (!ctx || !name) + return NULL; + + if (name[0] == '{') + name++; + len = strlen(name); + if (len == 0) + return NULL; + if (name[len-1] == '}') + len--; + + for (i = maci_max - 1; i >= 0; i--) { + if (ctx->macros[i].argv) { + char **p; + + for (p = ctx->macros[i].argv; *p; p += 2) { + if (strlen(*p) == len + && memcmp(*p, name, len) == 0) + return *++p; + } + } + } + return NULL; +} + +static int +enhanced_code_p(const char *p) +{ + int count = 0; + + if (!((*p == '2' || *p == '4' || *p == '5') && p[1] == '.')) + return 0; + + while (*p) { + int i; + for (i = 0; isascii(*p) && isdigit(*p); i++, p++) { + if (i == 3) + return 0; + } + if (*p) { + if (*p++ != '.') + return 0; + } + count++; + } + return count == 3; +} + +static int +format_message(char **buf, const char *rcode, const char *xcode, + const char *message) +{ + size_t xcodelen, pfxlen, len, numlines, i; + const char *p; + char *q; + + pfxlen = strlen(rcode) + 1; + if (xcode != NULL) + pfxlen += (xcodelen = strlen(xcode)) + 1; + + numlines = 0; + len = 0; + for (p = message; *p; p++) { + if (*p == '\r') { + if (p[1] == '\n') + p++; + numlines++; + } else if (*p == '\n') + numlines++; + else + len++; + } + + if (p > message && p[-1] != '\n') + numlines++; + + len += numlines * (pfxlen + 2); + + *buf = malloc(len + 1); + if (!*buf) + return MI_FAILURE; + + q = *buf; + p = message; + for (i = 1; i <= numlines; i++) { + strcpy(q, rcode); + q += 3; + if (xcode) { + *q++ = (i < numlines) ? '-' : ' '; + memcpy(q, xcode, xcodelen); + q += xcodelen; + *q++ = ' '; + } else + *q++ = (i < numlines) ? '-' : ' '; + while (*p && !(*p == '\r' || *p == '\n')) + *q++ = *p++; + if (numlines > 1 && i != numlines) { + *q++ = '\r'; + *q++ = '\n'; + } + if (*p == '\r') + p++; + if (*p == '\n') + p++; + } + *q = 0; + return MI_SUCCESS; +} + +#define MLBUF_INIT_ALLOC 512 +#define MLBUF_INCR_ALLOC 128 +int +_gacopyz_setmlreply_va(SMFICTX *ctx, size_t max, const char *rcode, + const char *xcode, va_list ap) +{ + size_t bufsize = MLBUF_INIT_ALLOC, i, lasti, numlines; + char *buf, *p; + size_t xcodelen, pfxlen; + + if (rcode == NULL || ctx == NULL) + return MI_FAILURE; + + if ((rcode[0] != '4' && rcode[0] != '5') || + !isascii(rcode[1]) || !isdigit(rcode[1]) || + !isascii(rcode[2]) || !isdigit(rcode[2])) + return MI_FAILURE; + + pfxlen = strlen(rcode); + + if (strlen(rcode) != 3) + return MI_FAILURE; + + if (xcode != NULL) { + if (!enhanced_code_p(xcode)) + return MI_FAILURE; + } else { + if (rcode[0] == '4') + xcode = "4.0.0"; + else + xcode = "5.0.0"; + } + if (xcode != NULL) + pfxlen += (xcodelen = strlen(xcode)) + 1; + + free(ctx->reply); + ctx->reply = NULL; + buf = malloc(bufsize); + if (!buf) + return MI_FAILURE; + i = 0; + lasti = 0; + numlines = 0; + for (p = va_arg(ap, char*); + p && (max == 0 || numlines < max); numlines++) { + size_t s = strlen(p) + pfxlen + 2; + if (strpbrk(p, "\r\n") != NULL) + break; + if (i + s > bufsize) { + char *newbuf; + size_t delta = (i + s - bufsize + + MLBUF_INCR_ALLOC) / + MLBUF_INCR_ALLOC; + bufsize += delta * MLBUF_INCR_ALLOC; + newbuf = realloc(buf, bufsize); + if (!newbuf) { + free(buf); + return MI_FAILURE; + } + buf = newbuf; + } + + strcpy(buf + i, rcode); + i += 3; + buf[i] = '-'; + lasti= i++; + memcpy(buf + i, xcode, xcodelen); + i += xcodelen; + buf[i++] = ' '; + strcpy(buf + i, p); + i += strlen(p); + + p = va_arg(ap, char*); + if (p) { + buf[i++] = '\r'; + buf[i++] = '\n'; + } + } + buf[i] = 0; + buf[lasti] = ' '; + + ctx->reply = buf; + + return MI_SUCCESS; +} + +int +gacopyz_setreply(SMFICTX *ctx, const char *rcode, const char *xcode, + const char *message) +{ + if (rcode == NULL || ctx == NULL) + return MI_FAILURE; + + if ((rcode[0] != '4' && rcode[0] != '5') || + !isascii(rcode[1]) || !isdigit(rcode[1]) || + !isascii(rcode[2]) || !isdigit(rcode[2])) + return MI_FAILURE; + + if (strlen(rcode) != 3) + return MI_FAILURE; + + if (xcode != NULL) { + if (!enhanced_code_p(xcode)) + return MI_FAILURE; + } + + free(ctx->reply); + ctx->reply = NULL; + return format_message(&ctx->reply, rcode, xcode, message); +} + + +int +gacopyz_setmlreply_va(SMFICTX *ctx, const char *rcode, const char *xcode, + va_list ap) +{ + return _gacopyz_setmlreply_va(ctx, 0, rcode, xcode, ap); + return MI_SUCCESS; +} + +int +gacopyz_setmlreply_v(SMFICTX *ctx, const char *rcode, const char *xcode, ...) +{ + int rc; + va_list ap; + + va_start(ap, xcode); + rc = gacopyz_setmlreply_va(ctx, rcode, xcode, ap); + va_end(ap); + + return MI_SUCCESS; +} + diff --git a/gacopyz/dummy.c b/gacopyz/dummy.c new file mode 100644 index 00000000..01c8eef5 --- /dev/null +++ b/gacopyz/dummy.c @@ -0,0 +1,114 @@ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#include <sys/types.h> +#include <sys/time.h> +#include <sys/un.h> +#include <netinet/in.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include "mfapi.h" + +int +dummy_idle (gacopyz_conn_t conn) +{ + printf("IDLING\n"); + return 0; +} + +sfsistat +dummy_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr) +{ + printf("CONNECT FROM: %s, family: %d\n", hostname, + hostaddr ? hostaddr->sa.sa_family : -1); + return SMFIS_CONTINUE; +} + +sfsistat +dummy_envfrom(SMFICTX *ctx, char **argv) +{ + return SMFIS_CONTINUE; +} + +sfsistat +dummy_eom(SMFICTX *ctx) +{ + static char newbody[] = "Zawartosc wiadomosci zostala usunieta\n" + "wylacznie ze zlej woli administratora systemu\n" + "--\n" + "Bwana Budu\n"; + smfi_addheader(ctx, "X-Been-Here", "Gacopyz"); + smfi_insheader(ctx, 1, "X-New-Header", "nowy"); + smfi_chgheader(ctx, "X-Duplicate-Header", 1, NULL); + smfi_addrcpt(ctx, "gray@gnu.org.ua"); +#if 0 + smfi_delrcpt(ctx, "<gray@localhost>"); + smfi_delrcpt(ctx, "gray"); +#endif + smfi_replacebody(ctx, newbody, sizeof newbody); + return SMFIS_CONTINUE; +} + +struct smfiDesc smfilter = +{ + "DummyFilter", + SMFI_VERSION, + SMFIF_ADDHDRS|SMFIF_CHGHDRS|SMFIF_ADDRCPT|SMFIF_DELRCPT| + SMFIF_CHGBODY|SMFIF_QUARANTINE, + dummy_connect, /* connection info filter */ + NULL, /* SMTP HELO command filter */ + dummy_envfrom, /* envelope sender filter */ + NULL, /* envelope recipient filter */ + NULL, /* header filter */ + NULL, /* end of header */ + NULL, /* body block filter */ + dummy_eom, /* end of message */ + NULL, /* message aborted */ + NULL, /* connection cleanup */ + NULL, /* unknown command handler */ + NULL, /* data handler */ + NULL, /* child start */ + NULL, /* child finish */ + NULL /* idle callback */ +}; + +char *level_name[] = { + "DEBUG", + "INFO", + "WARN", + "ERR", + "FATAL" +}; + +static void +stderr_log_printer(int level, char *fmt, va_list ap) +{ + fprintf(stderr, "%s: ", level_name[level]); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); +} + +int +main(int argc, char **argv) +{ + if (!argv[1]) { + fprintf(stderr, "usage: %s portspec\n", argv[0]); + exit(1); + } + + gacopyz_set_logger(stderr_log_printer); + smfi_setdbg(1); + + if (smfi_setconn(argv[1]) == MI_FAILURE) { + fprintf(stderr, "smfi_setconn: %s\n", strerror(errno)); + exit(1); + } + + if (smfi_register(smfilter) == MI_FAILURE) { + fprintf(stderr, "smfi_register failed\n"); + exit(1); + } + smfi_opensocket(1); + return smfi_main(); +} diff --git a/gacopyz/gacopyz.c b/gacopyz/gacopyz.c new file mode 100644 index 00000000..7466bffe --- /dev/null +++ b/gacopyz/gacopyz.c @@ -0,0 +1,1559 @@ +/* This file is part of gacopyz. + Copyright (C) 2006 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 2, 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, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301 USA */ + +#include <gacopyz_priv.h> + +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_logmsg(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_logmsg(SMI_LOG_ERR, + "smfi_register: %s: not enough memory", name); + return MI_FAILURE; + } + + memset(conn, 0, sizeof(*conn)); + conn->sd = -1; + conn->master_timeout.tv_usec = 0; + conn->master_timeout.tv_sec = 5; + conn->ctx_timeout.tv_usec = 0; + conn->ctx_timeout.tv_sec = MI_TIMEOUT; + conn->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) +{ + free(conn); +} + +static int +parse_connection(gacopyz_conn_t conn, + const char *cstr, + char **pproto, char **pport, char **ppath) +{ + const char *p; + size_t len; + + p = strchr(cstr, ':'); + if (!p) + *pproto = NULL; + else { + len = p - cstr; + *pproto = malloc(len + 1); + if (!*pproto) { + gacopyz_log(conn, SMI_LOG_ERR, + "parse_connection: not enough memory"); + return MI_FAILURE; + } + memcpy(*pproto, cstr, len); + (*pproto)[len] = 0; + + cstr = p + 1; + } + + p = strchr(cstr, '@'); + if (!p) + *pport = NULL; + else { + len = p - cstr; + *pport = malloc(len + 1); + if (!*pport) { + free(*pproto); + gacopyz_log(conn, SMI_LOG_ERR, + "parse_connection: not enough memory"); + return MI_FAILURE; + } + memcpy(*pport, cstr, len); + (*pport)[len] = 0; + + cstr = p + 1; + } + + *ppath = strdup(cstr); + if (!*ppath) { + free(*pproto); + free(*pport); + free(*ppath); + gacopyz_log(conn, SMI_LOG_ERR, + "parse_connection: not enough memory"); + return MI_FAILURE; + } + return MI_SUCCESS; +} + +static int +do_connect(gacopyz_conn_t conn, + const char *cstr, char *proto, char *port, char *path, + int backlog, int rmsocket) +{ + union { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_un sun; + } addr; + int socklen; + int fd, flags; + int yes = 1; + + if (!proto + || strcmp(proto, "unix") == 0 || strcmp(proto, "local") == 0) { + struct stat st; + + if (port) { + gacopyz_log(conn, 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.sun.sun_path) { + errno = EINVAL; + gacopyz_log(conn, 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.sun); + strcpy(addr.sun.sun_path, path); + + if (stat(path, &st)) { + if (errno != ENOENT) { + gacopyz_log(conn, 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)) { + gacopyz_log(conn, SMI_LOG_ERR, + "%s: %s: not a socket", + conn->desc.xxfi_name, path); + return -1; + } + if (rmsocket && unlink(path)) { + gacopyz_log(conn, 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) { + gacopyz_log(conn, 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) { + gacopyz_log(conn, 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(path, "tcp"); + if (!sp) { + gacopyz_log(conn, 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) { + gacopyz_log(conn, 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: + gacopyz_log(conn, SMI_LOG_ERR, + "%s: invalid connection type: %s; " + "unsupported address family", + conn->desc.xxfi_name, cstr); + return -1; + } + } + } + + fd = socket(addr.sa.sa_family, SOCK_STREAM, 0); + if (fd == -1) { + gacopyz_log(conn, 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) { + gacopyz_log(conn, 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) { + gacopyz_log(conn, SMI_LOG_ERR, + "%s: set reuseaddr failed (%s)", + conn->desc.xxfi_name, strerror(errno)); + close(fd); + return -1; + } + + if (bind(fd, &addr.sa, socklen) < 0) { + gacopyz_log(conn, 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)) { + gacopyz_log(conn, 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_logmsg(SMI_LOG_ERR, + "empty or missing socket information"); + errno = EINVAL; + return MI_FAILURE; + } + + gacopyz_log(conn, 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->logmask; + return MI_SUCCESS; +} + +int +gacopyz_set_logmask(gacopyz_conn_t conn, int mask) +{ + if (!conn) + return MI_FAILURE; + conn->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->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->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; + } +} + +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->conn->ctx_timeout; + + res = select(ctx->sd + 1, &rset, NULL, &xset, &to); + if (res == 0) { + errno = ETIMEDOUT; + continue; + } else if (res < 0) { + if (errno == EINTR) + continue; + rc = MI_FAILURE; + break; + } else if (rc > 0) { + if (FD_ISSET(ctx->sd, &xset)) { + gacopyz_log(ctx->conn, + SMI_LOG_ERR, + "ctx_read: exception on control fd"); + rc = MI_FAILURE; + break; + } + /* Otherwise, FD_ISSET(ctx->sd, &rset) is true */ + } + + res = read(ctx->sd, buf, size); + if (res == -1) { + gacopyz_log(ctx->conn, + SMI_LOG_ERR, + "read failed: %s", + strerror(errno)); + rc = MI_FAILURE; + break; + } + + buf += res; + size -= res; + } + return rc; +} + +static int +ctx_write(SMFICTX *ctx, 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->conn->ctx_timeout; + + res = select(ctx->sd + 1, NULL, &wset, &xset, &to); + if (res == 0) { + errno = ETIMEDOUT; + continue; + } else if (res < 0) { + if (errno == EINTR) + continue; + rc = MI_FAILURE; + break; + } else if (rc > 0) { + if (FD_ISSET(ctx->sd, &xset)) { + gacopyz_log(ctx->conn, + SMI_LOG_ERR, + "ctx_write: exception on control fd"); + rc = MI_FAILURE; + break; + } + /* Otherwise, FD_ISSET(ctx->sd, &wset) is true */ + } + + res = write(ctx->sd, buf, size); + if (res == -1) { + gacopyz_log(ctx->conn, + SMI_LOG_ERR, + "write failed: %s", + strerror(errno)); + rc = MI_FAILURE; + break; + } + + buf += res; + size -= res; + } + return rc; +} + + +union header { + struct { + mi_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) { + gacopyz_log(ctx->conn, + SMI_LOG_ERR, "not enough memory"); + return MI_FAILURE; + } + *pbuf = p; + *psize = size + 1; + } + + gacopyz_log(ctx->conn, 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; + + gacopyz_logdump(ctx->conn, SMI_LOG_DEBUG, + "read data", *pbuf, size); + *pcount = size; + *cmd = header.hdr.cmd; + return MI_SUCCESS; +} + +static int +send_reply(SMFICTX *ctx, unsigned char cmd) +{ + int rc; + union header header; + char *buf = NULL; + size_t bufsize = 0; + + switch (cmd) { + 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: + { + mi_uint32_t v[3]; + v[0] = htonl(ctx->conn->desc.xxfi_version); + v[1] = htonl(ctx->conn->desc.xxfi_flags); + v[2] = htonl(ctx->flags); + buf = (char*) v; + bufsize = sizeof v; + break; + } + + default: /* Ignore */ + break; + } + + header.hdr.size = htonl(bufsize + 1); + header.hdr.cmd = cmd; + rc = ctx_write(ctx, header.buf, sizeof header.buf); + if (rc != MI_SUCCESS) + return rc; + if (bufsize) + rc = ctx_write(ctx, buf, bufsize); + return rc; +} + +static void +macro_assoc_free(macro_assoc_t *p) +{ + free(p->argv); + free(p->buffer); +} + +static void +clear_macros(SMFICTX *ctx, int i) +{ + for (; i < maci_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]; + mi_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 unsigned char +convert_sfsistat(sfsistat stat) +{ + switch (stat) { + case SMFIS_CONTINUE: + return SMFIR_CONTINUE; + case SMFIS_REJECT: + return SMFIR_REJECT; + case SMFIS_DISCARD: + re |