A Sample Filter

The following sample logs each message to a separate temporary file, adds a recipient given with the -a flag, and rejects a disallowed recipient address given with the -r flag. It recognizes the following options:

-p portThe port through which the MTA will connect to the filter.
-t secThe timeout value.
-r addrA recipient to reject.
-a addrA recipient to add.

#include "mfapi.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sysexits.h>
#include <unistd.h>
#ifndef bool
#define bool char
#define TRUE 1
#define FALSE 0
#endif

extern int errno;


struct mlfiPriv
{
    char    *mlfi_fname;
    char    *mlfi_connectfrom;
    char    *mlfi_helofrom;
    FILE    *mlfi_fp;
};

#define MLFIPRIV        ((struct mlfiPriv *) smfi_getpriv(ctx))

extern sfsistat  mlfi_cleanup(SMFICTX *, bool);
/* recipients to add and reject (set with -a and -r options) */
char *add, *reject;

sfsistat
mlfi_connect(ctx, hostname, hostaddr)
     SMFICTX *ctx;
     char *hostname;
     _SOCK_ADDR *hostaddr;
{
    struct mlfiPriv *priv;
    char *ident;

    /* allocate some private memory */
    priv = malloc(sizeof *priv);
    if (priv == NULL)
    {
	/* can't accept this message right now */
	return SMFIS_TEMPFAIL;
    }
    memset(priv, '\0', sizeof *priv);

    /* save the private data */
    smfi_setpriv(ctx, priv);
        
    ident = smfi_getsymval(ctx, "_");
    if(!ident) ident = "???";
    if(!(priv->mlfi_connectfrom = strdup(ident))) {
	return SMFIS_TEMPFAIL;
    }
    /* Continue processing. */
    return SMFIS_CONTINUE;
}

sfsistat
mlfi_helo(ctx, helohost)
     SMFICTX *ctx;
     char *helohost;
{
    char *tls;
    char *buf;
    struct mlfiPriv *priv = MLFIPRIV;
    tls = smfi_getsymval(ctx, "{tls_version}");
    if(!tls) tls = "No TLS";
    if(!helohost) helohost = "???";
    if(!(buf = (char*)malloc(strlen(tls) + strlen(helohost) + 3))) {
	return SMFIS_TEMPFAIL;
    }
    sprintf(buf, "%s, %s", helohost, tls);
    if(priv->mlfi_helofrom)
	free(priv->mlfi_helofrom);
    priv->mlfi_helofrom = buf;
    /* Continue processing. */
    return SMFIS_CONTINUE;
}

sfsistat
mlfi_envfrom(ctx, argv)
     SMFICTX *ctx;
     char **argv;
{
    struct mlfiPriv *priv = MLFIPRIV;
    char *mailaddr = smfi_getsymval(ctx, "{mail_addr}");
    int argc = 0;

    /* open a file to store this message */
    priv->mlfi_fname = strdup("/tmp/msg.XXXXXX");
    mkstemp(priv->mlfi_fname);
    if (priv->mlfi_fname == NULL)
	return SMFIS_TEMPFAIL;
    if ((priv->mlfi_fp = fopen(priv->mlfi_fname, "w+")) == NULL)
    {
	free(priv->mlfi_fname);
	return SMFIS_TEMPFAIL;
    }

    /* count the arguments */
    while(*argv++) ++argc;
    /* log the connection information we stored earlier: */
    if(fprintf(priv->mlfi_fp, "Connect from %s (%s)\n\n", 
	       priv->mlfi_helofrom, priv->mlfi_connectfrom) == EOF) {
	(void) mlfi_cleanup(ctx, FALSE);
	return SMFIS_TEMPFAIL;
    }
    /* log the sender */
    if(fprintf(priv->mlfi_fp, "FROM %s (%d argument%s)\n", 
	       mailaddr?mailaddr:"???", argc,
	       (argc == 1)?"":"s")
       == EOF) {
	(void) mlfi_cleanup(ctx, FALSE);
	return SMFIS_TEMPFAIL;
    }
    /* continue processing */
    return SMFIS_CONTINUE;
}

sfsistat
mlfi_envrcpt(ctx, argv)
     SMFICTX *ctx;
     char **argv;
{
    struct mlfiPriv *priv = MLFIPRIV;
    char *rcptaddr = smfi_getsymval(ctx, "{rcpt_addr}");
    int argc = 0;
    /* count the arguments */
    while(*argv++) ++argc;
    /* log this recipient */
    if(reject && rcptaddr && (strcmp(rcptaddr, reject) == 0)) {
	if(fprintf(priv->mlfi_fp, "RCPT %s -- REJECTED\n", rcptaddr)
	   == EOF) {
	    (void) mlfi_cleanup(ctx, FALSE);
	    return SMFIS_TEMPFAIL;
	}
	return SMFIS_REJECT;
    }
    if(fprintf(priv->mlfi_fp, "RCPT %s (%d argument%s)\n", 
	       rcptaddr?rcptaddr:"???", argc, 
	       (argc == 1)?"":"s")
       == EOF) {
	(void) mlfi_cleanup(ctx, FALSE);
	return SMFIS_TEMPFAIL;
    }
    /* continue processing */
    return SMFIS_CONTINUE;
}

sfsistat
mlfi_header(ctx, headerf, headerv)
     SMFICTX *ctx;
     char *headerf;
     unsigned char *headerv;
{
    /* write the header to the log file */
    fprintf(MLFIPRIV->mlfi_fp, "%s: %s\n", headerf, headerv);

    /* continue processing */
    return SMFIS_CONTINUE;
}

sfsistat
mlfi_eoh(ctx)
     SMFICTX *ctx;
{
    /* output the blank line between the header and the body */
    fprintf(MLFIPRIV->mlfi_fp, "\n");

    /* continue processing */
    return SMFIS_CONTINUE;
}

sfsistat
mlfi_body(ctx, bodyp, bodylen)
     SMFICTX *ctx;
     unsigned char *bodyp;
     size_t bodylen;
{
    /* output body block to log file */
    int nwritten;
    if ((nwritten = fwrite(bodyp, bodylen, 1, MLFIPRIV->mlfi_fp)) != 1)
    {
	/* write failed */
	perror("error logging body");
	(void) mlfi_cleanup(ctx, FALSE);
	return SMFIS_TEMPFAIL;
    }

    /* continue processing */
    return SMFIS_CONTINUE;
}

sfsistat
mlfi_eom(ctx)
     SMFICTX *ctx;
{
    bool ok = TRUE;
    /* change recipients, if requested */
    if(add)
	ok = ok && (smfi_addrcpt(ctx, add) == MI_SUCCESS);
    return mlfi_cleanup(ctx, ok);
}

sfsistat
mlfi_abort(ctx)
     SMFICTX *ctx;
{
    return mlfi_cleanup(ctx, FALSE);
}

sfsistat
mlfi_cleanup(ctx, ok)
     SMFICTX *ctx;
     bool ok;
{
    sfsistat rstat = SMFIS_CONTINUE;
    struct mlfiPriv *priv = MLFIPRIV;
    char *p;
    char host[512];
    char hbuf[1024];

    if (priv == NULL)
	return rstat;

    /* close the archive file */
    if (priv->mlfi_fp != NULL && fclose(priv->mlfi_fp) == EOF)
    {
	/* failed; we have to wait until later */
	fprintf(stderr, "Couldn't close archive file %s: %s\n",
		priv->mlfi_fname, strerror(errno));
	rstat = SMFIS_TEMPFAIL;
	(void) unlink(priv->mlfi_fname);
    }
    else if (ok)
    {
	/* add a header to the message announcing our presence */
	if (gethostname(host, sizeof host) < 0)
	    strncpy(host, "localhost", sizeof host);
	p = strrchr(priv->mlfi_fname, '/');
	if (p == NULL)
	    p = priv->mlfi_fname;
	else
	    p++;
	snprintf(hbuf, sizeof hbuf, "%s@%s", p, host);
	smfi_addheader(ctx, "X-Archived", hbuf);
    }
    else
    {
	/* message was aborted -- delete the archive file */
	fprintf(stderr, "Message aborted.  Removing %s\n",
		priv->mlfi_fname);
	rstat = SMFIS_TEMPFAIL;
	(void) unlink(priv->mlfi_fname);
    }

    /* release private memory */
    free(priv->mlfi_fname);

    /* return status */
    return rstat;
}

sfsistat
mlfi_close(ctx)
     SMFICTX *ctx;
{
    struct mlfiPriv *priv = MLFIPRIV;
    if(priv->mlfi_connectfrom)
	free(priv->mlfi_connectfrom);
    if(priv->mlfi_helofrom)
	free(priv->mlfi_helofrom);
    free(priv);
    smfi_setpriv(ctx, NULL);
    return SMFIS_CONTINUE;
}

struct smfiDesc smfilter =
{
    "SampleFilter", /* filter name */
    SMFI_VERSION,   /* version code -- do not change */
    SMFIF_ADDHDRS,  /* flags */
    mlfi_connect,   /* connection info filter */
    mlfi_helo,      /* SMTP HELO command filter */
    mlfi_envfrom,   /* envelope sender filter */
    mlfi_envrcpt,   /* envelope recipient filter */
    mlfi_header,    /* header filter */
    mlfi_eoh,       /* end of header */
    mlfi_body,      /* body block filter */
    mlfi_eom,       /* end of message */
    mlfi_abort,     /* message aborted */
    mlfi_close,     /* connection cleanup */
};

static void 
usage()
{
    fprintf(stderr, 
	    "Usage: sample [-p socket-addr] [-t timeout] [-r reject-addr] \n\
\t[-a accept-addr]\n");
}

int
main(argc, argv)
     int argc;
     char *argv[];
{
    int retval;
    char c;
    const char *args = "p:t:r:a:h";
    extern char *optarg;

    /* Process command line options */
    while ((c = getopt(argc, argv, args)) != (char)EOF)
    {
	switch (c)
	{
	case 'p':
	    if (optarg == NULL || *optarg == '\0')
	    {
		(void) fprintf(stderr, "Illegal conn: %s\n",
			       optarg);
		exit(EX_USAGE);
	    }
	    if(smfi_setconn(optarg) == MI_FAILURE)
	    {
		(void) fputs("smfi_setconn failed", stderr);
		exit(EX_SOFTWARE);
	    }
	    /* 
	    ** If we're using a local socket, make sure it doesn't
	    ** already exist.
	    */
	    if(strncmp(optarg, "unix:", 5) == 0)
		unlink(optarg + 5);
	    else if(strncmp(optarg, "local:", 6) == 0)
		unlink(optarg + 6);
	    break;

	case 't':
	    if (optarg == NULL || *optarg == '\0')
	    {
		(void) fprintf(stderr, "Illegal timeout: %s\n",
			       optarg);
		exit(EX_USAGE);
	    }
	    if(smfi_settimeout(atoi(optarg)) == MI_FAILURE)
	    {
		(void) fputs("smfi_settimeout failed", stderr);
		exit(EX_SOFTWARE);
	    }
	    break;

	case 'r':
	    if (optarg == NULL)
	    {
		(void) fprintf(stderr, "Illegal reject rcpt: %s\n",
			       optarg);
		exit(EX_USAGE);
	    }
	    reject = optarg;
	    break;

	case 'a':
	    if (optarg == NULL)
	    {
		(void) fprintf(stderr, "Illegal add rcpt: %s\n",
			       optarg);
		exit(EX_USAGE);
	    }
	    add = optarg;
	    smfilter.xxfi_flags |= SMFIF_ADDRCPT;
	    break;
	case 'h':
	default:
	    usage();
	    exit(0);
	}
    }
    if (smfi_register(smfilter) == MI_FAILURE)
    {
	fprintf(stderr, "smfi_register failed\n");
	exit(EX_UNAVAILABLE);
    }
    retval = smfi_main();
    return retval;
}

/* eof */


Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers. All rights reserved.
By using this file, you agree to the terms and conditions set forth in the LICENSE.