/* 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; }