diff options
Diffstat (limited to 'contrib/sendmail/src/milter.c')
-rw-r--r-- | contrib/sendmail/src/milter.c | 3402 |
1 files changed, 3402 insertions, 0 deletions
diff --git a/contrib/sendmail/src/milter.c b/contrib/sendmail/src/milter.c new file mode 100644 index 0000000..82f1574 --- /dev/null +++ b/contrib/sendmail/src/milter.c @@ -0,0 +1,3402 @@ +/* + * Copyright (c) 1999-2000 Sendmail, Inc. and its suppliers. + * All rights reserved. + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the sendmail distribution. + * + */ + +#ifndef lint +static char id[] = "@(#)$Id: milter.c,v 8.50.4.30 2000/07/18 07:24:51 gshapiro Exp $"; +#endif /* ! lint */ + +#if _FFR_MILTER + +# include <sendmail.h> +# include <errno.h> +# include <sys/time.h> + +# if NETINET || NETINET6 +# include <arpa/inet.h> +# endif /* NETINET || NETINET6 */ + + +static void milter_error __P((struct milter *)); +static int milter_open __P((struct milter *, bool, ENVELOPE *)); +static void milter_parse_timeouts __P((char *, struct milter *)); + +static char *MilterConnectMacros[MAXFILTERMACROS + 1]; +static char *MilterHeloMacros[MAXFILTERMACROS + 1]; +static char *MilterEnvFromMacros[MAXFILTERMACROS + 1]; +static char *MilterEnvRcptMacros[MAXFILTERMACROS + 1]; + +# define MILTER_CHECK_DONE_MSG() \ + if (*state == SMFIR_REPLYCODE || \ + *state == SMFIR_REJECT || \ + *state == SMFIR_DISCARD || \ + *state == SMFIR_TEMPFAIL) \ + { \ + /* Abort the filters to let them know we are done with msg */ \ + milter_abort(e); \ + } + +# define MILTER_CHECK_ERROR(action) \ + if (bitnset(SMF_TEMPFAIL, m->mf_flags)) \ + *state = SMFIR_TEMPFAIL; \ + else if (bitnset(SMF_REJECT, m->mf_flags)) \ + *state = SMFIR_REJECT; \ + else \ + action; + +# define MILTER_CHECK_REPLYCODE(default) \ + if (response == NULL || \ + strlen(response) + 1 != (size_t) rlen || \ + rlen < 3 || \ + (response[0] != '4' && response[0] != '5') || \ + !isascii(response[1]) || !isdigit(response[1]) || \ + !isascii(response[2]) || !isdigit(response[2])) \ + { \ + if (response != NULL) \ + free(response); \ + response = newstr(default); \ + } \ + else \ + { \ + char *ptr = response; \ + \ + /* Check for unprotected %'s in the string */ \ + while (*ptr != '\0') \ + { \ + if (*ptr == '%' && *++ptr != '%') \ + { \ + free(response); \ + response = newstr(default); \ + break; \ + } \ + ptr++; \ + } \ + } + +# define MILTER_DF_ERROR(msg) \ +{ \ + int save_errno = errno; \ + \ + if (tTd(64, 5)) \ + { \ + dprintf(msg, dfname, errstring(save_errno)); \ + dprintf("\n"); \ + } \ + if (LogLevel > 0) \ + sm_syslog(LOG_ERR, e->e_id, msg, dfname, errstring(save_errno)); \ + if (SuperSafe) \ + { \ + if (e->e_dfp != NULL) \ + { \ + (void) fclose(e->e_dfp); \ + e->e_dfp = NULL; \ + } \ + e->e_flags &= ~EF_HAS_DF; \ + } \ + errno = save_errno; \ +} + +/* +** MILTER_TIMEOUT -- make sure socket is ready in time +** +** Parameters: +** routine -- routine name for debug/logging +** secs -- number of seconds in timeout +** write -- waiting to read or write? +** +** Assumes 'm' is a milter structure for the current socket. +*/ + + +# define MILTER_TIMEOUT(routine, secs, write) \ +{ \ + int ret; \ + int save_errno; \ + fd_set fds; \ + struct timeval tv; \ + \ + if (m->mf_sock >= FD_SETSIZE) \ + { \ + if (tTd(64, 5)) \ + dprintf("%s(%s): socket %d is larger than FD_SETSIZE %d\n", \ + routine, m->mf_name, m->mf_sock, FD_SETSIZE); \ + if (LogLevel > 0) \ + sm_syslog(LOG_ERR, e->e_id, \ + "%s(%s): socket %d is larger than FD_SETSIZE %d\n", \ + routine, m->mf_name, m->mf_sock, FD_SETSIZE); \ + milter_error(m); \ + return NULL; \ + } \ + \ + FD_ZERO(&fds); \ + FD_SET(m->mf_sock, &fds); \ + tv.tv_sec = secs; \ + tv.tv_usec = 0; \ + ret = select(m->mf_sock + 1, \ + write ? NULL : &fds, \ + write ? &fds : NULL, \ + NULL, &tv); \ + \ + switch (ret) \ + { \ + case 0: \ + if (tTd(64, 5)) \ + dprintf("%s(%s): timeout\n", routine, m->mf_name); \ + if (LogLevel > 0) \ + sm_syslog(LOG_ERR, e->e_id, "%s(%s): timeout\n", \ + routine, m->mf_name); \ + milter_error(m); \ + return NULL; \ + \ + case -1: \ + save_errno = errno; \ + if (tTd(64, 5)) \ + dprintf("%s(%s): select: %s\n", \ + routine, m->mf_name, errstring(save_errno)); \ + if (LogLevel > 0) \ + sm_syslog(LOG_ERR, e->e_id, \ + "%s(%s): select: %s\n", \ + routine, m->mf_name, errstring(save_errno)); \ + milter_error(m); \ + return NULL; \ + \ + default: \ + if (FD_ISSET(m->mf_sock, &fds)) \ + break; \ + if (tTd(64, 5)) \ + dprintf("%s(%s): socket not ready\n", \ + routine, m->mf_name); \ + if (LogLevel > 0) \ + sm_syslog(LOG_ERR, e->e_id, \ + "%s(%s): socket not ready\n", \ + m->mf_name, routine); \ + milter_error(m); \ + return NULL; \ + } \ +} + + +/* +** Low level functions +*/ + +/* +** MILTER_READ -- read from a remote milter filter +** +** Parameters: +** m -- milter to read from. +** cmd -- return param for command read. +** rlen -- return length of response string. +** to -- timeout in seconds. +** e -- current envelope. +** +** Returns: +** response string (may be NULL) +*/ + +static char * +milter_sysread(m, buf, sz, to, e) + struct milter *m; + char *buf; + ssize_t sz; + time_t to; + ENVELOPE *e; +{ + time_t readstart; + ssize_t len, curl; + + curl = 0; + + if (to > 0) + readstart = curtime(); + + for (;;) + { + if (to > 0) + { + time_t now; + + now = curtime(); + if (now - readstart >= to) + { + if (tTd(64, 5)) + dprintf("milter_read(%s): timeout before data read\n", + m->mf_name); + if (LogLevel > 0) + sm_syslog(LOG_ERR, e->e_id, + "milter_read(%s): timeout before data read\n", + m->mf_name); + milter_error(m); + return NULL; + } + to -= now - readstart; + readstart = now; + MILTER_TIMEOUT("milter_read", to, FALSE); + } + + len = read(m->mf_sock, buf + curl, sz - curl); + + if (len < 0) + { + int save_errno = errno; + + if (tTd(64, 5)) + dprintf("milter_read(%s): read returned %ld: %s\n", + m->mf_name, (long) len, + errstring(save_errno)); + if (LogLevel > 0) + sm_syslog(LOG_ERR, e->e_id, + "milter_read(%s): read returned %ld: %s", + m->mf_name, (long) len, + errstring(save_errno)); + milter_error(m); + return NULL; + } + + curl += len; + if (len == 0 || len >= sz) + break; + + } + + if (curl != sz) + { + if (tTd(64, 5)) + dprintf("milter_read(%s): read returned %ld, expecting %ld\n", + m->mf_name, (long) curl, (long) sz); + if (LogLevel > 0) + sm_syslog(LOG_ERR, e->e_id, + "milter_read(%s): read returned %ld, expecting %ld", + m->mf_name, (long) curl, (long) sz); + milter_error(m); + return NULL; + } + return buf; +} + +static char * +milter_read(m, cmd, rlen, to, e) + struct milter *m; + char *cmd; + ssize_t *rlen; + time_t to; + ENVELOPE *e; +{ + time_t readstart; + ssize_t expl; + mi_int32 i; + char *buf; + char data[MILTER_LEN_BYTES + 1]; + + *rlen = 0; + *cmd = '\0'; + + if (to > 0) + readstart = curtime(); + + if (milter_sysread(m, data, sizeof data, to, e) == NULL) + return NULL; + + /* reset timeout */ + if (to > 0) + { + time_t now; + + now = curtime(); + if (now - readstart >= to) + { + if (tTd(64, 5)) + dprintf("milter_read(%s): timeout before data read\n", + m->mf_name); + if (LogLevel > 0) + sm_syslog(LOG_ERR, e->e_id, + "milter_read(%s): timeout before data read\n", + m->mf_name); + milter_error(m); + return NULL; + } + to -= now - readstart; + } + + *cmd = data[MILTER_LEN_BYTES]; + data[MILTER_LEN_BYTES] = '\0'; + (void) memcpy(&i, data, MILTER_LEN_BYTES); + expl = ntohl(i) - 1; + + if (tTd(64, 25)) + dprintf("milter_read(%s): expecting %ld bytes\n", + m->mf_name, (long) expl); + + if (expl < 0) + { + if (tTd(64, 5)) + dprintf("milter_read(%s): read size %ld out of range\n", + m->mf_name, (long) expl); + if (LogLevel > 0) + sm_syslog(LOG_ERR, e->e_id, + "milter_read(%s): read size %ld out of range", + m->mf_name, (long) expl); + milter_error(m); + return NULL; + } + + if (expl == 0) + return NULL; + + buf = (char *)xalloc(expl); + + if (milter_sysread(m, buf, expl, to, e) == NULL) + { + free(buf); + return NULL; + } + + if (tTd(64, 50)) + dprintf("milter_read(%s): Returning %*s\n", + m->mf_name, (int) expl, buf); + *rlen = expl; + return buf; +} +/* +** MILTER_WRITE -- write to a remote milter filter +** +** Parameters: +** m -- milter to read from. +** cmd -- command to send. +** buf -- optional command data. +** len -- length of buf. +** to -- timeout in seconds. +** e -- current envelope. +** +** Returns: +** buf if successful, NULL otherwise +** Not actually used anywhere but function prototype +** must match milter_read() +*/ + +static char * +milter_write(m, cmd, buf, len, to, e) + struct milter *m; + char cmd; + char *buf; + ssize_t len; + time_t to; + ENVELOPE *e; +{ + time_t writestart = (time_t) 0; + ssize_t sl, i; + mi_int32 nl; + char data[MILTER_LEN_BYTES + 1]; + + if (len < 0 || len > MILTER_CHUNK_SIZE) + { + if (tTd(64, 5)) + dprintf("milter_write(%s): length %ld out of range\n", + m->mf_name, (long) len); + if (LogLevel > 0) + sm_syslog(LOG_ERR, e->e_id, + "milter_write(%s): length %ld out of range", + m->mf_name, (long) len); + milter_error(m); + return NULL; + } + + if (tTd(64, 20)) + dprintf("milter_write(%s): cmd %c, len %ld\n", + m->mf_name, cmd, (long) len); + + nl = htonl(len + 1); /* add 1 for the cmd char */ + (void) memcpy(data, (char *) &nl, MILTER_LEN_BYTES); + data[MILTER_LEN_BYTES] = cmd; + sl = MILTER_LEN_BYTES + 1; + + if (to > 0) + { + writestart = curtime(); + MILTER_TIMEOUT("milter_write", to, TRUE); + } + + /* use writev() instead to send the whole stuff at once? */ + i = write(m->mf_sock, (void *) data, sl); + if (i != sl) + { + int save_errno = errno; + + if (tTd(64, 5)) + dprintf("milter_write(%s): write(%c) returned %ld, expected %ld: %s\n", + m->mf_name, cmd, (long) i, (long) sl, + errstring(save_errno)); + if (LogLevel > 0) + sm_syslog(LOG_ERR, e->e_id, + "milter_write(%s): write(%c) returned %ld, expected %ld: %s", + m->mf_name, cmd, (long) i, (long) sl, + errstring(save_errno)); + milter_error(m); + return buf; + } + + if (len <= 0 || buf == NULL) + return buf; + + if (tTd(64, 50)) + dprintf("milter_write(%s): Sending %*s\n", + m->mf_name, (int) len, buf); + + if (to > 0) + { + time_t now; + + now = curtime(); + if (now - writestart >= to) + { + if (tTd(64, 5)) + dprintf("milter_write(%s): timeout before data send\n", + m->mf_name); + if (LogLevel > 0) + sm_syslog(LOG_ERR, e->e_id, + "milter_write(%s): timeout before data send\n", + m->mf_name); + milter_error(m); + return NULL; + } + else + { + to -= now - writestart; + MILTER_TIMEOUT("milter_write", to, TRUE); + } + } + + i = write(m->mf_sock, (void *) buf, len); + if (i != len) + { + int save_errno = errno; + + if (tTd(64, 5)) + dprintf("milter_write(%s): write(%c) returned %ld, expected %ld: %s\n", + m->mf_name, cmd, (long) i, (long) sl, + errstring(save_errno)); + if (LogLevel > 0) + sm_syslog(LOG_ERR, e->e_id, + "milter_write(%s): write(%c) returned %ld, expected %ld: %s", + m->mf_name, cmd, (long) i, (long) len, + errstring(save_errno)); + milter_error(m); + return NULL; + } + return buf; +} + +/* +** Utility functions +*/ + +/* +** MILTER_OPEN -- connect to remote milter filter +** +** Parameters: +** m -- milter to connect to. +** parseonly -- parse but don't connect. +** e -- current envelope. +** +** Returns: +** connected socket if sucessful && !parseonly, +** 0 upon parse success if parseonly, +** -1 otherwise. +*/ + +static int +milter_open(m, parseonly, e) + struct milter *m; + bool parseonly; + ENVELOPE *e; +{ + int sock = 0; + SOCKADDR_LEN_T addrlen = 0; + int addrno = 0; + int save_errno; + char *p; + char *colon; + char *at; + struct hostent *hp = NULL; + SOCKADDR addr; + + if (m->mf_conn == NULL || m->mf_conn[0] == '\0') + { + if (tTd(64, 5)) + dprintf("X%s: empty or missing socket information\n", + m->mf_name); + if (parseonly) + syserr("X%s: empty or missing socket information", + m->mf_name); + else if (LogLevel > 10) + sm_syslog(LOG_ERR, e->e_id, + "X%s: empty or missing socket information", + m->mf_name); + milter_error(m); + return -1; + } + + /* protocol:filename or protocol:port@host */ + p = m->mf_conn; + colon = strchr(p, ':'); + if (colon != NULL) + { + *colon = '\0'; + + if (*p == '\0') + { +# if NETUNIX + /* default to AF_UNIX */ + addr.sa.sa_family = AF_UNIX; +# else /* NETUNIX */ +# if NETINET + /* default to AF_INET */ + addr.sa.sa_family = AF_INET; +# else /* NETINET */ +# if NETINET6 + /* default to AF_INET6 */ + addr.sa.sa_family = AF_INET6; +# else /* NETINET6 */ + /* no protocols available */ + sm_syslog(LOG_ERR, e->e_id, + "X%s: no valid socket protocols available", + m->mf_name); + milter_error(m); + return -1; +# endif /* NETINET6 */ +# endif /* NETINET */ +# endif /* NETUNIX */ + } +# if NETUNIX + else if (strcasecmp(p, "unix") == 0 || + strcasecmp(p, "local") == 0) + addr.sa.sa_family = AF_UNIX; +# endif /* NETUNIX */ +# if NETINET + else if (strcasecmp(p, "inet") == 0) + addr.sa.sa_family = AF_INET; +# endif /* NETINET */ +# if NETINET6 + else if (strcasecmp(p, "inet6") == 0) + addr.sa.sa_family = AF_INET6; +# endif /* NETINET6 */ + else + { +# ifdef EPROTONOSUPPORT + errno = EPROTONOSUPPORT; +# else /* EPROTONOSUPPORT */ + errno = EINVAL; +# endif /* EPROTONOSUPPORT */ + if (tTd(64, 5)) + dprintf("X%s: unknown socket type %s\n", + m->mf_name, p); + if (parseonly) + syserr("X%s: unknown socket type %s", + m->mf_name, p); + else if (LogLevel > 10) + sm_syslog(LOG_ERR, e->e_id, + "X%s: unknown socket type %s", + m->mf_name, p); + milter_error(m); + return -1; + } + *colon++ = ':'; + } + else + { + /* default to AF_UNIX */ + addr.sa.sa_family = AF_UNIX; + colon = p; + } + +# if NETUNIX + if (addr.sa.sa_family == AF_UNIX) + { + long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_EXECOK; + + at = colon; + if (strlen(colon) >= sizeof addr.sunix.sun_path) + { + if (tTd(64, 5)) + dprintf("X%s: local socket name %s too long\n", + m->mf_name, colon); + errno = EINVAL; + if (parseonly) + syserr("X%s: local socket name %s too long", + m->mf_name, colon); + else if (LogLevel > 10) + sm_syslog(LOG_ERR, e->e_id, + "X%s: local socket name %s too long", + m->mf_name, colon); + milter_error(m); + return -1; + } + errno = safefile(colon, RunAsUid, RunAsGid, RunAsUserName, sff, + S_IRUSR|S_IWUSR, NULL); + + /* if just parsing .cf file, socket doesn't need to exist */ + if (parseonly && errno == ENOENT) + { + if (OpMode == MD_DAEMON || + OpMode == MD_FGDAEMON) + fprintf(stderr, + "WARNING: X%s: local socket name %s missing\n", + m->mf_name, colon); + } + else if (errno != 0) + { + /* if not safe, don't create */ + save_errno = errno; + if (tTd(64, 5)) + dprintf("X%s: local socket name %s unsafe\n", + m->mf_name, colon); + errno = save_errno; + if (parseonly) + { + if (OpMode == MD_DAEMON || + OpMode == MD_FGDAEMON || + OpMode == MD_SMTP) + syserr("X%s: local socket name %s unsafe", + m->mf_name, colon); + } + else if (LogLevel > 10) + sm_syslog(LOG_ERR, e->e_id, + "X%s: local socket name %s unsafe", + m->mf_name, colon); + milter_error(m); + return -1; + } + + (void) strlcpy(addr.sunix.sun_path, colon, + sizeof addr.sunix.sun_path); + addrlen = sizeof (struct sockaddr_un); + } + else +# endif /* NETUNIX */ +# if NETINET || NETINET6 + if (FALSE +# if NETINET + || addr.sa.sa_family == AF_INET +# endif /* NETINET */ +# if NETINET6 + || addr.sa.sa_family == AF_INET6 +# endif /* NETINET6 */ + ) + { + u_short port; + + /* Parse port@host */ + at = strchr(colon, '@'); + if (at == NULL) + { + if (tTd(64, 5)) + dprintf("X%s: bad address %s (expected port@host)\n", + m->mf_name, colon); + if (parseonly) + syserr("X%s: bad address %s (expected port@host)", + m->mf_name, colon); + else if (LogLevel > 10) + sm_syslog(LOG_ERR, e->e_id, + "X%s: bad address %s (expected port@host)", + m->mf_name, colon); + milter_error(m); + return -1; + } + *at = '\0'; + if (isascii(*colon) && isdigit(*colon)) + port = htons((u_short) atoi(colon)); + else + { +# ifdef NO_GETSERVBYNAME + if (tTd(64, 5)) + dprintf("X%s: invalid port number %s\n", + m->mf_name, colon); + if (parseonly) + syserr("X%s: invalid port number %s", + m->mf_name, colon); + else if (LogLevel > 10) + sm_syslog(LOG_ERR, e->e_id, + "X%s: invalid port number %s", + m->mf_name, colon); + milter_error(m); + return -1; +# else /* NO_GETSERVBYNAME */ + register struct servent *sp; + + sp = getservbyname(colon, "tcp"); + if (sp == NULL) + { + save_errno = errno; + if (tTd(64, 5)) + dprintf("X%s: unknown port name %s\n", + m->mf_name, colon); + errno = save_errno; + if (parseonly) + syserr("X%s: unknown port name %s", + m->mf_name, colon); + else if (LogLevel > 10) + sm_syslog(LOG_ERR, e->e_id, + "X%s: unknown port name %s", + m->mf_name, colon); + milter_error(m); + return -1; + } + port = sp->s_port; +# endif /* NO_GETSERVBYNAME */ + } + *at++ = '@'; + if (*at == '[') + { + char *end; + + end = strchr(at, ']'); + if (end != NULL) + { + bool found = FALSE; +# if NETINET + unsigned long hid = INADDR_NONE; +# endif /* NETINET */ +# if NETINET6 + struct sockaddr_in6 hid6; +# endif /* NETINET6 */ + + *end = '\0'; +# if NETINET + if (addr.sa.sa_family == AF_INET && + (hid = inet_addr(&at[1])) != INADDR_NONE) + { + addr.sin.sin_addr.s_addr = hid; + addr.sin.sin_port = port; + found = TRUE; + } +# endif /* NETINET */ +# if NETINET6 + (void) memset(&hid6, '\0', sizeof hid6); + if (addr.sa.sa_family == AF_INET6 && + inet_pton(AF_INET6, &at[1], + &hid6.sin6_addr) == 1) + { + addr.sin6.sin6_addr = hid6.sin6_addr; + addr.sin6.sin6_port = port; + found = TRUE; + } +# endif /* NETINET6 */ + *end = ']'; + if (!found) + { + if (tTd(64, 5)) + dprintf("X%s: Invalid numeric domain spec \"%s\"\n", + m->mf_name, at); + if (parseonly) + syserr("X%s: Invalid numeric domain spec \"%s\"", + m->mf_name, at); + else if (LogLevel > 10) + sm_syslog(LOG_ERR, e->e_id, + "X%s: Invalid numeric domain spec \"%s\"", + m->mf_name, at); + milter_error(m); + return -1; + } + } + else + { + if (tTd(64, 5)) + dprintf("X%s: Invalid numeric domain spec \"%s\"\n", + m->mf_name, at); + if (parseonly) + syserr("X%s: Invalid numeric domain spec \"%s\"", + m->mf_name, at); + else if (LogLevel > 10) + sm_syslog(LOG_ERR, e->e_id, + "X%s: Invalid numeric domain spec \"%s\"", + m->mf_name, at); + milter_error(m); + return -1; + } + } + else + { + hp = sm_gethostbyname(at, addr.sa.sa_family); + if (hp == NULL) + { + save_errno = errno; + if (tTd(64, 5)) + dprintf("X%s: Unknown host name %s\n", + m->mf_name, at); + errno = save_errno; + if (parseonly) + syserr("X%s: Unknown host name %s", + m->mf_name, at); + else if (LogLevel > 10) + sm_syslog(LOG_ERR, e->e_id, + "X%s: Unknown host name %s", + m->mf_name, at); + milter_error(m); + return -1; + } + addr.sa.sa_family = hp->h_addrtype; + switch (hp->h_addrtype) + { +# if NETINET + case AF_INET: + memmove(&addr.sin.sin_addr, + hp->h_addr, + INADDRSZ); + addr.sin.sin_port = port; + addrlen = sizeof (struct sockaddr_in); + addrno = 1; + break; +# endif /* NETINET */ + +# if NETINET6 + case AF_INET6: + memmove(&addr.sin6.sin6_addr, + hp->h_addr, + IN6ADDRSZ); + addr.sin6.sin6_port = port; + addrlen = sizeof (struct sockaddr_in6); + addrno = 1; + break; +# endif /* NETINET6 */ + + default: + if (tTd(64, 5)) + dprintf("X%s: Unknown protocol for %s (%d)\n", + m->mf_name, at, + hp->h_addrtype); + if (parseonly) + syserr("X%s: Unknown protocol for %s (%d)", + m->mf_name, at, hp->h_addrtype); + else if (LogLevel > 10) + sm_syslog(LOG_ERR, e->e_id, + "X%s: Unknown protocol for %s (%d)", + m->mf_name, at, + hp->h_addrtype); + milter_error(m); + return -1; + } + } + } + else +# endif /* NETINET || NETINET6 */ + { + if (tTd(64, 5)) + dprintf("X%s: unknown socket protocol\n", m->mf_name); + if (parseonly) + syserr("X%s: unknown socket protocol", m->mf_name); + else if (LogLevel > 10) + sm_syslog(LOG_ERR, e->e_id, + "X%s: unknown socket protocol", m->mf_name); + milter_error(m); + return -1; + } + + /* just parsing through? */ + if (parseonly) + { + m->mf_state = SMFS_READY; + return 0; + } + + /* sanity check */ + if (m->mf_state != SMFS_READY && + m->mf_state != SMFS_CLOSED) + { + /* shouldn't happen */ + if (tTd(64, 1)) + dprintf("milter_open(%s): Trying to open filter in state %c\n", + m->mf_name, (char) m->mf_state); + milter_error(m); + return -1; + } + + /* nope, actually connecting */ + for (;;) + { + sock = socket(addr.sa.sa_family, SOCK_STREAM, 0); + if (sock < 0) + { + save_errno = errno; + if (tTd(64, 5)) + dprintf("X%s: error creating socket: %s\n", + m->mf_name, errstring(save_errno)); + if (LogLevel > 0) + sm_syslog(LOG_ERR, e->e_id, + "X%s: error creating socket: %s", + m->mf_name, errstring(save_errno)); + milter_error(m); + return -1; + } + + if (connect(sock, (struct sockaddr *) &addr, addrlen) >= 0) + break; + + /* couldn't connect.... try next address */ + save_errno = errno; + if (tTd(64, 5)) + dprintf("milter_open(%s): %s failed: %s\n", + m->mf_name, at, errstring(save_errno)); + if (LogLevel >= 14) + sm_syslog(LOG_INFO, e->e_id, + "milter_open(%s): %s failed: %s", + m->mf_name, at, errstring(save_errno)); + (void) close(sock); + + /* try next address */ + if (hp != NULL && hp->h_addr_list[addrno] != NULL) + { + switch (addr.sa.sa_family) + { +# if NETINET + case AF_INET: + memmove(&addr.sin.sin_addr, + hp->h_addr_list[addrno++], + INADDRSZ); + break; +# endif /* NETINET */ + +# if NETINET6 + case AF_INET6: + memmove(&addr.sin6.sin6_addr, + hp->h_addr_list[addrno++], + IN6ADDRSZ); + break; +# endif /* NETINET6 */ + + default: + if (tTd(64, 5)) + dprintf("X%s: Unknown protocol for %s (%d)\n", + m->mf_name, at, + hp->h_addrtype); + if (LogLevel > 0) + sm_syslog(LOG_ERR, e->e_id, + "X%s: Unknown protocol for %s (%d)", + m->mf_name, at, + hp->h_addrtype); + milter_error(m); + return -1; + } + continue; + } + if (tTd(64, 5)) + dprintf("X%s: error connecting to filter\n", + m->mf_name); + if (LogLevel > 0) + sm_syslog(LOG_ERR, e->e_id, + "X%s: error connecting to filter", + m->mf_name); + milter_error(m); + return -1; + } + m->mf_state = SMFS_OPEN; + return sock; +} +/* +** MILTER_SETUP -- setup structure for a mail filter +** +** Parameters: +** line -- the options line. +** +** Returns: +** none +*/ + +void +milter_setup(line) + char *line; +{ + char fcode; + register char *p; + register struct milter *m; + STAB *s; + + /* collect the mailer name */ + for (p = line; + *p != '\0' && *p != ',' && !(isascii(*p) && isspace(*p)); + p++) + continue; + if (*p != '\0') + *p++ = '\0'; + if (line[0] == '\0') + { + syserr("name required for mail filter"); + return; + } + m = (struct milter *)xalloc(sizeof *m); + memset((char *) m, '\0', sizeof *m); + m->mf_name = newstr(line); + m->mf_state = SMFS_READY; + m->mf_sock = -1; + m->mf_timeout[SMFTO_WRITE] = (time_t) 10; + m->mf_timeout[SMFTO_READ] = (time_t) 10; + m->mf_timeout[SMFTO_EOM] = (time_t) 300; + + /* now scan through and assign info from the fields */ + while (*p != '\0') + { + char *delimptr; + + while (*p != '\0' && + (*p == ',' || (isascii(*p) && isspace(*p)))) + p++; + + /* p now points to field code */ + fcode = *p; + while (*p != '\0' && *p != '=' && *p != ',') + p++; + if (*p++ != '=') + { + syserr("X%s: `=' expected", m->mf_name); + return; + } + while (isascii(*p) && isspace(*p)) + p++; + + /* p now points to the field body */ + p = munchstring(p, &delimptr, ','); + + /* install the field into the mailer struct */ + switch (fcode) + { + case 'S': /* socket */ + if (p == NULL) + m->mf_conn = NULL; + else + m->mf_conn = newstr(p); + break; + + case 'F': /* Milter flags configured on MTA */ + for (; *p != '\0'; p++) + { + if (!(isascii(*p) && isspace(*p))) + setbitn(*p, m->mf_flags); + } + break; + + case 'T': /* timeouts */ + milter_parse_timeouts(p, m); + break; + + default: + syserr("X%s: unknown filter equate %c=", + m->mf_name, fcode); + break; + } + p = delimptr; + } + + /* early check for errors */ + (void) milter_open(m, TRUE, CurEnv); + + /* enter the mailer into the symbol table */ + s = stab(m->mf_name, ST_MILTER, ST_ENTER); + if (s->s_milter != NULL) + syserr("X%s: duplicate filter definition", m->mf_name); + else + s->s_milter = m; +} +/* +** MILTER_PARSE_LIST -- parse option list into an array +** +** Called when reading configuration file. +** +** Parameters: +** spec -- the filter list. +** list -- the array to fill in. +** max -- the maximum number of entries in list. +** +** Returns: +** none +*/ + +void +milter_parse_list(spec, list, max) + char *spec; + struct milter **list; + int max; +{ + int numitems = 0; + register char *p; + + /* leave one for the NULL signifying the end of the list */ + max--; + + for (p = spec; p != NULL; ) + { + STAB *s; + + while (isascii(*p) && isspace(*p)) + p++; + if (*p == '\0') + break; + spec = p; + + if (numitems >= max) + { + syserr("Too many filters defined, %d max", max); + if (max > 0) + list[0] = NULL; + return; + } + p = strpbrk(p, ","); + if (p != NULL) + *p++ = '\0'; + + s = stab(spec, ST_MILTER, ST_FIND); + if (s == NULL) + { + syserr("InputFilter %s not defined", spec); + ExitStat = EX_CONFIG; + return; + } + list[numitems++] = s->s_milter; + } + list[numitems] = NULL; +} +/* +** MILTER_PARSE_TIMEOUTS -- parse timeout list +** +** Called when reading configuration file. +** +** Parameters: +** spec -- the timeout list. +** m -- milter to set. +** +** Returns: +** none +*/ + +static void +milter_parse_timeouts(spec, m) + char *spec; + struct milter *m; +{ + char fcode; + register char *p; + + p = spec; + + /* now scan through and assign info from the fields */ + while (*p != '\0') + { + char *delimptr; + + while (*p != '\0' && + (*p == ';' || (isascii(*p) && isspace(*p)))) + p++; + + /* p now points to field code */ + fcode = *p; + while (*p != '\0' && *p != ':') + p++; + if (*p++ != ':') + { + syserr("X%s, T=: `:' expected", m->mf_name); + return; + } + while (isascii(*p) && isspace(*p)) + p++; + + /* p now points to the field body */ + p = munchstring(p, &delimptr, ';'); + + /* install the field into the mailer struct */ + switch (fcode) + { + case 'S': + m->mf_timeout[SMFTO_WRITE] = convtime(p, 's'); + if (tTd(64, 5)) + printf("X%s: %c=%ld\n", + m->mf_name, fcode, + (u_long) m->mf_timeout[SMFTO_WRITE]); + break; + + case 'R': + m->mf_timeout[SMFTO_READ] = convtime(p, 's'); + if (tTd(64, 5)) + printf("X%s: %c=%ld\n", + m->mf_name, fcode, + (u_long) m->mf_timeout[SMFTO_READ]); + break; + + case 'E': + m->mf_timeout[SMFTO_EOM] = convtime(p, 's'); + if (tTd(64, 5)) + printf("X%s: %c=%ld\n", + m->mf_name, fcode, + (u_long) m->mf_timeout[SMFTO_EOM]); + break; + + default: + if (tTd(64, 5)) + printf("X%s: %c unknown\n", + m->mf_name, fcode); + syserr("X%s: unknown filter timeout %c", + m->mf_name, fcode); + break; + } + p = delimptr; + } +} +/* +** MILTER_SET_OPTION -- set an individual milter option +** +** Parameters: +** name -- the name of the option. +** val -- the value of the option. +** sticky -- if set, don't let other setoptions override +** this value. +** +** Returns: +** none. +*/ + +/* set if Milter sub-option is stuck */ +static BITMAP256 StickyMilterOpt; + +static struct milteropt +{ + char *mo_name; /* long name of milter option */ + u_char mo_code; /* code for option */ +} MilterOptTab[] = +{ +# define MO_MACROS_CONNECT 0x01 + { "macros.connect", MO_MACROS_CONNECT }, +# define MO_MACROS_HELO 0x02 + { "macros.helo", MO_MACROS_HELO }, +# define MO_MACROS_ENVFROM 0x03 + { "macros.envfrom", MO_MACROS_ENVFROM }, +# define MO_MACROS_ENVRCPT 0x04 + { "macros.envrcpt", MO_MACROS_ENVRCPT }, + { NULL, 0 }, +}; + +void +milter_set_option(name, val, sticky) + char *name; + char *val; + bool sticky; +{ + int nummac = 0; + register struct milteropt *mo; + char *p; + char **macros = NULL; + + if (tTd(37, 2) || tTd(64, 5)) + dprintf("milter_set_option(%s = %s)", name, val); + + for (mo = MilterOptTab; mo->mo_name != NULL; mo++) + { + if (strcasecmp(mo->mo_name, name) == 0) + break; + } + + if (mo->mo_name == NULL) + syserr("milter_set_option: invalid Milter option %s", name); + + /* + ** See if this option is preset for us. + */ + + if (!sticky && bitnset(mo->mo_code, StickyMilterOpt)) + { + if (tTd(37, 2) || tTd(64,5)) + dprintf(" (ignored)\n"); + return; + } + + if (tTd(37, 2) || tTd(64,5)) + dprintf("\n"); + + switch (mo->mo_code) + { + case MO_MACROS_CONNECT: + if (macros == NULL) + macros = MilterConnectMacros; + /* FALLTHROUGH */ + + case MO_MACROS_HELO: + if (macros == NULL) + macros = MilterHeloMacros; + /* FALLTHROUGH */ + + case MO_MACROS_ENVFROM: + if (macros == NULL) + macros = MilterEnvFromMacros; + /* FALLTHROUGH */ + + case MO_MACROS_ENVRCPT: + if (macros == NULL) + macros = MilterEnvRcptMacros; + + p = newstr(val); + while (*p != '\0') + { + char *macro; + + /* Skip leading commas, spaces */ + while (*p != '\0' && + (*p == ',' || (isascii(*p) && isspace(*p)))) + p++; + + if (*p == '\0') + break; + + /* Find end of macro */ + macro = p; + while (*p != '\0' && *p != ',' && + isascii(*p) && !isspace(*p)) + p++; + if (*p != '\0') + *p++ = '\0'; + + if (nummac >= MAXFILTERMACROS) + { + syserr("milter_set_option: too many macros in Milter.%s (max %d)", + name, MAXFILTERMACROS); + macros[nummac] = NULL; + break; + } + macros[nummac++] = macro; + } + macros[nummac] = NULL; + break; + + default: + syserr("milter_set_option: invalid Milter option %s", name); + break; + } + + if (sticky) + setbitn(mo->mo_code, StickyMilterOpt); +} +/* +** MILTER_REOPEN_DF -- open & truncate the df file (for replbody) +** +** Parameters: +** e -- current envelope. +** +** Returns: +** 0 if succesful, -1 otherwise +*/ + +static int +milter_reopen_df(e) + ENVELOPE *e; +{ + char dfname[MAXPATHLEN]; + + (void) strlcpy(dfname, queuename(e, 'd'), sizeof dfname); + + /* + ** In SuperSafe mode, e->e_dfp is a read-only FP so + ** close and reopen writable (later close and reopen + ** read only again). + ** + ** In !SuperSafe mode, e->e_dfp still points at the + ** buffered file I/O descriptor, still open for writing + ** so there isn't as much work to do, just truncate it + ** and go. + */ + + if (SuperSafe) + { + /* close read-only df */ + if (bitset(EF_HAS_DF, e->e_flags) && e->e_dfp != NULL) + { + (void) fclose(e->e_dfp); + e->e_flags &= ~EF_HAS_DF; + } + + /* open writable */ + if ((e->e_dfp = fopen(dfname, "w+")) == NULL) + { + MILTER_DF_ERROR("milter_reopen_df: fopen %s: %s"); + return -1; + } + } + else if (e->e_dfp == NULL) + { + /* shouldn't happen */ + errno = ENOENT; + MILTER_DF_ERROR("milter_reopen_df: NULL e_dfp (%s: %s)"); + return -1; + } + return 0; +} +/* +** MILTER_RESET_DF -- re-open read-only the df file (for replbody) +** +** Parameters: +** e -- current envelope. +** +** Returns: +** 0 if succesful, -1 otherwise +*/ + +static int +milter_reset_df(e) + ENVELOPE *e; +{ + int afd; + char dfname[MAXPATHLEN]; + + (void) strlcpy(dfname, queuename(e, 'd'), sizeof dfname); + + if (fflush(e->e_dfp) != 0 || ferror(e->e_dfp)) + { + MILTER_DF_ERROR("milter_reset_df: error writing/flushing %s: %s"); + return -1; + } + else if (!SuperSafe) + { + /* skip next few clauses */ + /* EMPTY */ + } + else if ((afd = fileno(e->e_dfp)) >= 0 && fsync(afd) < 0) + { + MILTER_DF_ERROR("milter_reset_df: error sync'ing %s: %s"); + return -1; + } + else if (fclose(e->e_dfp) < 0) + { + MILTER_DF_ERROR("milter_reset_df: error closing %s: %s"); + return -1; + } + else if ((e->e_dfp = fopen(dfname, "r")) == NULL) + { + MILTER_DF_ERROR("milter_reset_df: error reopening %s: %s"); + return -1; + } + else + e->e_flags |= EF_HAS_DF; + return 0; +} +/* +** MILTER_CAN_DELRCPTS -- can any milter filters delete recipients? +** +** Parameters: +** none +** +** Returns: +** TRUE if any filter deletes recipients, FALSE otherwise +*/ + +bool +milter_can_delrcpts() +{ + bool can = FALSE; + int i; + + if (tTd(64, 10)) + dprintf("milter_can_delrcpts:"); + + for (i = 0; InputFilters[i] != NULL; i++) + { + struct milter *m = InputFilters[i]; + + if (bitset(SMFIF_DELRCPT, m->mf_fflags)) + { + can = TRUE; + break; + } + } + if (tTd(64, 10)) + dprintf("%s\n", can ? "TRUE" : "FALSE"); + + return can; +} +/* +** MILTER_QUIT_FILTER -- close down a single filter +** +** Parameters: +** m -- milter structure of filter to close down. +** e -- current envelope. +** +** Returns: +** none +*/ + +static void +milter_quit_filter(m, e) + struct milter *m; + ENVELOPE *e; +{ + if (tTd(64, 10)) + dprintf("milter_quit_filter(%s)\n", m->mf_name); + + /* Never replace error state */ + if (m->mf_state == SMFS_ERROR) + return; + + if (m->mf_sock < 0 || + m->mf_state == SMFS_CLOSED || + m->mf_state == SMFS_READY) + { + m->mf_sock = -1; + m->mf_state = SMFS_CLOSED; + return; + } + + (void) milter_write(m, SMFIC_QUIT, (char *) NULL, 0, + m->mf_timeout[SMFTO_WRITE], e); + (void) close(m->mf_sock); + m->mf_sock = -1; + if (m->mf_state != SMFS_ERROR) + m->mf_state = SMFS_CLOSED; +} +/* +** MILTER_ABORT_FILTER -- tell filter to abort current message +** +** Parameters: +** m -- milter structure of filter to abort. +** e -- current envelope. +** +** Returns: +** none +*/ + +static void +milter_abort_filter(m, e) + struct milter *m; + ENVELOPE *e; +{ + if (tTd(64, 10)) + dprintf("milter_abort_filter(%s)\n", m->mf_name); + + if (m->mf_sock < 0 || + m->mf_state != SMFS_INMSG) + return; + + (void) milter_write(m, SMFIC_ABORT, (char *) NULL, 0, + m->mf_timeout[SMFTO_WRITE], e); + if (m->mf_state != SMFS_ERROR) + m->mf_state = SMFS_DONE; +} +/* +** MILTER_SEND_MACROS -- provide macros to the filters +** +** Parameters: +** m -- milter to send macros to. +** macros -- macros to send for filter smfi_getsymval(). +** cmd -- which command the macros are associated with. +** e -- current envelope (for macro access). +** +** Returns: +** none +*/ + +static void +milter_send_macros(m, macros, cmd, e) + struct milter *m; + char **macros; + char cmd; + ENVELOPE *e; +{ + int i; + int mid; + char *v; + char *buf, *bp; + ssize_t s; + + /* sanity check */ + if (macros == NULL || macros[0] == NULL) + return; + + /* put together data */ + s = 1; /* for the command character */ + for (i = 0; macros[i] != NULL; i++) + { + mid = macid(macros[i], NULL); + if (mid == '\0') + continue; + v = macvalue(mid, e); + if (v == NULL) + continue; + s += strlen(macros[i]) + 1 + strlen(v) + 1; + } + + buf = (char *)xalloc(s); + bp = buf; + *bp++ = cmd; + for (i = 0; macros[i] != NULL; i++) + { + mid = macid(macros[i], NULL); + if (mid == '\0') + continue; + v = macvalue(mid, e); + if (v == NULL) + continue; + + if (tTd(64, 10)) + dprintf("milter_send_macros(%s, %c): %s=%s\n", + m->mf_name, cmd, macros[i], v); + + (void) strlcpy(bp, macros[i], s - (bp - buf)); + bp += strlen(bp) + 1; + (void) strlcpy(bp, v, s - (bp - buf)); + bp += strlen(bp) + 1; + } + (void) milter_write(m, SMFIC_MACRO, buf, s, + m->mf_timeout[SMFTO_WRITE], e); + free(buf); +} + +/* +** MILTER_SEND_COMMAND -- send a command and return the response for a filter +** +** Parameters: +** m -- current milter filter +** command -- command to send. +** data -- optional command data. +** sz -- length of buf. +** e -- current envelope (for e->e_id). +** state -- return state word. +** +** Returns: +** response string (may be NULL) +*/ + +static char * +milter_send_command(m, command, data, sz, e, state) + struct milter *m; + char command; + void *data; + ssize_t sz; + ENVELOPE *e; + char *state; +{ + char rcmd; + ssize_t rlen; + u_long skipflag; + char *defresponse; + char *response; + + if (tTd(64, 10)) + dprintf("milter_send_command(%s): cmd %c len %ld\n", + m->mf_name, (char) command, (long) sz); + + /* find skip flag and default failure */ + switch (command) + { + case SMFIC_CONNECT: + skipflag = SMFIP_NOCONNECT; + defresponse = "554 Command rejected"; + break; + + case SMFIC_HELO: + skipflag = SMFIP_NOHELO; + defresponse = "550 Command rejected"; + break; + + case SMFIC_MAIL: + skipflag = SMFIP_NOMAIL; + defresponse = "550 5.7.1 Command rejected"; + break; + + case SMFIC_RCPT: + skipflag = SMFIP_NORCPT; + defresponse = "550 5.7.1 Command rejected"; + break; + + case SMFIC_HEADER: + skipflag = SMFIP_NOHDRS; + defresponse = "550 5.7.1 Command rejected"; + break; + + case SMFIC_BODY: + skipflag = SMFIP_NOBODY; + defresponse = "554 5.7.1 Command rejected"; + break; + + case SMFIC_EOH: + skipflag = SMFIP_NOEOH; + defresponse = "550 5.7.1 Command rejected"; + break; + + case SMFIC_BODYEOB: + case SMFIC_OPTNEG: + case SMFIC_MACRO: + case SMFIC_ABORT: + case SMFIC_QUIT: + /* NOTE: not handled by milter_send_command() */ + /* FALLTHROUGH */ + + default: + skipflag = 0; + defresponse = "550 5.7.1 Command rejected"; + break; + } + + /* check if filter wants this command */ + if (skipflag != 0 && + bitset(skipflag, m->mf_pflags)) + return NULL; + + + (void) milter_write(m, command, data, sz, + m->mf_timeout[SMFTO_WRITE], e); + if (m->mf_state == SMFS_ERROR) + { + MILTER_CHECK_ERROR(/* EMPTY */;); + return NULL; + } + + response = milter_read(m, &rcmd, &rlen, + m->mf_timeout[SMFTO_READ], e); + if (m->mf_state == SMFS_ERROR) + { + MILTER_CHECK_ERROR(/* EMPTY */;); + return NULL; + } + + if (tTd(64, 10)) + dprintf("milter_send_command(%s): returned %c\n", + m->mf_name, (char) rcmd); + + switch (rcmd) + { + case SMFIR_REPLYCODE: + MILTER_CHECK_REPLYCODE(defresponse); + /* FALLTHROUGH */ + + case SMFIR_REJECT: + case SMFIR_DISCARD: + case SMFIR_TEMPFAIL: + *state = rcmd; + break; + + case SMFIR_ACCEPT: + /* this filter is done with message/connection */ + m->mf_state = SMFS_DONE; + break; + + case SMFIR_CONTINUE: + /* if MAIL command is ok, filter is in message state */ + if (command == SMFIC_MAIL) + m->mf_state = SMFS_INMSG; + break; + + default: + /* Invalid response to command */ + if (LogLevel > 0) + sm_syslog(LOG_ERR, e->e_id, + "milter_send_command(%s): returned bogus response %c", + m->mf_name, rcmd); + milter_error(m); + break; + } + + if (*state != SMFIR_REPLYCODE && + response != NULL) + { + free(response); + response = NULL; + } + return response; +} + +/* +** MILTER_COMMAND -- send a command and return the response for each filter +** +** Parameters: +** command -- command to send. +** data -- optional command data. +** sz -- length of buf. +** macros -- macros to send for filter smfi_getsymval(). +** e -- current envelope (for macro access). +** state -- return state word. +** +** Returns: +** response string (may be NULL) +*/ + +static char * +milter_command(command, data, sz, macros, e, state) + char command; + void *data; + ssize_t sz; + char **macros; + ENVELOPE *e; + char *state; +{ + int i; + char *response = NULL; + + if (tTd(64, 10)) + dprintf("milter_command: cmd %c len %ld\n", + (char) command, (long) sz); + + *state = SMFIR_CONTINUE; + for (i = 0; InputFilters[i] != NULL; i++) + { + struct milter *m = InputFilters[i]; + + /* sanity check */ + if (m->mf_sock < 0 || + (m->mf_state != SMFS_OPEN && m->mf_state != SMFS_INMSG)) + continue; + + /* send macros (regardless of whether we send command) */ + if (macros != NULL && macros[0] != NULL) + { + milter_send_macros(m, macros, command, e); + if (m->mf_state == SMFS_ERROR) + { + MILTER_CHECK_ERROR(continue); + break; + } + } + + response = milter_send_command(m, command, data, sz, e, state); + if (*state != SMFIR_CONTINUE) + break; + } + return response; +} +/* +** MILTER_NEGOTIATE -- get version and flags from filter +** +** Parameters: +** m -- milter filter structure. +** e -- current envelope. +** +** Returns: +** 0 on success, -1 otherwise +*/ + +static int +milter_negotiate(m, e) + struct milter *m; + ENVELOPE *e; +{ + char rcmd; + mi_int32 fvers; + mi_int32 fflags; + mi_int32 pflags; + char *response; + ssize_t rlen; + char data[MILTER_OPTLEN]; + + /* sanity check */ + if (m->mf_sock < 0 || m->mf_state != SMFS_OPEN) + { + if (LogLevel > 0) + sm_syslog(LOG_ERR, e->e_id, + "milter_negotiate(%s): impossible state", + m->mf_name); + milter_error(m); + return -1; + } + + fvers = htonl(SMFI_VERSION); + fflags = htonl(SMFI_CURR_ACTS); + pflags = htonl(SMFI_CURR_PROT); + (void) memcpy(data, (char *) &fvers, MILTER_LEN_BYTES); + (void) memcpy(data + MILTER_LEN_BYTES, + (char *) &fflags, MILTER_LEN_BYTES); + (void) memcpy(data + (MILTER_LEN_BYTES * 2), + (char *) &pflags, MILTER_LEN_BYTES); + (void) milter_write(m, SMFIC_OPTNEG, data, sizeof data, + m->mf_timeout[SMFTO_WRITE], e); + + if (m->mf_state == SMFS_ERROR) + return -1; + + response = milter_read(m, &rcmd, &rlen, m->mf_timeout[SMFTO_READ], e); + if (m->mf_state == SMFS_ERROR) + return -1; + + if (rcmd != SMFIC_OPTNEG) + { + if (tTd(64, 5)) + dprintf("milter_negotiate(%s): returned %c instead of %c\n", + m->mf_name, rcmd, SMFIC_OPTNEG); + if (LogLevel > 0) + sm_syslog(LOG_ERR, e->e_id, + "milter_negotiate(%s): returned %c instead of %c", + m->mf_name, rcmd, SMFIC_OPTNEG); + if (response != NULL) + free(response); + milter_error(m); + return -1; + } + + /* Make sure we have enough bytes for the version */ + if (response == NULL || rlen < MILTER_LEN_BYTES) + { + if (tTd(64, 5)) + dprintf("milter_negotiate(%s): did not return valid info\n", + m->mf_name); + if (LogLevel > 0) + sm_syslog(LOG_ERR, e->e_id, + "milter_negotiate(%s): did not return valid info", + m->mf_name); + if (response != NULL) + free(response); + milter_error(m); + return -1; + } + + /* extract information */ + (void) memcpy((char *) &fvers, response, MILTER_LEN_BYTES); + + /* Now make sure we have enough for the feature bitmap */ + if (rlen != MILTER_OPTLEN) + { + if (tTd(64, 5)) + dprintf("milter_negotiate(%s): did not return enough info\n", + m->mf_name); + if (LogLevel > 0) + sm_syslog(LOG_ERR, e->e_id, + "milter_negotiate(%s): did not return enough info", + m->mf_name); + if (response != NULL) + free(response); + milter_error(m); + return -1; + } + + (void) memcpy((char *) &fflags, response + MILTER_LEN_BYTES, + MILTER_LEN_BYTES); + (void) memcpy((char *) &pflags, response + (MILTER_LEN_BYTES * 2), + MILTER_LEN_BYTES); + free(response); + response = NULL; + + m->mf_fvers = ntohl(fvers); + m->mf_fflags = ntohl(fflags); + m->mf_pflags = ntohl(pflags); + + /* check for version compatibility */ + if (m->mf_fvers == 1 || + m->mf_fvers > SMFI_VERSION) + { + if (tTd(64, 5)) + dprintf("milter_negotiate(%s): version %lu != MTA milter version %d\n", + m->mf_name, m->mf_fvers, SMFI_VERSION); + if (LogLevel > 0) + sm_syslog(LOG_ERR, e->e_id, + "milter_negotiate(%s): version %ld != MTA milter version %d", + m->mf_name, m->mf_fvers, SMFI_VERSION); + milter_error(m); + return -1; + } + + /* check for filter feature mismatch */ + if ((m->mf_fflags & SMFI_CURR_ACTS) != m->mf_fflags) + { + if (tTd(64, 5)) + dprintf("milter_negotiate(%s): filter abilities 0x%lx != MTA milter abilities 0x%lx\n", + m->mf_name, m->mf_fflags, + (u_long) SMFI_CURR_ACTS); + if (LogLevel > 0) + sm_syslog(LOG_ERR, e->e_id, + "milter_negotiate(%s): filter abilities 0x%lx != MTA milter abilities 0x%lx\n", + m->mf_name, m->mf_fflags, + (u_long) SMFI_CURR_ACTS); + milter_error(m); + return -1; + } + + /* check for protocol feature mismatch */ + if ((m->mf_pflags & SMFI_CURR_PROT) != m->mf_pflags) + { + if (tTd(64, 5)) + dprintf("milter_negotiate(%s): protocol abilities 0x%lx != MTA milter abilities 0x%lx\n", + m->mf_name, m->mf_pflags, + (u_long) SMFI_CURR_PROT); + if (LogLevel > 0) + sm_syslog(LOG_ERR, e->e_id, + "milter_negotiate(%s): protocol abilities 0x%lx != MTA milter abilities 0x%lx\n", + m->mf_name, m->mf_pflags, + (u_long) SMFI_CURR_PROT); + milter_error(m); + return -1; + } + + if (tTd(64, 5)) + dprintf("milter_negotiate(%s): version %lu, fflags 0x%lx, pflags 0x%lx\n", + m->mf_name, m->mf_fvers, m->mf_fflags, m->mf_pflags); + return 0; +} +/* +** MILTER_PER_CONNECTION_CHECK -- checks on per-connection commands +** +** Reduce code duplication by putting these checks in one place +** +** Parameters: +** e -- current envelope. +** +** Returns: +** none +*/ + +static void +milter_per_connection_check(e) + ENVELOPE *e; +{ + int i; + + /* see if we are done with any of the filters */ + for (i = 0; InputFilters[i] != NULL; i++) + { + struct milter *m = InputFilters[i]; + + if (m->mf_state == SMFS_DONE) + milter_quit_filter(m, e); + } +} +/* +** MILTER_ERROR -- Put a milter filter into error state +** +** Parameters: +** m -- the broken filter. +** +** Returns: +** none +*/ + +static void +milter_error(m) + struct milter *m; +{ + /* + ** We could send a quit here but + ** we may have gotten here due to + ** an I/O error so we don't want + ** to try to make things worse. + */ + + if (m->mf_sock >= 0) + { + (void) close(m->mf_sock); + m->mf_sock = -1; + } + m->mf_state = SMFS_ERROR; +} +/* +** MILTER_HEADERS -- send headers to a single milter filter +** +** Parameters: +** m -- current filter. +** e -- current envelope. +** state -- return state from response. +** +** Returns: +** response string (may be NULL) +*/ + +static char * +milter_headers(m, e, state) + struct milter *m; + ENVELOPE *e; + char *state; +{ + char *response = NULL; + HDR *h; + + for (h = e->e_header; h != NULL; h = h->h_link) + { + char *buf; + ssize_t s; + + /* don't send over deleted headers */ + if (h->h_value == NULL) + { + /* strip H_USER so not counted in milter_chgheader() */ + h->h_flags &= ~H_USER; + continue; + } + + /* skip auto-generated */ + if (!bitset(H_USER, h->h_flags)) + continue; + + if (tTd(64, 10)) + dprintf("milter_headers: %s: %s\n", + h->h_field, h->h_value); + + s = strlen(h->h_field) + 1 + + strlen(h->h_value) + 1; + buf = (char *) xalloc(s); + snprintf(buf, s, "%s%c%s", h->h_field, '\0', h->h_value); + + /* send it over */ + response = milter_send_command(m, SMFIC_HEADER, buf, + s, e, state); + free(buf); + if (m->mf_state == SMFS_ERROR || + m->mf_state == SMFS_DONE || + *state != SMFIR_CONTINUE) + break; + } + return response; +} +/* +** MILTER_BODY -- send the body to a filter +** +** Parameters: +** m -- current filter. +** e -- current envelope. +** state -- return state from response. +** +** Returns: +** response string (may be NULL) +*/ + +static char * +milter_body(m, e, state) + struct milter *m; + ENVELOPE *e; + char *state; +{ + char bufchar = '\0'; + char prevchar = '\0'; + int c; + char *response = NULL; + char *bp; + char buf[MILTER_CHUNK_SIZE]; + + if (tTd(64, 10)) + dprintf("milter_body\n"); + + if (bfrewind(e->e_dfp) < 0) + { + ExitStat = EX_IOERR; + *state = SMFIR_TEMPFAIL; + syserr("milter_body: %s/df%s: rewind error", + qid_printqueue(e->e_queuedir), e->e_id); + return NULL; + } + + bp = buf; + while ((c = getc(e->e_dfp)) != EOF) + { + /* Change LF to CRLF */ + if (c == '\n') + { + /* Not a CRLF already? */ + if (prevchar != '\r') + { + /* Room for CR now? */ + if (bp + 2 > &buf[sizeof buf]) + { + /* No room, buffer LF */ + bufchar = c; + + /* and send CR now */ + c = '\r'; + } + else + { + /* Room to do it now */ + *bp++ = '\r'; + prevchar = '\r'; + } + } + } + *bp++ = (char) c; + prevchar = c; + if (bp >= &buf[sizeof buf]) + { + /* send chunk */ + response = milter_send_command(m, SMFIC_BODY, buf, + bp - buf, e, state); + bp = buf; + if (bufchar != '\0') + { + *bp++ = bufchar; + bufchar = '\0'; + prevchar = bufchar; + } + } + if (m->mf_state == SMFS_ERROR || + m->mf_state == SMFS_DONE || + *state != SMFIR_CONTINUE) + break; + } + + /* check for read errors */ + if (ferror(e->e_dfp)) + { + ExitStat = EX_IOERR; + if (*state == SMFIR_CONTINUE || + *state == SMFIR_ACCEPT) + { + *state = SMFIR_TEMPFAIL; + if (response != NULL) + { + free(response); + response = NULL; + } + } + syserr("milter_body: %s/df%s: read error", + qid_printqueue(e->e_queuedir), e->e_id); + return response; + } + + /* send last body chunk */ + if (bp > buf && + m->mf_state != SMFS_ERROR && + m->mf_state != SMFS_DONE && + *state == SMFIR_CONTINUE) + { + /* send chunk */ + response = milter_send_command(m, SMFIC_BODY, buf, bp - buf, + e, state); + bp = buf; + } + return response; +} + +/* +** Actions +*/ + +/* +** MILTER_ADDHEADER -- Add the supplied header to the message +** +** Parameters: +** response -- encoded form of header/value. +** rlen -- length of response. +** e -- current envelope. +** +** Returns: +** none +*/ + +static void +milter_addheader(response, rlen, e) + char *response; + ssize_t rlen; + ENVELOPE *e; +{ + char *val; + + if (tTd(64, 10)) + dprintf("milter_addheader: "); + + /* sanity checks */ + if (response == NULL) + { + if (tTd(64, 10)) + dprintf("NULL response\n"); + return; + } + + if (rlen < 2 || strlen(response) + 1 >= (size_t) rlen) + { + if (tTd(64, 10)) + dprintf("didn't follow protocol (total len)\n"); + return; + } + + /* Find separating NUL */ + val = response + strlen(response) + 1; + + /* another sanity check */ + if (strlen(response) + strlen(val) + 2 != (size_t) rlen) + { + if (tTd(64, 10)) + dprintf("didn't follow protocol (part len)\n"); + return; + } + + if (*response == '\0') + { + if (tTd(64, 10)) + dprintf("empty field name\n"); + return; + } + + /* add to e_msgsize */ + e->e_msgsize += strlen(response) + 2 + strlen(val); + + if (tTd(64, 10)) + dprintf("Add %s: %s\n", response, val); + + addheader(newstr(response), val, H_USER, &e->e_header); +} +/* +** MILTER_CHANGEHEADER -- Change the supplied header in the message +** +** Parameters: +** response -- encoded form of header/index/value. +** rlen -- length of response. +** e -- current envelope. +** +** Returns: +** none +*/ + +static void +milter_changeheader(response, rlen, e) + char *response; + ssize_t rlen; + ENVELOPE *e; +{ + mi_int32 i, index; + char *field, *val; + HDR *h; + + if (tTd(64, 10)) + dprintf("milter_changeheader: "); + + /* sanity checks */ + if (response == NULL) + { + if (tTd(64, 10)) + dprintf("NULL response\n"); + return; + } + + if (rlen < 2 || strlen(response) + 1 >= (size_t) rlen) + { + if (tTd(64, 10)) + dprintf("didn't follow protocol (total len)\n"); + return; + } + + /* Find separating NUL */ + (void) memcpy((char *) &i, response, MILTER_LEN_BYTES); + index = ntohl(i); + field = response + MILTER_LEN_BYTES; + val = field + strlen(field) + 1; + + /* another sanity check */ + if (MILTER_LEN_BYTES + strlen(field) + 1 + + strlen(val) + 1 != (size_t) rlen) + { + if (tTd(64, 10)) + dprintf("didn't follow protocol (part len)\n"); + return; + } + + if (*field == '\0') + { + if (tTd(64, 10)) + dprintf("empty field name\n"); + return; + } + + for (h = e->e_header; h != NULL; h = h->h_link) + { + if (bitset(H_USER, h->h_flags) && + strcasecmp(h->h_field, field) == 0 && + --index <= 0) + break; + } + + if (h == NULL) + { + if (*val == '\0') + { + if (tTd(64, 10)) + dprintf("Delete (noop) %s:\n", field); + } + else + { + /* treat modify value with no existing header as add */ + if (tTd(64, 10)) + dprintf("Add %s: %s\n", field, val); + + addheader(newstr(field), val, H_USER, &e->e_header); + } + return; + } + + if (tTd(64, 10)) + { + if (*val == '\0') + { + dprintf("Delete %s: %s\n", field, + h->h_value == NULL ? "<NULL>" : h->h_value); + } + else + { + dprintf("Change %s: from %s to %s\n", + field, + h->h_value == NULL ? "<NULL>" : h->h_value, + val); + } + } + + if (h->h_value != NULL) + { + e->e_msgsize -= strlen(h->h_value); + free(h->h_value); + } + + if (*val == '\0') + { + /* Remove "Field: " from message size */ + e->e_msgsize -= strlen(h->h_field) + 2; + h->h_value = NULL; + } + else + { + h->h_value = newstr(val); + e->e_msgsize += strlen(h->h_value); + } +} +/* +** MILTER_ADDRCPT -- Add the supplied recipient to the message +** +** Parameters: +** response -- encoded form of recipient address. +** rlen -- length of response. +** e -- current envelope. +** +** Returns: +** none +*/ + +static void +milter_addrcpt(response, rlen, e) + char *response; + ssize_t rlen; + ENVELOPE *e; +{ + if (tTd(64, 10)) + dprintf("milter_addrcpt: "); + + /* sanity checks */ + if (response == NULL) + { + if (tTd(64, 10)) + dprintf("NULL response\n"); + return; + } + + if (*response == '\0' || + strlen(response) + 1 != (size_t) rlen) + { + if (tTd(64, 10)) + dprintf("didn't follow protocol (total len %d != rlen %d)\n", + strlen(response), rlen -1); + return; + } + + if (tTd(64, 10)) + dprintf("%s\n", response); + (void) sendtolist(response, NULLADDR, &e->e_sendqueue, 0, e); + return; +} +/* +** MILTER_DELRCPT -- Delete the supplied recipient from the message +** +** Parameters: +** response -- encoded form of recipient address. +** rlen -- length of response. +** e -- current envelope. +** +** Returns: +** none +*/ + +static void +milter_delrcpt(response, rlen, e) + char *response; + ssize_t rlen; + ENVELOPE *e; +{ + if (tTd(64, 10)) + dprintf("milter_delrcpt: "); + + /* sanity checks */ + if (response == NULL) + { + if (tTd(64, 10)) + dprintf("NULL response\n"); + return; + } + + if (*response == '\0' || + strlen(response) + 1 != (size_t) rlen) + { + if (tTd(64, 10)) + dprintf("didn't follow protocol (total len)\n"); + return; + } + + if (tTd(64, 10)) + dprintf("%s\n", response); + (void) removefromlist(response, &e->e_sendqueue, e); + return; +} +/* +** MILTER_REPLBODY -- Replace the current df file with new body +** +** Parameters: +** response -- encoded form of new body. +** rlen -- length of response. +** newfilter -- if first time called by a new filter +** e -- current envelope. +** +** Returns: +** 0 upon success, -1 upon failure +*/ + +static int +milter_replbody(response, rlen, newfilter, e) + char *response; + ssize_t rlen; + bool newfilter; + ENVELOPE *e; +{ + static char prevchar; + int i; + + if (tTd(64, 10)) + dprintf("milter_replbody\n"); + + /* If a new filter, reset previous character and truncate df */ + if (newfilter) + { + off_t prevsize = 0; + char dfname[MAXPATHLEN]; + + (void) strlcpy(dfname, queuename(e, 'd'), sizeof dfname); + + /* Reset prevchar */ + prevchar = '\0'; + + /* Get the current df information */ + if (bitset(EF_HAS_DF, e->e_flags) && e->e_dfp != NULL) + { + int afd; + struct stat st; + + afd = fileno(e->e_dfp); + if (afd > 0 && fstat(afd, &st) == 0) + prevsize = st.st_size; + } + + /* truncate current df file */ + if (bftruncate(e->e_dfp) < 0) + { + MILTER_DF_ERROR("milter_reopen_df: bftruncate %s: %s"); + return -1; + } + else + { + if (prevsize > e->e_msgsize) + e->e_msgsize = 0; + else + e->e_msgsize -= prevsize; + } + } + + if (response == NULL) + { + /* Flush the buffered '\r' */ + if (prevchar == '\r') + { + (void) putc(prevchar, e->e_dfp); + e->e_msgsize++; + } + return 0; + } + + for (i = 0; i < rlen; i++) + { + /* Buffered char from last chunk */ + if (i == 0 && prevchar == '\r') + { + /* Not CRLF, output prevchar */ + if (response[i] != '\n') + { + (void) putc(prevchar, e->e_dfp); + e->e_msgsize++; + } + prevchar = '\0'; + } + + /* Turn CRLF into LF */ + if (response[i] == '\r') + { + /* check if at end of chunk */ + if (i + 1 < rlen) + { + /* If LF, strip CR */ + if (response[i + 1] == '\n') + i++; + } + else + { + /* check next chunk */ + prevchar = '\r'; + continue; + } + } + (void) putc(response[i], e->e_dfp); + e->e_msgsize++; + } + return 0; +} + +/* +** MTA callouts +*/ + +/* +** MILTER_INIT -- open and negotiate with all of the filters +** +** Parameters: +** e -- current envelope. +** state -- return state from response. +** +** Returns: +** none +*/ + +/* ARGSUSED */ +void +milter_init(e, state) + ENVELOPE *e; + char *state; +{ + int i; + + if (tTd(64, 10)) + dprintf("milter_init\n"); + + *state = SMFIR_CONTINUE; + for (i = 0; InputFilters[i] != NULL; i++) + { + struct milter *m = InputFilters[i]; + + m->mf_sock = milter_open(m, FALSE, e); + if (m->mf_state == SMFS_ERROR) + { + MILTER_CHECK_ERROR(continue); + break; + } + + if (m->mf_sock < 0 || + milter_negotiate(m, e) < 0 || + m->mf_state == SMFS_ERROR) + { + if (tTd(64, 5)) + dprintf("milter_init(%s): failed to %s\n", + m->mf_name, + m->mf_sock < 0 ? "open" : "negotiate"); + + /* if negotation failure, close socket */ + if (m->mf_sock >= 0) + { + (void) close(m->mf_sock); + m->mf_sock = -1; + } + milter_error(m); + if (m->mf_state == SMFS_ERROR) + { + MILTER_CHECK_ERROR(continue); + break; + } + } + } + + /* + ** If something temp/perm failed with one of the filters, + ** we won't be using any of them, so clear any existing + ** connections. + */ + + if (*state != SMFIR_CONTINUE) + milter_quit(e); +} +/* +** MILTER_CONNECT -- send connection info to milter filters +** +** Parameters: +** hostname -- hostname of remote machine. +** addr -- address of remote machine. +** e -- current envelope. +** state -- return state from response. +** +** Returns: +** response string (may be NULL) +*/ + +char * +milter_connect(hostname, addr, e, state) + char *hostname; + SOCKADDR addr; + ENVELOPE *e; + char *state; +{ + char family; + u_short port; + char *buf, *bp; + char *response; + char *sockinfo = NULL; + ssize_t s; +# if NETINET6 + char buf6[INET6_ADDRSTRLEN]; +# endif /* NETINET6 */ + + if (tTd(64, 10)) + dprintf("milter_connect(%s)\n", hostname); + + /* gather data */ + switch (addr.sa.sa_family) + { +# if NETUNIX + case AF_UNIX: + family = SMFIA_UNIX; + port = htons(0); + sockinfo = addr.sunix.sun_path; + break; +# endif /* NETUNIX */ + +# if NETINET + case AF_INET: + family = SMFIA_INET; + port = htons(addr.sin.sin_port); + sockinfo = (char *) inet_ntoa(addr.sin.sin_addr); + break; +# endif /* NETINET */ + +# if NETINET6 + case AF_INET6: + family = SMFIA_INET6; + port = htons(addr.sin6.sin6_port); + sockinfo = anynet_ntop(&addr.sin6.sin6_addr, buf6, + sizeof buf6); + if (sockinfo == NULL) + sockinfo = ""; + break; +# endif /* NETINET6 */ + + default: + family = SMFIA_UNKNOWN; + break; + } + + s = strlen(hostname) + 1 + sizeof(family); + if (family != SMFIA_UNKNOWN) + s += sizeof(port) + strlen(sockinfo) + 1; + + buf = (char *)xalloc(s); + bp = buf; + + /* put together data */ + (void) memcpy(bp, hostname, strlen(hostname)); + bp += strlen(hostname); + *bp++ = '\0'; + (void) memcpy(bp, &family, sizeof family); + bp += sizeof family; + if (family != SMFIA_UNKNOWN) + { + (void) memcpy(bp, &port, sizeof port); + bp += sizeof port; + + /* include trailing '\0' */ + (void) memcpy(bp, sockinfo, strlen(sockinfo) + 1); + } + + response = milter_command(SMFIC_CONNECT, buf, s, + MilterConnectMacros, e, state); + free(buf); + + /* + ** If this message connection is done for, + ** close the filters. + */ + + if (*state != SMFIR_CONTINUE) + milter_quit(e); + else + milter_per_connection_check(e); + + /* + ** SMFIR_REPLYCODE can't work with connect due to + ** the requirements of SMTP. Therefore, ignore the + ** reply code text but keep the state it would reflect. + */ + + if (*state == SMFIR_REPLYCODE) + { + if (response != NULL && + *response == '4') + *state = SMFIR_TEMPFAIL; + else + *state = SMFIR_REJECT; + if (response != NULL) + { + free(response); + response = NULL; + } + } + return response; +} +/* +** MILTER_HELO -- send SMTP HELO/EHLO command info to milter filters +** +** Parameters: +** helo -- argument to SMTP HELO/EHLO command. +** e -- current envelope. +** state -- return state from response. +** +** Returns: +** response string (may be NULL) +*/ + +char * +milter_helo(helo, e, state) + char *helo; + ENVELOPE *e; + char *state; +{ + char *response; + + if (tTd(64, 10)) + dprintf("milter_helo(%s)\n", helo); + + response = milter_command(SMFIC_HELO, helo, strlen(helo) + 1, + MilterHeloMacros, e, state); + milter_per_connection_check(e); + return response; +} +/* +** MILTER_ENVFROM -- send SMTP MAIL command info to milter filters +** +** Parameters: +** args -- SMTP MAIL command args (args[0] == sender). +** e -- current envelope. +** state -- return state from response. +** +** Returns: +** response string (may be NULL) +*/ + +char * +milter_envfrom(args, e, state) + char **args; + ENVELOPE *e; + char *state; +{ + int i; + char *buf, *bp; + char *response; + ssize_t s; + + if (tTd(64, 10)) + { + dprintf("milter_envfrom:"); + for (i = 0; args[i] != NULL; i++) + dprintf(" %s", args[i]); + dprintf("\n"); + } + + /* sanity check */ + if (args[0] == NULL) + { + *state = SMFIR_REJECT; + return NULL; + } + + /* new message, so ... */ + for (i = 0; InputFilters[i] != NULL; i++) + { + struct milter *m = InputFilters[i]; + + switch (m->mf_state) + { + case SMFS_INMSG: + /* abort in message filters */ + milter_abort_filter(m, e); + /* FALLTHROUGH */ + + case SMFS_DONE: + /* reset done filters */ + m->mf_state = SMFS_OPEN; + break; + } + } + + /* put together data */ + s = 0; + for (i = 0; args[i] != NULL; i++) + s += strlen(args[i]) + 1; + buf = (char *)xalloc(s); + bp = buf; + for (i = 0; args[i] != NULL; i++) + { + (void) strlcpy(bp, args[i], s - (bp - buf)); + bp += strlen(bp) + 1; + } + + /* send it over */ + response = milter_command(SMFIC_MAIL, buf, s, + MilterEnvFromMacros, e, state); + free(buf); + + /* + ** If filter rejects/discards a per message command, + ** abort the other filters since we are done with the + ** current message. + */ + + MILTER_CHECK_DONE_MSG(); + return response; +} +/* +** MILTER_ENVRCPT -- send SMTP RCPT command info to milter filters +** +** Parameters: +** args -- SMTP MAIL command args (args[0] == recipient). +** e -- current envelope. +** state -- return state from response. +** +** Returns: +** response string (may be NULL) +*/ + +char * +milter_envrcpt(args, e, state) + char **args; + ENVELOPE *e; + char *state; +{ + int i; + char *buf, *bp; + char *response; + ssize_t s; + + if (tTd(64, 10)) + { + dprintf("milter_envrcpt:"); + for (i = 0; args[i] != NULL; i++) + dprintf(" %s", args[i]); + dprintf("\n"); + } + + /* sanity check */ + if (args[0] == NULL) + { + *state = SMFIR_REJECT; + return NULL; + } + + /* put together data */ + s = 0; + for (i = 0; args[i] != NULL; i++) + s += strlen(args[i]) + 1; + buf = (char *)xalloc(s); + bp = buf; + for (i = 0; args[i] != NULL; i++) + { + (void) strlcpy(bp, args[i], s - (bp - buf)); + bp += strlen(bp) + 1; + } + + /* send it over */ + response = milter_command(SMFIC_RCPT, buf, s, + MilterEnvRcptMacros, e, state); + free(buf); + return response; +} +/* +** MILTER_DATA -- send message headers/body and gather final message results +** +** Parameters: +** e -- current envelope. +** state -- return state from response. +** +** Returns: +** response string (may be NULL) +** +** Side effects: +** - Uses e->e_dfp for access to the body +** - Can call the various milter action routines to +** modify the envelope or message. +*/ + +# define MILTER_CHECK_RESULTS() \ + if (*state == SMFIR_ACCEPT || \ + m->mf_state == SMFS_DONE || \ + m->mf_state == SMFS_ERROR) \ + { \ + if (m->mf_state != SMFS_ERROR) \ + m->mf_state = SMFS_DONE; \ + continue; /* to next filter */ \ + } \ + if (*state != SMFIR_CONTINUE) \ + { \ + m->mf_state = SMFS_DONE; \ + goto finishup; \ + } + +char * +milter_data(e, state) + ENVELOPE *e; + char *state; +{ + bool replbody = FALSE; /* milter_replbody() called? */ + bool replfailed = FALSE; /* milter_replbody() failed? */ + bool rewind = FALSE; /* rewind df file? */ + bool dfopen = FALSE; /* df open for writing? */ + bool newfilter; /* reset on each new filter */ + char rcmd; + int i; + int save_errno; + char *response = NULL; + time_t eomsent; + ssize_t rlen; + + if (tTd(64, 10)) + dprintf("milter_data\n"); + + *state = SMFIR_CONTINUE; + + /* + ** XXX: Should actually send body chunks to each filter + ** a chunk at a time instead of sending the whole body to + ** each filter in turn. However, only if the filters don't + ** change the body. + */ + + for (i = 0; InputFilters[i] != NULL; i++) + { + struct milter *m = InputFilters[i]; + + if (*state != SMFIR_CONTINUE && + *state != SMFIR_ACCEPT) + { + /* + ** A previous filter has dealt with the message, + ** safe to stop processing the filters. + */ + + break; + } + + /* Now reset state for later evaluation */ + *state = SMFIR_CONTINUE; + newfilter = TRUE; + + /* sanity checks */ + if (m->mf_sock < 0 || + (m->mf_state != SMFS_OPEN && m->mf_state != SMFS_INMSG)) + continue; + + m->mf_state = SMFS_INMSG; + + /* check if filter wants the headers */ + if (!bitset(SMFIP_NOHDRS, m->mf_pflags)) + { + response = milter_headers(m, e, state); + MILTER_CHECK_RESULTS(); + } + + /* check if filter wants EOH */ + if (!bitset(SMFIP_NOEOH, m->mf_pflags)) + { + if (tTd(64, 10)) + dprintf("milter_data: eoh\n"); + + /* send it over */ + response = milter_send_command(m, SMFIC_EOH, NULL, 0, + e, state); + MILTER_CHECK_RESULTS(); + } + + /* check if filter wants the body */ + if (!bitset(SMFIP_NOBODY, m->mf_pflags) && + e->e_dfp != NULL) + { + rewind = TRUE; + response = milter_body(m, e, state); + MILTER_CHECK_RESULTS(); + } + + /* send the final body chunk */ + (void) milter_write(m, SMFIC_BODYEOB, NULL, 0, + m->mf_timeout[SMFTO_WRITE], e); + + /* Get time EOM sent for timeout */ + eomsent = curtime(); + + /* deal with the possibility of multiple responses */ + while (*state == SMFIR_CONTINUE) + { + /* Check total timeout from EOM to final ACK/NAK */ + if (m->mf_timeout[SMFTO_EOM] > 0 && + curtime() - eomsent >= m->mf_timeout[SMFTO_EOM]) + { + if (tTd(64, 5)) + dprintf("milter_data(%s): EOM ACK/NAK timeout\n", + m->mf_name); + if (LogLevel > 0) + sm_syslog(LOG_ERR, e->e_id, + "milter_data(%s): EOM ACK/NAK timeout\n", + m->mf_name); + milter_error(m); + MILTER_CHECK_ERROR(continue); + break; + } + + response = milter_read(m, &rcmd, &rlen, + m->mf_timeout[SMFTO_READ], e); + if (m->mf_state == SMFS_ERROR) + break; + + if (tTd(64, 10)) + dprintf("milter_data(%s): state %c\n", + m->mf_name, (char) rcmd); + + switch (rcmd) + { + case SMFIR_REPLYCODE: + MILTER_CHECK_REPLYCODE("554 5.7.1 Command rejected"); + *state = rcmd; + m->mf_state = SMFS_DONE; + break; + + case SMFIR_REJECT: + case SMFIR_DISCARD: + case SMFIR_TEMPFAIL: + *state = rcmd; + m->mf_state = SMFS_DONE; + break; + + case SMFIR_CONTINUE: + case SMFIR_ACCEPT: + /* this filter is done with message */ + if (replfailed) + *state = SMFIR_TEMPFAIL; + else + *state = SMFIR_ACCEPT; + m->mf_state = SMFS_DONE; + break; + + case SMFIR_PROGRESS: + break; + + case SMFIR_ADDHEADER: + if (!bitset(SMFIF_ADDHDRS, m->mf_fflags)) + { + if (LogLevel > 9) + sm_syslog(LOG_WARNING, e->e_id, + "milter_data(%s): lied about adding headers, honoring request anyway", + m->mf_name); + } + milter_addheader(response, rlen, e); + break; + + case SMFIR_CHGHEADER: + if (!bitset(SMFIF_CHGHDRS, m->mf_fflags)) + { + if (LogLevel > 9) + sm_syslog(LOG_WARNING, e->e_id, + "milter_data(%s): lied about changing headers, honoring request anyway", + m->mf_name); + } + milter_changeheader(response, rlen, e); + break; + + case SMFIR_ADDRCPT: + if (!bitset(SMFIF_ADDRCPT, m->mf_fflags)) + { + if (LogLevel > 9) + sm_syslog(LOG_WARNING, e->e_id, + "milter_data(%s) lied about adding recipients, honoring request anyway", + m->mf_name); + } + milter_addrcpt(response, rlen, e); + break; + + case SMFIR_DELRCPT: + if (!bitset(SMFIF_DELRCPT, m->mf_fflags)) + { + if (LogLevel > 9) + sm_syslog(LOG_WARNING, e->e_id, + "milter_data(%s): lied about removing recipients, honoring request anyway", + m->mf_name); + } + milter_delrcpt(response, rlen, e); + break; + + case SMFIR_REPLBODY: + if (!bitset(SMFIF_MODBODY, m->mf_fflags)) + { + if (LogLevel > 0) + sm_syslog(LOG_ERR, e->e_id, + "milter_data(%s): lied about replacing body, rejecting request and tempfailing message", + m->mf_name); + replfailed = TRUE; + break; + } + + /* already failed in attempt */ + if (replfailed) + break; + + if (!dfopen) + { + if (milter_reopen_df(e) < 0) + { + replfailed = TRUE; + break; + } + dfopen = TRUE; + rewind = TRUE; + } + + if (milter_replbody(response, rlen, + newfilter, e) < 0) + replfailed = TRUE; + newfilter = FALSE; + replbody = TRUE; + break; + + default: + /* Invalid response to command */ + if (LogLevel > 0) + sm_syslog(LOG_ERR, e->e_id, + "milter_data(%s): returned bogus response %c", + m->mf_name, rcmd); + milter_error(m); + break; + } + if (rcmd != SMFIR_REPLYCODE && + response != NULL) + { + free(response); + response = NULL; + } + + if (m->mf_state == SMFS_ERROR) + break; + } + + if (replbody && !replfailed) + { + /* flush possible buffered character */ + milter_replbody(NULL, 0, !replbody, e); + replbody = FALSE; + } + + if (m->mf_state == SMFS_ERROR) + { + MILTER_CHECK_ERROR(continue); + goto finishup; + } + } + +finishup: + /* leave things in the expected state if we touched it */ + if (replfailed) + { + if (*state == SMFIR_CONTINUE || + *state == SMFIR_ACCEPT) + { + *state = SMFIR_TEMPFAIL; + if (response != NULL) + { + free(response); + response = NULL; + } + } + + if (dfopen) + { + (void) fclose(e->e_dfp); + e->e_dfp = NULL; + e->e_flags &= ~EF_HAS_DF; + dfopen = FALSE; + } + rewind = FALSE; + } + + if ((dfopen && milter_reset_df(e) < 0) || + (rewind && bfrewind(e->e_dfp) < 0)) + { + save_errno = errno; + ExitStat = EX_IOERR; + + /* + ** If filter told us to keep message but we had + ** an error, we can't really keep it, tempfail it. + */ + + if (*state == SMFIR_CONTINUE || + *state == SMFIR_ACCEPT) + { + *state = SMFIR_TEMPFAIL; + if (response != NULL) + { + free(response); + response = NULL; + } + } + + errno = save_errno; + syserr("milter_data: %s/df%s: read error", + qid_printqueue(e->e_queuedir), e->e_id); + } + MILTER_CHECK_DONE_MSG(); + return response; +} +/* +** MILTER_QUIT -- informs the filter(s) we are done and closes connection(s) +** +** Parameters: +** e -- current envelope. +** +** Returns: +** none +*/ + +void +milter_quit(e) + ENVELOPE *e; +{ + int i; + + if (tTd(64, 10)) + dprintf("milter_quit\n"); + + for (i = 0; InputFilters[i] != NULL; i++) + milter_quit_filter(InputFilters[i], e); +} +/* +** MILTER_ABORT -- informs the filter(s) that we are aborting current message +** +** Parameters: +** e -- current envelope. +** +** Returns: +** none +*/ + +void +milter_abort(e) + ENVELOPE *e; +{ + int i; + + if (tTd(64, 10)) + dprintf("milter_abort\n"); + + for (i = 0; InputFilters[i] != NULL; i++) + { + struct milter *m = InputFilters[i]; + + /* sanity checks */ + if (m->mf_sock < 0 || m->mf_state != SMFS_INMSG) + continue; + + milter_abort_filter(m, e); + } +} +#endif /* _FFR_MILTER */ |