diff options
Diffstat (limited to 'contrib/sendmail/src/milter.c')
-rw-r--r-- | contrib/sendmail/src/milter.c | 403 |
1 files changed, 291 insertions, 112 deletions
diff --git a/contrib/sendmail/src/milter.c b/contrib/sendmail/src/milter.c index c25101c..b89fac0 100644 --- a/contrib/sendmail/src/milter.c +++ b/contrib/sendmail/src/milter.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999-2003 Sendmail, Inc. and its suppliers. + * Copyright (c) 1999-2004 Sendmail, Inc. and its suppliers. * All rights reserved. * * By using this file, you agree to the terms and conditions set @@ -10,7 +10,7 @@ #include <sendmail.h> -SM_RCSID("@(#)$Id: milter.c,v 8.197.2.10 2003/12/01 23:57:44 msk Exp $") +SM_RCSID("@(#)$Id: milter.c,v 8.225 2004/07/08 21:52:20 ca Exp $") #if MILTER # include <libmilter/mfapi.h> @@ -18,9 +18,13 @@ SM_RCSID("@(#)$Id: milter.c,v 8.197.2.10 2003/12/01 23:57:44 msk Exp $") # include <errno.h> # include <sys/time.h> +# include <sys/uio.h> # if NETINET || NETINET6 # include <arpa/inet.h> +# if _FFR_MILTER_NAGLE +# include <netinet/tcp.h> +# endif /* _FFR_MILTER_NAGLE */ # endif /* NETINET || NETINET6 */ # include <sm/fdset.h> @@ -34,9 +38,9 @@ static char *MilterConnectMacros[MAXFILTERMACROS + 1]; static char *MilterHeloMacros[MAXFILTERMACROS + 1]; static char *MilterEnvFromMacros[MAXFILTERMACROS + 1]; static char *MilterEnvRcptMacros[MAXFILTERMACROS + 1]; -#if _FFR_MILTER_MACROS_EOM +static char *MilterDataMacros[MAXFILTERMACROS + 1]; static char *MilterEOMMacros[MAXFILTERMACROS + 1]; -#endif /* _FFR_MILTER_MACROS_EOM */ +static size_t MilterMaxDataSize = MILTER_MAX_DATA_SIZE; # define MILTER_CHECK_DONE_MSG() \ if (*state == SMFIR_REPLYCODE || \ @@ -48,8 +52,7 @@ static char *MilterEOMMacros[MAXFILTERMACROS + 1]; milter_abort(e); \ } -# if _FFR_QUARANTINE -# define MILTER_CHECK_ERROR(initial, action) \ +# define MILTER_CHECK_ERROR(initial, action) \ if (!initial && tTd(71, 100)) \ { \ if (e->e_quarmsg == NULL) \ @@ -76,15 +79,6 @@ static char *MilterEOMMacros[MAXFILTERMACROS + 1]; *state = SMFIR_REJECT; \ else \ action; -# else /* _FFR_QUARANTINE */ -# define MILTER_CHECK_ERROR(initial, action) \ - if (bitnset(SMF_TEMPFAIL, m->mf_flags)) \ - *state = SMFIR_TEMPFAIL; \ - else if (bitnset(SMF_REJECT, m->mf_flags)) \ - *state = SMFIR_REJECT; \ - else \ - action; -# endif /* _FFR_QUARANTINE */ # define MILTER_CHECK_REPLYCODE(default) \ if (response == NULL || \ @@ -346,6 +340,11 @@ milter_read(m, cmd, rlen, to, e) time_t readstart = 0; ssize_t expl; mi_int32 i; +# if _FFR_MILTER_NAGLE +# ifdef TCP_CORK + int cork = 0; +# endif +# endif /* _FFR_MILTER_NAGLE */ char *buf; char data[MILTER_LEN_BYTES + 1]; @@ -355,9 +354,24 @@ milter_read(m, cmd, rlen, to, e) if (to > 0) readstart = curtime(); +# if _FFR_MILTER_NAGLE +# ifdef TCP_CORK + setsockopt(m->mf_sock, IPPROTO_TCP, TCP_CORK, (char *)&cork, + sizeof(cork)); +# endif +# endif /* _FFR_MILTER_NAGLE */ + if (milter_sysread(m, data, sizeof data, to, e) == NULL) return NULL; +# if _FFR_MILTER_NAGLE +# ifdef TCP_CORK + cork = 1; + setsockopt(m->mf_sock, IPPROTO_TCP, TCP_CORK, (char *)&cork, + sizeof(cork)); +# endif +# endif /* _FFR_MILTER_NAGLE */ + /* reset timeout */ if (to > 0) { @@ -418,6 +432,7 @@ milter_read(m, cmd, rlen, to, e) *rlen = expl; return buf; } + /* ** MILTER_WRITE -- write to a remote milter filter ** @@ -446,11 +461,19 @@ milter_write(m, cmd, buf, len, to, e) { time_t writestart = (time_t) 0; ssize_t sl, i; + int num_vectors; mi_int32 nl; char data[MILTER_LEN_BYTES + 1]; bool started = false; + struct iovec vector[2]; + + /* + ** At most two buffers will be written, though + ** only one may actually be used (see num_vectors). + ** The first is the size/command and the second is the command data. + */ - if (len < 0 || len > MILTER_CHUNK_SIZE) + if (len < 0 || len > MilterMaxDataSize) { if (tTd(64, 5)) sm_dprintf("milter_write(%s): length %ld out of range\n", @@ -472,65 +495,48 @@ milter_write(m, cmd, buf, len, to, e) data[MILTER_LEN_BYTES] = cmd; sl = MILTER_LEN_BYTES + 1; - if (to > 0) - { - writestart = curtime(); - MILTER_TIMEOUT("write", to, true, started); - } + /* set up the vector for the size / command */ + vector[0].iov_base = (void *) data; + vector[0].iov_len = sl; - /* 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; + /* + ** Determine if there is command data. If so, there will be two + ** vectors. If not, there will be only one. The vectors are set + ** up here and 'num_vectors' and 'sl' are set appropriately. + */ - if (tTd(64, 5)) - sm_dprintf("milter_write (%s): write(%c) returned %ld, expected %ld: %s\n", - m->mf_name, cmd, (long) i, (long) sl, - sm_errstring(save_errno)); - if (MilterLogLevel > 0) - sm_syslog(LOG_ERR, e->e_id, - "Milter (%s): write(%c) returned %ld, expected %ld: %s", - m->mf_name, cmd, (long) i, (long) sl, - sm_errstring(save_errno)); - milter_error(m, e); - return buf; + /* NOTE: len<0 has already been checked for. Pedantic */ + if (len <= 0 || buf == NULL) + { + /* There is no command data -- only a size / command data */ + num_vectors = 1; } + else + { + /* + ** There is both size / command and command data. + ** Set up the vector for the command data. + */ - if (len <= 0 || buf == NULL) - return buf; + num_vectors = 2; + sl += len; + vector[1].iov_base = (void *) buf; + vector[1].iov_len = len; - if (tTd(64, 50)) - sm_dprintf("milter_write(%s): Sending %*s\n", - m->mf_name, (int) len, buf); - started = true; + if (tTd(64, 50)) + sm_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)) - sm_dprintf("milter_write(%s): timeout before data write\n", - m->mf_name); - if (MilterLogLevel > 0) - sm_syslog(LOG_ERR, e->e_id, - "Milter (%s): timeout before data write", - m->mf_name); - milter_error(m, e); - return NULL; - } - else - { - to -= now - writestart; - MILTER_TIMEOUT("write", to, true, started); - } + writestart = curtime(); + MILTER_TIMEOUT("write", to, true, started); } - i = write(m->mf_sock, (void *) buf, len); - if (i != len) + /* write the vector(s) */ + i = writev(m->mf_sock, vector, num_vectors); + if (i != sl) { int save_errno = errno; @@ -541,7 +547,7 @@ milter_write(m, cmd, buf, len, to, e) if (MilterLogLevel > 0) sm_syslog(LOG_ERR, e->e_id, "Milter (%s): write(%c) returned %ld, expected %ld: %s", - m->mf_name, cmd, (long) i, (long) len, + m->mf_name, cmd, (long) i, (long) sl, sm_errstring(save_errno)); milter_error(m, e); return NULL; @@ -1107,6 +1113,16 @@ milter_open(m, parseonly, e) hp = NULL; } # endif /* NETINET6 */ +# if _FFR_MILTER_NAGLE +# ifndef TCP_CORK + { + int nodelay = 1; + + setsockopt(m->mf_sock, IPPROTO_TCP, TCP_NODELAY, + (char *)&nodelay, sizeof(nodelay)); + } +# endif /* TCP_CORK */ +# endif /* _FFR_MILTER_NAGLE */ return sock; } @@ -1270,11 +1286,7 @@ milter_config(spec, list, max) list[0] = NULL; return; } -#if _FFR_MILTER_PERDAEMON p = strpbrk(p, ";,"); -#else /* _FFR_MILTER_PERDAEMON */ - p = strpbrk(p, ","); -#endif /* _FFR_MILTER_PERDAEMON */ if (p != NULL) *p++ = '\0'; @@ -1312,6 +1324,7 @@ milter_parse_timeouts(spec, m) struct milter *m; { char fcode; + int tcode; register char *p; p = spec; @@ -1339,40 +1352,25 @@ milter_parse_timeouts(spec, m) /* p now points to the field body */ p = munchstring(p, &delimptr, ';'); + tcode = -1; /* install the field into the filter struct */ switch (fcode) { case 'C': - m->mf_timeout[SMFTO_CONNECT] = convtime(p, 's'); - if (tTd(64, 5)) - sm_dprintf("X%s: %c=%lu\n", - m->mf_name, fcode, - (unsigned long) m->mf_timeout[SMFTO_CONNECT]); + tcode = SMFTO_CONNECT; break; case 'S': - m->mf_timeout[SMFTO_WRITE] = convtime(p, 's'); - if (tTd(64, 5)) - sm_dprintf("X%s: %c=%lu\n", - m->mf_name, fcode, - (unsigned long) m->mf_timeout[SMFTO_WRITE]); + tcode = SMFTO_WRITE; break; case 'R': - m->mf_timeout[SMFTO_READ] = convtime(p, 's'); - if (tTd(64, 5)) - sm_dprintf("X%s: %c=%lu\n", - m->mf_name, fcode, - (unsigned long) m->mf_timeout[SMFTO_READ]); + tcode = SMFTO_READ; break; case 'E': - m->mf_timeout[SMFTO_EOM] = convtime(p, 's'); - if (tTd(64, 5)) - sm_dprintf("X%s: %c=%lu\n", - m->mf_name, fcode, - (unsigned long) m->mf_timeout[SMFTO_EOM]); + tcode = SMFTO_EOM; break; default: @@ -1383,6 +1381,14 @@ milter_parse_timeouts(spec, m) m->mf_name, fcode); break; } + if (tcode >= 0) + { + m->mf_timeout[tcode] = convtime(p, 's'); + if (tTd(64, 5)) + sm_dprintf("X%s: %c=%ld\n", + m->mf_name, fcode, + (u_long) m->mf_timeout[tcode]); + } p = delimptr; } } @@ -1416,12 +1422,16 @@ static struct milteropt { "macros.envfrom", MO_MACROS_ENVFROM }, # define MO_MACROS_ENVRCPT 0x04 { "macros.envrcpt", MO_MACROS_ENVRCPT }, -# define MO_LOGLEVEL 0x05 - { "loglevel", MO_LOGLEVEL }, -#if _FFR_MILTER_MACROS_EOM +# define MO_MACROS_DATA 0x05 + { "macros.data", MO_MACROS_DATA }, # define MO_MACROS_EOM 0x06 { "macros.eom", MO_MACROS_EOM }, -#endif /* _FFR_MILTER_MACROS_EOM */ +# define MO_LOGLEVEL 0x07 + { "loglevel", MO_LOGLEVEL }, +# if _FFR_MAXDATASIZE +# define MO_MAXDATASIZE 0x08 + { "maxdatasize", MO_MAXDATASIZE }, +# endif /* _FFR_MAXDATASIZE */ { NULL, 0 }, }; @@ -1477,6 +1487,12 @@ milter_set_option(name, val, sticky) MilterLogLevel = atoi(val); break; +#if _FFR_MAXDATASIZE + case MO_MAXDATASIZE: + MilterMaxDataSize = (size_t)atol(val); + break; +#endif /* _FFR_MAXDATASIZE */ + case MO_MACROS_CONNECT: if (macros == NULL) macros = MilterConnectMacros; @@ -1495,13 +1511,16 @@ milter_set_option(name, val, sticky) case MO_MACROS_ENVRCPT: if (macros == NULL) macros = MilterEnvRcptMacros; -#if _FFR_MILTER_MACROS_EOM /* FALLTHROUGH */ case MO_MACROS_EOM: if (macros == NULL) macros = MilterEOMMacros; -#endif /* _FFR_MILTER_MACROS_EOM */ + /* FALLTHROUGH */ + + case MO_MACROS_DATA: + if (macros == NULL) + macros = MilterDataMacros; p = newstr(val); while (*p != '\0') @@ -1567,9 +1586,8 @@ milter_reopen_df(e) ** read only again). ** ** In SuperSafe != SAFE_REALLY 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. + ** buffered file I/O descriptor, still open for writing so there + ** isn't any work to do here (except checking for consistency). */ if (SuperSafe == SAFE_REALLY) @@ -1861,6 +1879,9 @@ milter_send_command(m, command, data, sz, e, state) char rcmd; ssize_t rlen; unsigned long skipflag; +#if _FFR_MILTER_NOHDR_RESP + unsigned long norespflag = 0; +#endif /* _FFR_MILTER_NOHDR_RESP */ char *action; char *defresponse; char *response; @@ -1898,6 +1919,9 @@ milter_send_command(m, command, data, sz, e, state) case SMFIC_HEADER: skipflag = SMFIP_NOHDRS; +#if _FFR_MILTER_NOHDR_RESP + norespflag = SMFIP_NOHREPL; +#endif /* _FFR_MILTER_NOHDR_RESP */ action = "header"; defresponse = "550 5.7.1 Command rejected"; break; @@ -1914,6 +1938,13 @@ milter_send_command(m, command, data, sz, e, state) defresponse = "550 5.7.1 Command rejected"; break; +#if SMFI_VERSION > 2 + case SMFIC_UNKNOWN: + action = "unknown"; + defresponse = "550 5.7.1 Command rejected"; + break; +#endif /* SMFI_VERSION > 2 */ + case SMFIC_BODYEOB: case SMFIC_OPTNEG: case SMFIC_MACRO: @@ -1943,6 +1974,12 @@ milter_send_command(m, command, data, sz, e, state) return NULL; } +#if _FFR_MILTER_NOHDR_RESP + /* check if filter sends response to this command */ + if (norespflag != 0 && bitset(norespflag, m->mf_pflags)) + return NULL; +#endif /* _FFR_MILTER_NOHDR_RESP */ + /* get the response from the filter */ response = milter_read(m, &rcmd, &rlen, m->mf_timeout[SMFTO_READ], e); @@ -2364,7 +2401,7 @@ milter_headers(m, e, state) /* don't send over deleted headers */ if (h->h_value == NULL) { - /* strip H_USER so not counted in milter_chgheader() */ + /* strip H_USER so not counted in milter_changeheader() */ h->h_flags &= ~H_USER; continue; } @@ -2621,6 +2658,84 @@ milter_addheader(response, rlen, e) } } /* +** MILTER_INSHEADER -- Insert the supplied header +** +** Parameters: +** response -- encoded form of header/value. +** rlen -- length of response. +** e -- current envelope. +** +** Returns: +** none +** +** Notes: +** Unlike milter_addheader(), this does not attempt to determine +** if the header already exists in the envelope, even a +** deleted version. It just blindly inserts. +*/ + +static void +milter_insheader(response, rlen, e) + char *response; + ssize_t rlen; + ENVELOPE *e; +{ + mi_int32 idx, i; + char *field; + char *val; + + if (tTd(64, 10)) + sm_dprintf("milter_insheader: "); + + /* sanity checks */ + if (response == NULL) + { + if (tTd(64, 10)) + sm_dprintf("NULL response\n"); + return; + } + + if (rlen < 2 || strlen(response) + 1 >= (size_t) rlen) + { + if (tTd(64, 10)) + sm_dprintf("didn't follow protocol (total len)\n"); + return; + } + + /* decode */ + (void) memcpy((char *) &i, response, MILTER_LEN_BYTES); + idx = 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)) + sm_dprintf("didn't follow protocol (part len)\n"); + return; + } + + if (*field == '\0') + { + if (tTd(64, 10)) + sm_dprintf("empty field name\n"); + return; + } + + /* add to e_msgsize */ + e->e_msgsize += strlen(response) + 2 + strlen(val); + + if (tTd(64, 10)) + sm_dprintf("Insert (%d) %s: %s\n", idx, response, val); + if (MilterLogLevel > 8) + sm_syslog(LOG_INFO, e->e_id, + "Milter insert (%d): header: %s: %s", + idx, field, val); + insheader(idx, newstr(field), val, H_USER, e); +} +/* ** MILTER_CHANGEHEADER -- Change the supplied header in the message ** ** Parameters: @@ -2753,7 +2868,7 @@ milter_changeheader(response, rlen, e) if (*val == '\0') { sm_syslog(LOG_INFO, e->e_id, - "Milter delete: header %s %s: %s", + "Milter delete: header%s %s: %s", h == sysheader ? " (default header)" : "", field, h->h_value == NULL ? "<NULL>" : h->h_value); @@ -2761,7 +2876,7 @@ milter_changeheader(response, rlen, e) else { sm_syslog(LOG_INFO, e->e_id, - "Milter change: header %s %s: from %s to %s", + "Milter change: header%s %s: from %s to %s", h == sysheader ? " (default header)" : "", field, h->h_value == NULL ? "<NULL>" : h->h_value, @@ -2848,8 +2963,8 @@ milter_addrcpt(response, rlen, e) if (MilterLogLevel > 8) sm_syslog(LOG_INFO, e->e_id, "Milter add: rcpt: %s", response); olderrors = Errors; - (void) sendtolist(response, NULLADDR, &e->e_sendqueue, 0, e); - Errors = olderrors; + (void) sendtolist(response, NULLADDR, &e->e_sendqueue, 0, e); + Errors = olderrors; return; } /* @@ -3113,6 +3228,7 @@ milter_init(e, state) /* if negotation failure, close socket */ milter_error(m, e); MILTER_CHECK_ERROR(true, continue); + continue; } if (MilterLogLevel > 9) sm_syslog(LOG_INFO, e->e_id, @@ -3256,11 +3372,9 @@ milter_connect(hostname, addr, e, state) if (response != NULL && *response == '4') { -#if _FFR_MILTER_421 if (strncmp(response, "421 ", 4) == 0) *state = SMFIR_SHUTDOWN; else -#endif /* _FFR_MILTER_421 */ *state = SMFIR_TEMPFAIL; } else @@ -3419,6 +3533,7 @@ milter_envfrom(args, e, state) sm_syslog(LOG_INFO, e->e_id, "Milter: reject, senders"); return response; } + /* ** MILTER_ENVRCPT -- send SMTP RCPT command info to milter filters ** @@ -3487,6 +3602,32 @@ milter_envrcpt(args, e, state) sm_free(buf); /* XXX */ return response; } + +#if SMFI_VERSION > 3 +/* +** MILTER_DATA_CMD -- send SMTP DATA command info to milter filters +** +** Parameters: +** e -- current envelope. +** state -- return state from response. +** +** Returns: +** response string (may be NULL) +*/ + +char * +milter_data_cmd(e, state) + ENVELOPE *e; + char *state; +{ + if (tTd(64, 10)) + sm_dprintf("milter_data_cmd\n"); + + /* send it over */ + return milter_command(SMFIC_DATA, NULL, 0, MilterDataMacros, e, state); +} +#endif /* SMFI_VERSION > 3 */ + /* ** MILTER_DATA -- send message headers/body and gather final message results ** @@ -3608,10 +3749,9 @@ milter_data(e, state) MILTER_CHECK_RESULTS(); } -#if _FFR_MILTER_MACROS_EOM if (MilterEOMMacros[0] != NULL) - milter_send_macros(m, MilterEOMMacros, SMFIC_BODYEOB, e); -#endif /* _FFR_MILTER_MACROS_EOM */ + milter_send_macros(m, MilterEOMMacros, + SMFIC_BODYEOB, e); /* send the final body chunk */ (void) milter_write(m, SMFIC_BODYEOB, NULL, 0, @@ -3696,7 +3836,6 @@ milter_data(e, state) case SMFIR_PROGRESS: break; -# if _FFR_QUARANTINE case SMFIR_QUARANTINE: if (!bitset(SMFIF_QUARANTINE, m->mf_fflags)) { @@ -3716,7 +3855,6 @@ milter_data(e, state) macdefine(&e->e_macro, A_PERM, macid("{quarantine}"), e->e_quarmsg); break; -# endif /* _FFR_QUARANTINE */ case SMFIR_ADDHEADER: if (!bitset(SMFIF_ADDHDRS, m->mf_fflags)) @@ -3729,6 +3867,17 @@ milter_data(e, state) milter_addheader(response, rlen, e); break; + case SMFIR_INSHEADER: + if (!bitset(SMFIF_ADDHDRS, m->mf_fflags)) + { + if (MilterLogLevel > 9) + sm_syslog(LOG_WARNING, e->e_id, + "milter_data(%s): lied about adding headers, honoring request anyway", + m->mf_name); + } + milter_insheader(response, rlen, e); + break; + case SMFIR_CHGHEADER: if (!bitset(SMFIF_CHGHDRS, m->mf_fflags)) { @@ -3765,7 +3914,7 @@ milter_data(e, state) case SMFIR_REPLBODY: if (!bitset(SMFIF_MODBODY, m->mf_fflags)) { - if (MilterLogLevel > 9) + if (MilterLogLevel > 0) sm_syslog(LOG_ERR, e->e_id, "milter_data(%s): lied about replacing body, rejecting request and tempfailing message", m->mf_name); @@ -3878,6 +4027,36 @@ finishup: sm_syslog(LOG_INFO, e->e_id, "Milter: reject, data"); return response; } + +#if SMFI_VERSION > 2 +/* +** MILTER_UNKNOWN -- send any unrecognized or unimplemented command +** string to milter filters +** +** Parameters: +** cmd -- the string itself. +** e -- current envelope. +** state -- return state from response. +** +** +** Returns: +** response string (may be NULL) +*/ + +char * +milter_unknown(cmd, e, state) + char *cmd; + ENVELOPE *e; + char *state; +{ + if (tTd(64, 10)) + sm_dprintf("milter_unknown(%s)\n", cmd); + + return milter_command(SMFIC_UNKNOWN, cmd, strlen(cmd) + 1, + NULL, e, state); +} +#endif /* SMFI_VERSION > 2 */ + /* ** MILTER_QUIT -- informs the filter(s) we are done and closes connection(s) ** |