root/usr/src/cmd/sendmail/libmilter/smfi.c
/*
 *  Copyright (c) 1999-2007 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.
 *
 */

#include <sm/gen.h>
SM_RCSID("@(#)$Id: smfi.c,v 8.83 2007/04/23 16:44:39 ca Exp $")
#include <sm/varargs.h>
#include "libmilter.h"

static int smfi_header __P((SMFICTX *, int, int, char *, char *));
static int myisenhsc __P((const char *, int));

/* for smfi_set{ml}reply, let's be generous. 256/16 should be sufficient */
#define MAXREPLYLEN     980     /* max. length of a reply string */
#define MAXREPLIES      32      /* max. number of reply strings */

/*
**  SMFI_HEADER -- send a header to the MTA
**
**      Parameters:
**              ctx -- Opaque context structure
**              cmd -- Header modification command
**              hdridx -- Header index
**              headerf -- Header field name
**              headerv -- Header field value
**
**      Returns:
**              MI_SUCCESS/MI_FAILURE
*/

static int
smfi_header(ctx, cmd, hdridx, headerf, headerv)
        SMFICTX *ctx;
        int cmd;
        int hdridx;
        char *headerf;
        char *headerv;
{
        size_t len, l1, l2, offset;
        int r;
        mi_int32 v;
        char *buf;
        struct timeval timeout;

        if (headerf == NULL || *headerf == '\0' || headerv == NULL)
                return MI_FAILURE;
        timeout.tv_sec = ctx->ctx_timeout;
        timeout.tv_usec = 0;
        l1 = strlen(headerf) + 1;
        l2 = strlen(headerv) + 1;
        len = l1 + l2;
        if (hdridx >= 0)
                len += MILTER_LEN_BYTES;
        buf = malloc(len);
        if (buf == NULL)
                return MI_FAILURE;
        offset = 0;
        if (hdridx >= 0)
        {
                v = htonl(hdridx);
                (void) memcpy(&(buf[0]), (void *) &v, MILTER_LEN_BYTES);
                offset += MILTER_LEN_BYTES;
        }
        (void) memcpy(buf + offset, headerf, l1);
        (void) memcpy(buf + offset + l1, headerv, l2);
        r = mi_wr_cmd(ctx->ctx_sd, &timeout, cmd, buf, len);
        free(buf);
        return r;
}

/*
**  SMFI_ADDHEADER -- send a new header to the MTA
**
**      Parameters:
**              ctx -- Opaque context structure
**              headerf -- Header field name
**              headerv -- Header field value
**
**      Returns:
**              MI_SUCCESS/MI_FAILURE
*/

int
smfi_addheader(ctx, headerf, headerv)
        SMFICTX *ctx;
        char *headerf;
        char *headerv;
{
        if (!mi_sendok(ctx, SMFIF_ADDHDRS))
                return MI_FAILURE;

        return smfi_header(ctx, SMFIR_ADDHEADER, -1, headerf, headerv);
}

/*
**  SMFI_INSHEADER -- send a new header to the MTA (to be inserted)
**
**      Parameters:
**              ctx -- Opaque context structure
**              hdridx -- index into header list where insertion should occur
**              headerf -- Header field name
**              headerv -- Header field value
**
**      Returns:
**              MI_SUCCESS/MI_FAILURE
*/

int
smfi_insheader(ctx, hdridx, headerf, headerv)
        SMFICTX *ctx;
        int hdridx;
        char *headerf;
        char *headerv;
{
        if (!mi_sendok(ctx, SMFIF_ADDHDRS) || hdridx < 0)
                return MI_FAILURE;

        return smfi_header(ctx, SMFIR_INSHEADER, hdridx, headerf, headerv);
}

/*
**  SMFI_CHGHEADER -- send a changed header to the MTA
**
**      Parameters:
**              ctx -- Opaque context structure
**              headerf -- Header field name
**              hdridx -- Header index value
**              headerv -- Header field value
**
**      Returns:
**              MI_SUCCESS/MI_FAILURE
*/

int
smfi_chgheader(ctx, headerf, hdridx, headerv)
        SMFICTX *ctx;
        char *headerf;
        mi_int32 hdridx;
        char *headerv;
{
        if (!mi_sendok(ctx, SMFIF_CHGHDRS) || hdridx < 0)
                return MI_FAILURE;
        if (headerv == NULL)
                headerv = "";

        return smfi_header(ctx, SMFIR_CHGHEADER, hdridx, headerf, headerv);
}

#if 0
/*
**  BUF_CRT_SEND -- construct buffer to send from arguments
**
**      Parameters:
**              ctx -- Opaque context structure
**              cmd -- command
**              arg0 -- first argument
**              argv -- list of arguments (NULL terminated)
**
**      Returns:
**              MI_SUCCESS/MI_FAILURE
*/

static int
buf_crt_send __P((SMFICTX *, int cmd, char *, char **));

static int
buf_crt_send(ctx, cmd, arg0, argv)
        SMFICTX *ctx;
        int cmd;
        char *arg0;
        char **argv;
{
        size_t len, l0, l1, offset;
        int r;
        char *buf, *arg, **argvl;
        struct timeval timeout;

        if (arg0 == NULL || *arg0 == '\0')
                return MI_FAILURE;
        timeout.tv_sec = ctx->ctx_timeout;
        timeout.tv_usec = 0;
        l0 = strlen(arg0) + 1;
        len = l0;
        argvl = argv;
        while (argvl != NULL && (arg = *argv) != NULL && *arg != '\0')
        {
                l1 = strlen(arg) + 1;
                len += l1;
                SM_ASSERT(len > l1);
        }

        buf = malloc(len);
        if (buf == NULL)
                return MI_FAILURE;
        (void) memcpy(buf, arg0, l0);
        offset = l0;

        argvl = argv;
        while (argvl != NULL && (arg = *argv) != NULL && *arg != '\0')
        {
                l1 = strlen(arg) + 1;
                SM_ASSERT(offset < len);
                SM_ASSERT(offset + l1 <= len);
                (void) memcpy(buf + offset, arg, l1);
                offset += l1;
                SM_ASSERT(offset > l1);
        }

        r = mi_wr_cmd(ctx->ctx_sd, &timeout, cmd, buf, len);
        free(buf);
        return r;
}
#endif /* 0 */

/*
**  SEND2 -- construct buffer to send from arguments
**
**      Parameters:
**              ctx -- Opaque context structure
**              cmd -- command
**              arg0 -- first argument
**              argv -- list of arguments (NULL terminated)
**
**      Returns:
**              MI_SUCCESS/MI_FAILURE
*/

static int
send2 __P((SMFICTX *, int cmd, char *, char *));

static int
send2(ctx, cmd, arg0, arg1)
        SMFICTX *ctx;
        int cmd;
        char *arg0;
        char *arg1;
{
        size_t len, l0, l1, offset;
        int r;
        char *buf;
        struct timeval timeout;

        if (arg0 == NULL || *arg0 == '\0')
                return MI_FAILURE;
        timeout.tv_sec = ctx->ctx_timeout;
        timeout.tv_usec = 0;
        l0 = strlen(arg0) + 1;
        len = l0;
        if (arg1 != NULL)
        {
                l1 = strlen(arg1) + 1;
                len += l1;
                SM_ASSERT(len > l1);
        }

        buf = malloc(len);
        if (buf == NULL)
                return MI_FAILURE;
        (void) memcpy(buf, arg0, l0);
        offset = l0;

        if (arg1 != NULL)
        {
                l1 = strlen(arg1) + 1;
                SM_ASSERT(offset < len);
                SM_ASSERT(offset + l1 <= len);
                (void) memcpy(buf + offset, arg1, l1);
                offset += l1;
                SM_ASSERT(offset > l1);
        }

        r = mi_wr_cmd(ctx->ctx_sd, &timeout, cmd, buf, len);
        free(buf);
        return r;
}

/*
**  SMFI_CHGFROM -- change enveloper sender ("from") address
**
**      Parameters:
**              ctx -- Opaque context structure
**              from -- new envelope sender address ("MAIL From")
**              args -- ESMTP arguments
**
**      Returns:
**              MI_SUCCESS/MI_FAILURE
*/

int
smfi_chgfrom(ctx, from, args)
        SMFICTX *ctx;
        char *from;
        char *args;
{
        if (from == NULL || *from == '\0')
                return MI_FAILURE;
        if (!mi_sendok(ctx, SMFIF_CHGFROM))
                return MI_FAILURE;
        return send2(ctx, SMFIR_CHGFROM, from, args);
}

/*
**  SMFI_SETSYMLIST -- set list of macros that the MTA should send.
**
**      Parameters:
**              ctx -- Opaque context structure
**              where -- SMTP stage
**              macros -- list of macros
**
**      Returns:
**              MI_SUCCESS/MI_FAILURE
*/

int
smfi_setsymlist(ctx, where, macros)
        SMFICTX *ctx;
        int where;
        char *macros;
{
        SM_ASSERT(ctx != NULL);

        if (macros == NULL || *macros == '\0')
                return MI_FAILURE;
        if (where < SMFIM_FIRST || where > SMFIM_LAST)
                return MI_FAILURE;
        if (where < 0 || where >= MAX_MACROS_ENTRIES)
                return MI_FAILURE;

        if (ctx->ctx_mac_list[where] != NULL)
                return MI_FAILURE;

        ctx->ctx_mac_list[where] = strdup(macros);
        if (ctx->ctx_mac_list[where] == NULL)
                return MI_FAILURE;

        return MI_SUCCESS;
}

/*
**  SMFI_ADDRCPT_PAR -- send an additional recipient to the MTA
**
**      Parameters:
**              ctx -- Opaque context structure
**              rcpt -- recipient address
**              args -- ESMTP arguments
**
**      Returns:
**              MI_SUCCESS/MI_FAILURE
*/

int
smfi_addrcpt_par(ctx, rcpt, args)
        SMFICTX *ctx;
        char *rcpt;
        char *args;
{
        if (rcpt == NULL || *rcpt == '\0')
                return MI_FAILURE;
        if (!mi_sendok(ctx, SMFIF_ADDRCPT_PAR))
                return MI_FAILURE;
        return send2(ctx, SMFIR_ADDRCPT_PAR, rcpt, args);
}

/*
**  SMFI_ADDRCPT -- send an additional recipient to the MTA
**
**      Parameters:
**              ctx -- Opaque context structure
**              rcpt -- recipient address
**
**      Returns:
**              MI_SUCCESS/MI_FAILURE
*/

int
smfi_addrcpt(ctx, rcpt)
        SMFICTX *ctx;
        char *rcpt;
{
        size_t len;
        struct timeval timeout;

        if (rcpt == NULL || *rcpt == '\0')
                return MI_FAILURE;
        if (!mi_sendok(ctx, SMFIF_ADDRCPT))
                return MI_FAILURE;
        timeout.tv_sec = ctx->ctx_timeout;
        timeout.tv_usec = 0;
        len = strlen(rcpt) + 1;
        return mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_ADDRCPT, rcpt, len);
}

/*
**  SMFI_DELRCPT -- send a recipient to be removed to the MTA
**
**      Parameters:
**              ctx -- Opaque context structure
**              rcpt -- recipient address
**
**      Returns:
**              MI_SUCCESS/MI_FAILURE
*/

int
smfi_delrcpt(ctx, rcpt)
        SMFICTX *ctx;
        char *rcpt;
{
        size_t len;
        struct timeval timeout;

        if (rcpt == NULL || *rcpt == '\0')
                return MI_FAILURE;
        if (!mi_sendok(ctx, SMFIF_DELRCPT))
                return MI_FAILURE;
        timeout.tv_sec = ctx->ctx_timeout;
        timeout.tv_usec = 0;
        len = strlen(rcpt) + 1;
        return mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_DELRCPT, rcpt, len);
}

/*
**  SMFI_REPLACEBODY -- send a body chunk to the MTA
**
**      Parameters:
**              ctx -- Opaque context structure
**              bodyp -- body chunk
**              bodylen -- length of body chunk
**
**      Returns:
**              MI_SUCCESS/MI_FAILURE
*/

int
smfi_replacebody(ctx, bodyp, bodylen)
        SMFICTX *ctx;
        unsigned char *bodyp;
        int bodylen;
{
        int len, off, r;
        struct timeval timeout;

        if (bodylen < 0 ||
            (bodyp == NULL && bodylen > 0))
                return MI_FAILURE;
        if (!mi_sendok(ctx, SMFIF_CHGBODY))
                return MI_FAILURE;
        timeout.tv_sec = ctx->ctx_timeout;
        timeout.tv_usec = 0;

        /* split body chunk if necessary */
        off = 0;
        do
        {
                len = (bodylen >= MILTER_CHUNK_SIZE) ? MILTER_CHUNK_SIZE :
                                                       bodylen;
                if ((r = mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_REPLBODY,
                                (char *) (bodyp + off), len)) != MI_SUCCESS)
                        return r;
                off += len;
                bodylen -= len;
        } while (bodylen > 0);
        return MI_SUCCESS;
}

/*
**  SMFI_QUARANTINE -- quarantine an envelope
**
**      Parameters:
**              ctx -- Opaque context structure
**              reason -- why?
**
**      Returns:
**              MI_SUCCESS/MI_FAILURE
*/

int
smfi_quarantine(ctx, reason)
        SMFICTX *ctx;
        char *reason;
{
        size_t len;
        int r;
        char *buf;
        struct timeval timeout;

        if (reason == NULL || *reason == '\0')
                return MI_FAILURE;
        if (!mi_sendok(ctx, SMFIF_QUARANTINE))
                return MI_FAILURE;
        timeout.tv_sec = ctx->ctx_timeout;
        timeout.tv_usec = 0;
        len = strlen(reason) + 1;
        buf = malloc(len);
        if (buf == NULL)
                return MI_FAILURE;
        (void) memcpy(buf, reason, len);
        r = mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_QUARANTINE, buf, len);
        free(buf);
        return r;
}

/*
**  MYISENHSC -- check whether a string contains an enhanced status code
**
**      Parameters:
**              s -- string with possible enhanced status code.
**              delim -- delim for enhanced status code.
**
**      Returns:
**              0  -- no enhanced status code.
**              >4 -- length of enhanced status code.
**
**      Side Effects:
**              none.
*/

static int
myisenhsc(s, delim)
        const char *s;
        int delim;
{
        int l, h;

        if (s == NULL)
                return 0;
        if (!((*s == '2' || *s == '4' || *s == '5') && s[1] == '.'))
                return 0;
        h = 0;
        l = 2;
        while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h]))
                ++h;
        if (h == 0 || s[l + h] != '.')
                return 0;
        l += h + 1;
        h = 0;
        while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h]))
                ++h;
        if (h == 0 || s[l + h] != delim)
                return 0;
        return l + h;
}

/*
**  SMFI_SETREPLY -- set the reply code for the next reply to the MTA
**
**      Parameters:
**              ctx -- Opaque context structure
**              rcode -- The three-digit (RFC 821) SMTP reply code.
**              xcode -- The extended (RFC 2034) reply code.
**              message -- The text part of the SMTP reply.
**
**      Returns:
**              MI_SUCCESS/MI_FAILURE
*/

int
smfi_setreply(ctx, rcode, xcode, message)
        SMFICTX *ctx;
        char *rcode;
        char *xcode;
        char *message;
{
        size_t len;
        char *buf;

        if (rcode == NULL || ctx == NULL)
                return MI_FAILURE;

        /* ### <sp> \0 */
        len = strlen(rcode) + 2;
        if (len != 5)
                return MI_FAILURE;
        if ((rcode[0] != '4' && rcode[0] != '5') ||
            !isascii(rcode[1]) || !isdigit(rcode[1]) ||
            !isascii(rcode[2]) || !isdigit(rcode[2]))
                return MI_FAILURE;
        if (xcode != NULL)
        {
                if (!myisenhsc(xcode, '\0'))
                        return MI_FAILURE;
                len += strlen(xcode) + 1;
        }
        if (message != NULL)
        {
                size_t ml;

                /* XXX check also for unprintable chars? */
                if (strpbrk(message, "\r\n") != NULL)
                        return MI_FAILURE;
                ml = strlen(message);
                if (ml > MAXREPLYLEN)
                        return MI_FAILURE;
                len += ml + 1;
        }
        buf = malloc(len);
        if (buf == NULL)
                return MI_FAILURE;              /* oops */
        (void) sm_strlcpy(buf, rcode, len);
        (void) sm_strlcat(buf, " ", len);
        if (xcode != NULL)
                (void) sm_strlcat(buf, xcode, len);
        if (message != NULL)
        {
                if (xcode != NULL)
                        (void) sm_strlcat(buf, " ", len);
                (void) sm_strlcat(buf, message, len);
        }
        if (ctx->ctx_reply != NULL)
                free(ctx->ctx_reply);
        ctx->ctx_reply = buf;
        return MI_SUCCESS;
}

/*
**  SMFI_SETMLREPLY -- set multiline reply code for the next reply to the MTA
**
**      Parameters:
**              ctx -- Opaque context structure
**              rcode -- The three-digit (RFC 821) SMTP reply code.
**              xcode -- The extended (RFC 2034) reply code.
**              txt, ... -- The text part of the SMTP reply,
**                      MUST be terminated with NULL.
**
**      Returns:
**              MI_SUCCESS/MI_FAILURE
*/

int
#if SM_VA_STD
smfi_setmlreply(SMFICTX *ctx, const char *rcode, const char *xcode, ...)
#else /* SM_VA_STD */
smfi_setmlreply(ctx, rcode, xcode, va_alist)
        SMFICTX *ctx;
        const char *rcode;
        const char *xcode;
        va_dcl
#endif /* SM_VA_STD */
{
        size_t len;
        size_t rlen;
        int args;
        char *buf, *txt;
        const char *xc;
        char repl[16];
        SM_VA_LOCAL_DECL

        if (rcode == NULL || ctx == NULL)
                return MI_FAILURE;

        /* ### <sp> */
        len = strlen(rcode) + 1;
        if (len != 4)
                return MI_FAILURE;
        if ((rcode[0] != '4' && rcode[0] != '5') ||
            !isascii(rcode[1]) || !isdigit(rcode[1]) ||
            !isascii(rcode[2]) || !isdigit(rcode[2]))
                return MI_FAILURE;
        if (xcode != NULL)
        {
                if (!myisenhsc(xcode, '\0'))
                        return MI_FAILURE;
                xc = xcode;
        }
        else
        {
                if (rcode[0] == '4')
                        xc = "4.0.0";
                else
                        xc = "5.0.0";
        }

        /* add trailing space */
        len += strlen(xc) + 1;
        rlen = len;
        args = 0;
        SM_VA_START(ap, xcode);
        while ((txt = SM_VA_ARG(ap, char *)) != NULL)
        {
                size_t tl;

                tl = strlen(txt);
                if (tl > MAXREPLYLEN)
                        break;

                /* this text, reply codes, \r\n */
                len += tl + 2 + rlen;
                if (++args > MAXREPLIES)
                        break;

                /* XXX check also for unprintable chars? */
                if (strpbrk(txt, "\r\n") != NULL)
                        break;
        }
        SM_VA_END(ap);
        if (txt != NULL)
                return MI_FAILURE;

        /* trailing '\0' */
        ++len;
        buf = malloc(len);
        if (buf == NULL)
                return MI_FAILURE;              /* oops */
        (void) sm_strlcpyn(buf, len, 3, rcode, args == 1 ? " " : "-", xc);
        (void) sm_strlcpyn(repl, sizeof repl, 4, rcode, args == 1 ? " " : "-",
                           xc, " ");
        SM_VA_START(ap, xcode);
        txt = SM_VA_ARG(ap, char *);
        if (txt != NULL)
        {
                (void) sm_strlcat2(buf, " ", txt, len);
                while ((txt = SM_VA_ARG(ap, char *)) != NULL)
                {
                        if (--args <= 1)
                                repl[3] = ' ';
                        (void) sm_strlcat2(buf, "\r\n", repl, len);
                        (void) sm_strlcat(buf, txt, len);
                }
        }
        if (ctx->ctx_reply != NULL)
                free(ctx->ctx_reply);
        ctx->ctx_reply = buf;
        SM_VA_END(ap);
        return MI_SUCCESS;
}

/*
**  SMFI_SETPRIV -- set private data
**
**      Parameters:
**              ctx -- Opaque context structure
**              privatedata -- pointer to private data
**
**      Returns:
**              MI_SUCCESS/MI_FAILURE
*/

int
smfi_setpriv(ctx, privatedata)
        SMFICTX *ctx;
        void *privatedata;
{
        if (ctx == NULL)
                return MI_FAILURE;
        ctx->ctx_privdata = privatedata;
        return MI_SUCCESS;
}

/*
**  SMFI_GETPRIV -- get private data
**
**      Parameters:
**              ctx -- Opaque context structure
**
**      Returns:
**              pointer to private data
*/

void *
smfi_getpriv(ctx)
        SMFICTX *ctx;
{
        if (ctx == NULL)
                return NULL;
        return ctx->ctx_privdata;
}

/*
**  SMFI_GETSYMVAL -- get the value of a macro
**
**      See explanation in mfapi.h about layout of the structures.
**
**      Parameters:
**              ctx -- Opaque context structure
**              symname -- name of macro
**
**      Returns:
**              value of macro (NULL in case of failure)
*/

char *
smfi_getsymval(ctx, symname)
        SMFICTX *ctx;
        char *symname;
{
        int i;
        char **s;
        char one[2];
        char braces[4];

        if (ctx == NULL || symname == NULL || *symname == '\0')
                return NULL;

        if (strlen(symname) == 3 && symname[0] == '{' && symname[2] == '}')
        {
                one[0] = symname[1];
                one[1] = '\0';
        }
        else
                one[0] = '\0';
        if (strlen(symname) == 1)
        {
                braces[0] = '{';
                braces[1] = *symname;
                braces[2] = '}';
                braces[3] = '\0';
        }
        else
                braces[0] = '\0';

        /* search backwards through the macro array */
        for (i = MAX_MACROS_ENTRIES - 1 ; i >= 0; --i)
        {
                if ((s = ctx->ctx_mac_ptr[i]) == NULL ||
                    ctx->ctx_mac_buf[i] == NULL)
                        continue;
                while (s != NULL && *s != NULL)
                {
                        if (strcmp(*s, symname) == 0)
                                return *++s;
                        if (one[0] != '\0' && strcmp(*s, one) == 0)
                                return *++s;
                        if (braces[0] != '\0' && strcmp(*s, braces) == 0)
                                return *++s;
                        ++s;    /* skip over macro value */
                        ++s;    /* points to next macro name */
                }
        }
        return NULL;
}

/*
**  SMFI_PROGRESS -- send "progress" message to the MTA to prevent premature
**                   timeouts during long milter-side operations
**
**      Parameters:
**              ctx -- Opaque context structure
**
**      Return value:
**              MI_SUCCESS/MI_FAILURE
*/

int
smfi_progress(ctx)
        SMFICTX *ctx;
{
        struct timeval timeout;

        if (ctx == NULL)
                return MI_FAILURE;

        timeout.tv_sec = ctx->ctx_timeout;
        timeout.tv_usec = 0;

        return mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_PROGRESS, NULL, 0);
}

/*
**  SMFI_VERSION -- return (runtime) version of libmilter
**
**      Parameters:
**              major -- (pointer to) major version
**              minor -- (pointer to) minor version
**              patchlevel -- (pointer to) patchlevel version
**
**      Return value:
**              MI_SUCCESS
*/

int
smfi_version(major, minor, patchlevel)
        unsigned int *major;
        unsigned int *minor;
        unsigned int *patchlevel;
{
        if (major != NULL)
                *major = SM_LM_VRS_MAJOR(SMFI_VERSION);
        if (minor != NULL)
                *minor = SM_LM_VRS_MINOR(SMFI_VERSION);
        if (patchlevel != NULL)
                *patchlevel = SM_LM_VRS_PLVL(SMFI_VERSION);
        return MI_SUCCESS;
}