diff options
| author | cvs2svn <cvs2svn@FreeBSD.org> | 2000-08-13 19:20:03 +0000 |
|---|---|---|
| committer | cvs2svn <cvs2svn@FreeBSD.org> | 2000-08-13 19:20:03 +0000 |
| commit | 5f4053c8ecd89d0ca5fd58b7a1ef7ce6ccf0e0b2 (patch) | |
| tree | c78c6bf436e65ec80276e773565bd712940a05c0 /contrib/sendmail/src | |
| parent | dfd9ce69af41851def892f5a6b931c99f3e99c31 (diff) | |
Notes
Diffstat (limited to 'contrib/sendmail/src')
| -rw-r--r-- | contrib/sendmail/src/Makefile | 17 | ||||
| -rw-r--r-- | contrib/sendmail/src/bf.h | 26 | ||||
| -rw-r--r-- | contrib/sendmail/src/bf_portable.c | 482 | ||||
| -rw-r--r-- | contrib/sendmail/src/bf_portable.h | 46 | ||||
| -rw-r--r-- | contrib/sendmail/src/bf_torek.c | 784 | ||||
| -rw-r--r-- | contrib/sendmail/src/bf_torek.h | 42 | ||||
| -rw-r--r-- | contrib/sendmail/src/helpfile | 136 | ||||
| -rw-r--r-- | contrib/sendmail/src/milter.c | 3402 | ||||
| -rw-r--r-- | contrib/sendmail/src/sfsasl.c | 367 | ||||
| -rw-r--r-- | contrib/sendmail/src/sfsasl.h | 60 | ||||
| -rw-r--r-- | contrib/sendmail/src/shmticklib.c | 84 | ||||
| -rw-r--r-- | contrib/sendmail/src/statusd_shm.h | 43 | ||||
| -rw-r--r-- | contrib/sendmail/src/timers.c | 229 | ||||
| -rw-r--r-- | contrib/sendmail/src/timers.h | 33 |
14 files changed, 5751 insertions, 0 deletions
diff --git a/contrib/sendmail/src/Makefile b/contrib/sendmail/src/Makefile new file mode 100644 index 0000000000000..c86bbf5e22041 --- /dev/null +++ b/contrib/sendmail/src/Makefile @@ -0,0 +1,17 @@ +# $Id: Makefile,v 8.11 1999/09/23 22:36:42 ca Exp $ + +SHELL= /bin/sh +BUILD= ./Build +OPTIONS= $(CONFIG) $(FLAGS) + +all: FRC + $(SHELL) $(BUILD) $(OPTIONS) $@ +clean: FRC + $(SHELL) $(BUILD) $(OPTIONS) $@ +install: FRC + $(SHELL) $(BUILD) $(OPTIONS) $@ + +fresh: FRC + $(SHELL) $(BUILD) $(OPTIONS) -c + +FRC: diff --git a/contrib/sendmail/src/bf.h b/contrib/sendmail/src/bf.h new file mode 100644 index 0000000000000..d7d6303fb4125 --- /dev/null +++ b/contrib/sendmail/src/bf.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 1999 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. + * + * $Id: bf.h,v 8.5 1999/11/04 19:31:25 ca Exp $ + * + * Contributed by Exactis.com, Inc. + * + */ + +#ifndef BF_H +#define BF_H 1 + +extern FILE *bfopen __P((char *, int, size_t, long)); +extern FILE *bfdup __P((FILE *)); +extern int bfcommit __P((FILE *)); +extern int bfrewind __P((FILE *)); +extern int bftruncate __P((FILE *)); +extern int bfclose __P((FILE *)); +extern bool bftest __P((FILE *)); + +#endif /* BF_H */ diff --git a/contrib/sendmail/src/bf_portable.c b/contrib/sendmail/src/bf_portable.c new file mode 100644 index 0000000000000..4de0af3cd3f36 --- /dev/null +++ b/contrib/sendmail/src/bf_portable.c @@ -0,0 +1,482 @@ +/* + * 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. + * + * Contributed by Exactis.com, Inc. + * + */ + +#ifndef lint +static char id[] = "@(#)$Id: bf_portable.c,v 8.25.4.3 2000/06/29 21:21:58 gshapiro Exp $"; +#endif /* ! lint */ + +#if SFIO +# include <sfio/stdio.h> +#endif /* SFIO */ + +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <stdlib.h> +#include <string.h> +#include <sys/uio.h> +#include <errno.h> +#if !SFIO +# include <stdio.h> +#endif /* !SFIO */ +#ifndef BF_STANDALONE +# include "sendmail.h" +#endif /* ! BF_STANDALONE */ +#include "bf_portable.h" +#include "bf.h" + +/* +** BFOPEN -- create a new buffered file +** +** Parameters: +** filename -- the file's name +** fmode -- what mode the file should be created as +** bsize -- amount of buffer space to allocate (may be 0) +** flags -- if running under sendmail, passed directly to safeopen +** +** Returns: +** a FILE * which may then be used with stdio functions, or NULL +** on failure. FILE * is opened for writing (mode "w+"). +** +** Side Effects: +** none. +** +** Sets errno: +** ENOMEM -- out of memory +** ENOENT -- illegal empty filename specified +** any value of errno specified by open() +** any value of errno specified by fdopen() +** any value of errno specified by funopen() +*/ + +#ifdef BF_STANDALONE +# define OPEN(fn, omode, cmode, sff) open(fn, omode, cmode) +#else /* BF_STANDALONE */ +# define OPEN(fn, omode, cmode, sff) safeopen(fn, omode, cmode, sff) +#endif /* BF_STANDALONE */ + +/* List of currently-open buffered files */ +struct bf *bflist = NULL; + +FILE * +bfopen(filename, fmode, bsize, flags) + char *filename; + int fmode; + size_t bsize; + long flags; +{ + struct bf *bfp; + FILE *retval; + int fd, l; + + fd = OPEN(filename, O_RDWR | O_CREAT | O_TRUNC, fmode, flags); + if (fd == -1) + { + /* errno is set implicitly by open */ + return NULL; + } + + retval = fdopen(fd, "w+"); + + /* If failure, return immediately */ + if (retval == NULL) + { + /* errno is set implicitly by fdopen */ + return NULL; + } + + /* Allocate memory */ + bfp = (struct bf *)malloc(sizeof(struct bf)); + if (bfp == NULL) + { + (void) fclose(retval); + + /* don't care about errors */ + (void) unlink(filename); + errno = ENOMEM; + return NULL; + } + if (tTd(58, 8)) + dprintf("bfopen(%s): malloced %ld\n", + filename, (long) sizeof(struct bf)); + + l = strlen(filename) + 1; + bfp->bf_filename = (char *)malloc(l); + if (bfp->bf_filename == NULL) + { + free(bfp); + (void) fclose(retval); + + /* don't care about errors */ + (void) unlink(filename); + errno = ENOMEM; + return NULL; + } + (void) strlcpy(bfp->bf_filename, filename, l); + + /* Fill in the other fields, then add it to the list */ + bfp->bf_key = retval; + bfp->bf_committed = FALSE; + bfp->bf_refcount = 1; + + bfinsert(bfp); + + /* Whew. Nothing bad happened. We're okay. */ + return retval; +} +/* +** BFDUP -- increase refcount on buffered file +** +** Parameters: +** fp -- FILE * to "duplicate" +** +** Returns: +** fp with increased refcount +*/ + +FILE * +bfdup(fp) + FILE *fp; +{ + struct bf *bfp; + + /* Get associated bf structure */ + bfp = bflookup(fp); + + if (bfp == NULL) + return NULL; + + /* Increase the refcount */ + bfp->bf_refcount++; + + return fp; +} + +/* +** BFCOMMIT -- "commits" the buffered file +** +** Parameters: +** fp -- FILE * to commit to disk +** +** Returns: +** 0 on success, -1 on error +** +** Side Effects: +** Forces the given FILE * to be written to disk if it is not +** already, and ensures that it will be kept after closing. If +** fp is not a buffered file, this is a no-op. +** +** Sets errno: +** any value of errno specified by open() +** any value of errno specified by write() +** any value of errno specified by lseek() +*/ + +int +bfcommit(fp) + FILE *fp; +{ + struct bf *bfp; + + /* Get associated bf structure */ + bfp = bflookup(fp); + + /* If called on a normal FILE *, noop */ + if (bfp != NULL) + bfp->bf_committed = TRUE; + + return 0; +} + +/* +** BFREWIND -- rewinds the FILE * +** +** Parameters: +** fp -- FILE * to rewind +** +** Returns: +** 0 on success, -1 on error +** +** Side Effects: +** rewinds the FILE * and puts it into read mode. Normally one +** would bfopen() a file, write to it, then bfrewind() and +** fread(). If fp is not a buffered file, this is equivalent to +** rewind(). +** +** Sets errno: +** any value of errno specified by fseek() +*/ + +int +bfrewind(fp) + FILE *fp; +{ + int err; + + /* check to see if there is an error on the stream */ + err = ferror(fp); + + (void) fflush(fp); + + /* + ** Clear error if tried to fflush() + ** a read-only file pointer and + ** there wasn't a previous error. + */ + + if (err == 0) + clearerr(fp); + + /* errno is set implicitly by fseek() before return */ + return fseek(fp, 0, SEEK_SET); +} + +/* +** BFTRUNCATE -- rewinds and truncates the FILE * +** +** Parameters: +** fp -- FILE * to truncate +** +** Returns: +** 0 on success, -1 on error +** +** Side Effects: +** rewinds the FILE *, truncates it to zero length, and puts it +** into write mode. If fp is not a buffered file, this is +** equivalent to a rewind() and then an ftruncate(fileno(fp), 0). +** +** Sets errno: +** any value of errno specified by fseek() +** any value of errno specified by ftruncate() +*/ + +int +bftruncate(fp) + FILE *fp; +{ + int ret; + + if (bfrewind(fp) == -1) + { + /* errno is set implicitly by bfrewind() */ + return -1; + } + +#if NOFTRUNCATE + /* XXX */ + errno = EINVAL; + ret = -1; +#else /* NOFTRUNCATE */ + /* errno is set implicitly by ftruncate() before return */ + ret = ftruncate(fileno(fp), 0); +#endif /* NOFTRUNCATE */ + return ret; +} + +/* +** BFCLOSE -- close a buffered file +** +** Parameters: +** fp -- FILE * to close +** +** Returns: +** 0 on success, EOF on failure +** +** Side Effects: +** Closes fp. If fp is a buffered file, unlink it if it has not +** already been committed. If fp is not a buffered file, this is +** equivalent to fclose(). +** +** Sets errno: +** any value of errno specified by fclose() +*/ + +int +bfclose(fp) + FILE *fp; +{ + int retval; + struct bf *bfp = NULL; + + /* Get associated bf structure */ + bfp = bflookup(fp); + + /* Decrement and check refcount */ + if (bfp != NULL && --bfp->bf_refcount > 0) + return 0; + + /* If bf, get bf structure and remove from list */ + if (bfp != NULL) + bfp = bfdelete(fp); + + if (fclose(fp) == EOF) + { + if (tTd(58, 8)) + dprintf("bfclose: fclose failed\n"); + /* errno is set implicitly by fclose() */ + return -1; + } + + if (bfp == NULL) + return 0; + + /* Success unless we determine otherwise in next block */ + retval = 0; + + if (bfp != NULL) + { + /* Might have to unlink; certainly will have to deallocate */ + if (!bfp->bf_committed) + retval = unlink(bfp->bf_filename); + + free(bfp->bf_filename); + free(bfp); + if (tTd(58, 8)) + dprintf("bfclose: freed %ld\n", + (long) sizeof(struct bf)); + } + else + { + if (tTd(58, 8)) + dprintf("bfclose: bfp was NULL\n"); + } + + return retval; +} + +/* +** BFTEST -- test if a FILE * is a buffered file +** +** Parameters: +** fp -- FILE * to test +** +** Returns: +** TRUE if fp is a buffered file, FALSE otherwise. +** +** Side Effects: +** none. +** +** Sets errno: +** never. +*/ + +bool +bftest(fp) + FILE *fp; +{ + return (bflookup(fp) != NULL); +} + +/* +** BFINSERT -- insert item in linking list +** +** Parameters: +** datum -- item to insert +** +** Returns: +** none. +** +** Side Effects: +** none. +** +** Sets errno: +** never. +*/ + +void +bfinsert(datum) + struct bf *datum; +{ + datum->bf_cdr = bflist; + bflist = datum; +} + +/* +** BFLOOKUP -- lookup FILE * in list +** +** Parameters: +** fp -- FILE * to lookup +** +** Returns: +** bf struct for the FILE *, NULL if not found +** +** Side Effects: +** none. +** +** Sets errno: +** never. +*/ + +struct bf * +bflookup(key) + FILE *key; +{ + struct bf *t; + + for (t = bflist; t != NULL; t = t->bf_cdr) + { + if (t->bf_key == key) + { + return t; + } + } + + /* If we got this far, we didn't find it */ + return NULL; +} + +/* +** BFDELETE -- delete a FILE * in list +** +** Parameters: +** fp -- FILE * to delete +** +** Returns: +** bf struct for deleted FILE *, NULL if not found, +** +** Side Effects: +** none. +** +** Sets errno: +** never. +*/ + +struct bf * +bfdelete(key) + FILE *key; +{ + struct bf *t, *u; + + if (bflist == NULL) + return NULL; + + /* if first element, special case */ + if (bflist->bf_key == key) + { + u = bflist; + bflist = bflist->bf_cdr; + return u; + } + + for (t = bflist; t->bf_cdr != NULL; t = t->bf_cdr) + { + if (t->bf_cdr->bf_key == key) + { + u = t->bf_cdr; + t->bf_cdr = u->bf_cdr; + return u; + } + } + + /* If we got this far, we didn't find it */ + return NULL; +} diff --git a/contrib/sendmail/src/bf_portable.h b/contrib/sendmail/src/bf_portable.h new file mode 100644 index 0000000000000..e319a740399cb --- /dev/null +++ b/contrib/sendmail/src/bf_portable.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 1999 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. + * + * $Id: bf_portable.h,v 8.6 1999/11/04 19:31:25 ca Exp $ + * + * Contributed by Exactis.com, Inc. + * + */ + +#ifndef BF_PORTABLE_H +#define BF_PORTABLE_H 1 +/* +** This implementation will behave differently from the Torek-based code in +** the following major ways: +** - The buffer size argument to bfopen() will be sent in, sent back, +** queried, lost, found, subjected to public inquiry, lost again, and +** finally buried in soft peat and recycled as firelighters. +** - Errors in creating the file (but not necessarily writing to it) will +** always be detected and reported synchronously with the bfopen() +*/ + +/* Linked structure for storing information about each buffered file */ +struct bf +{ + FILE *bf_key; /* Unused except as a key for lookup */ + bool bf_committed; /* buffered file is on disk */ + char *bf_filename; /* Name of disk file */ + int bf_refcount; /* Reference count */ + struct bf *bf_cdr; +}; + +/* +** Access routines for looking up bf structures +** +** maybe replace with a faster data structure later +*/ + +extern void bfinsert __P((struct bf *)); +extern struct bf *bflookup __P((FILE *)); +extern struct bf *bfdelete __P((FILE *)); +#endif /* BF_PORTABLE_H */ diff --git a/contrib/sendmail/src/bf_torek.c b/contrib/sendmail/src/bf_torek.c new file mode 100644 index 0000000000000..fb0536860d4c4 --- /dev/null +++ b/contrib/sendmail/src/bf_torek.c @@ -0,0 +1,784 @@ +/* + * Copyright (c) 1999 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. + * + * Contributed by Exactis.com, Inc. + * + */ + +#ifndef lint +static char id[] = "@(#)$Id: bf_torek.c,v 8.19.18.1 2000/07/18 16:52:26 gshapiro Exp $"; +#endif /* ! lint */ + +#if SFIO + ERROR README: Can not use bf_torek.c with SFIO. +#endif /* SFIO */ + +#include <sys/types.h> +#include <sys/uio.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <stdio.h> +#ifndef BF_STANDALONE +# include "sendmail.h" +#endif /* ! BF_STANDALONE */ +#include "bf_torek.h" +#include "bf.h" + +/* +** BFOPEN -- create a new buffered file +** +** Parameters: +** filename -- the file's name +** fmode -- what mode the file should be created as +** bsize -- amount of buffer space to allocate (may be 0) +** flags -- if running under sendmail, passed directly to safeopen +** +** Returns: +** a FILE * which may then be used with stdio functions, or NULL +** on failure. FILE * is opened for writing (mode "w+"). +** +** Side Effects: +** none. +** +** Sets errno: +** ENOMEM -- out of memory +** ENOENT -- illegal empty filename specified +** any value of errno specified by open() +** any value of errno specified by fdopen() +** any value of errno specified by funopen() +*/ + +#ifdef BF_STANDALONE +# define OPEN(fn, omode, cmode, sff) open(fn, omode, cmode) +#else /* BF_STANDALONE */ +# define OPEN(fn, omode, cmode, sff) safeopen(fn, omode, cmode, sff) +#endif /* BF_STANDALONE */ + +FILE * +bfopen(filename, fmode, bsize, flags) + char *filename; + int fmode; + size_t bsize; + long flags; +{ + struct bf *bfp; + FILE *retval; + int save_errno, l; + struct stat st; + + /* Sanity checks */ + /* Empty filename string */ + if (*filename == '\0') + { + errno = ENOENT; + return NULL; + } + + if (stat(filename, &st) == 0) + { + /* file already exists on disk */ + errno = EEXIST; + return NULL; + } + + /* Allocate memory */ + bfp = (struct bf *)malloc(sizeof(struct bf)); + if (bfp == NULL) + { + errno = ENOMEM; + return NULL; + } + + /* A zero bsize is valid, just don't allocate memory */ + if (bsize > 0) + { + bfp->bf_buf = (char *)malloc(bsize); + if (bfp->bf_buf == NULL) + { + free(bfp); + errno = ENOMEM; + return NULL; + } + } + else + bfp->bf_buf = NULL; + + /* Nearly home free, just set all the parameters now */ + bfp->bf_committed = FALSE; + bfp->bf_ondisk = FALSE; + bfp->bf_refcount = 1; + bfp->bf_flags = flags; + bfp->bf_bufsize = bsize; + bfp->bf_buffilled = 0; + l = strlen(filename) + 1; + bfp->bf_filename = (char *)malloc(l); + if (bfp->bf_filename == NULL) + { + free(bfp); + if (bfp->bf_buf != NULL) + free(bfp->bf_buf); + errno = ENOMEM; + return NULL; + } + (void) strlcpy(bfp->bf_filename, filename, l); + bfp->bf_filemode = fmode; + bfp->bf_offset = 0; + bfp->bf_size = 0; + + if (tTd(58, 8)) + dprintf("bfopen(%s, %d)\n", filename, bsize); + + /* The big test: will funopen accept it? */ + retval = funopen((void *)bfp, _bfread, _bfwrite, _bfseek, _bfclose); + if (retval == NULL) + { + /* Just in case free() sets errno */ + save_errno = errno; + free(bfp); + free(bfp->bf_filename); + if (bfp->bf_buf != NULL) + free(bfp->bf_buf); + errno = save_errno; + return NULL; + } + else + { + /* Success */ + return retval; + } +} +/* +** BFDUP -- increase refcount on buffered file +** +** Parameters: +** fp -- FILE * to "duplicate" +** +** Returns: +** If file is memory buffered, fp with increased refcount +** If file is on disk, NULL (need to use link()) +*/ + +FILE * +bfdup(fp) + FILE *fp; +{ + struct bf *bfp; + + /* If called on a normal FILE *, noop */ + if (!bftest(fp)) + return NULL; + + /* Get associated bf structure */ + bfp = (struct bf *)fp->_cookie; + + /* Increase ref count */ + bfp->bf_refcount++; + + return fp; +} + +/* +** BFCOMMIT -- "commits" the buffered file +** +** Parameters: +** fp -- FILE * to commit to disk +** +** Returns: +** 0 on success, -1 on error +** +** Side Effects: +** Forces the given FILE * to be written to disk if it is not +** already, and ensures that it will be kept after closing. If +** fp is not a buffered file, this is a no-op. +** +** Sets errno: +** any value of errno specified by open() +** any value of errno specified by write() +** any value of errno specified by lseek() +*/ + +int +bfcommit(fp) + FILE *fp; +{ + struct bf *bfp; + int retval; + int byteswritten; + + /* If called on a normal FILE *, noop */ + if (!bftest(fp)) + return 0; + + /* Get associated bf structure */ + bfp = (struct bf *)fp->_cookie; + + /* If already committed, noop */ + if (bfp->bf_committed) + return 0; + + /* Do we need to open a file? */ + if (!bfp->bf_ondisk) + { + struct stat st; + + if (tTd(58, 8)) + dprintf("bfcommit(%s): to disk\n", bfp->bf_filename); + + if (stat(bfp->bf_filename, &st) == 0) + { + errno = EEXIST; + return -1; + } + + retval = OPEN(bfp->bf_filename, O_RDWR | O_CREAT | O_TRUNC, + bfp->bf_filemode, bfp->bf_flags); + + /* Couldn't create file: failure */ + if (retval < 0) + { + /* errno is set implicitly by open() */ + return -1; + } + + bfp->bf_disk_fd = retval; + bfp->bf_ondisk = TRUE; + } + + /* Write out the contents of our buffer, if we have any */ + if (bfp->bf_buffilled > 0) + { + byteswritten = 0; + + if (lseek(bfp->bf_disk_fd, 0, SEEK_SET) < 0) + { + /* errno is set implicitly by lseek() */ + return -1; + } + + while (byteswritten < bfp->bf_buffilled) + { + retval = write(bfp->bf_disk_fd, + bfp->bf_buf + byteswritten, + bfp->bf_buffilled - byteswritten); + if (retval < 0) + { + /* errno is set implicitly by write() */ + return -1; + } + else + byteswritten += retval; + } + } + bfp->bf_committed = TRUE; + + /* Invalidate buf; all goes to file now */ + bfp->bf_buffilled = 0; + if (bfp->bf_bufsize > 0) + { + /* Don't need buffer anymore; free it */ + bfp->bf_bufsize = 0; + free(bfp->bf_buf); + } + return 0; +} + +/* +** BFREWIND -- rewinds the FILE * +** +** Parameters: +** fp -- FILE * to rewind +** +** Returns: +** 0 on success, -1 on error +** +** Side Effects: +** rewinds the FILE * and puts it into read mode. Normally one +** would bfopen() a file, write to it, then bfrewind() and +** fread(). If fp is not a buffered file, this is equivalent to +** rewind(). +** +** Sets errno: +** any value of errno specified by fseek() +*/ + +int +bfrewind(fp) + FILE *fp; +{ + int err; + + /* check to see if there is an error on the stream */ + err = ferror(fp); + + (void) fflush(fp); + + /* + ** Clear error if tried to fflush() + ** a read-only file pointer and + ** there wasn't a previous error. + */ + + if (err == 0) + clearerr(fp); + + /* errno is set implicitly by fseek() before return */ + return fseek(fp, 0, SEEK_SET); +} + +/* +** BFTRUNCATE -- rewinds and truncates the FILE * +** +** Parameters: +** fp -- FILE * to truncate +** +** Returns: +** 0 on success, -1 on error +** +** Side Effects: +** rewinds the FILE *, truncates it to zero length, and puts it +** into write mode. If fp is not a buffered file, this is +** equivalent to a rewind() and then an ftruncate(fileno(fp), 0). +** +** Sets errno: +** any value of errno specified by fseek() +** any value of errno specified by ftruncate() +*/ + +int +bftruncate(fp) + FILE *fp; +{ + struct bf *bfp; + + if (bfrewind(fp) < 0) + return -1; + + if (bftest(fp)) + { + /* Get bf structure */ + bfp = (struct bf *)fp->_cookie; + bfp->bf_buffilled = 0; + bfp->bf_size = 0; + + /* Need to zero the buffer */ + if (bfp->bf_bufsize > 0) + memset(bfp->bf_buf, '\0', bfp->bf_bufsize); + if (bfp->bf_ondisk) + return ftruncate(bfp->bf_disk_fd, 0); + else + return 0; + } + else + return ftruncate(fileno(fp), 0); +} + +/* +** BFCLOSE -- close a buffered file +** +** Parameters: +** fp -- FILE * to close +** +** Returns: +** 0 on success, EOF on failure +** +** Side Effects: +** Closes fp. If fp is a buffered file, unlink it if it has not +** already been committed. If fp is not a buffered file, this is +** equivalent to fclose(). +** +** Sets errno: +** any value of errno specified by fclose() +*/ + +int +bfclose(fp) + FILE *fp; +{ + struct bf *bfp; + + /* If called on a normal FILE *, call fclose() on it */ + if (!bftest(fp)) + return fclose(fp); + + /* Cast cookie back to correct type */ + bfp = (struct bf *)fp->_cookie; + + /* Check reference count to see if we actually want to close */ + if (bfp != NULL && --bfp->bf_refcount > 0) + return 0; + + /* + ** In this implementation, just call fclose--the _bfclose + ** routine will be called by that + */ + + return fclose(fp); +} + +/* +** BFTEST -- test if a FILE * is a buffered file +** +** Parameters: +** fp -- FILE * to test +** +** Returns: +** TRUE if fp is a buffered file, FALSE otherwise. +** +** Side Effects: +** none. +** +** Sets errno: +** never. +*/ + +bool +bftest(fp) + FILE *fp; +{ + /* + ** Check to see if our special I/O routines are installed + ** in this file structure + */ + + return ((fp->_close == _bfclose) && + (fp->_read == _bfread) && + (fp->_seek == _bfseek) && + (fp->_write == _bfwrite)); +} + +/* +** _BFCLOSE -- close a buffered file +** +** Parameters: +** cookie -- cookie of file to close +** +** Returns: +** 0 to indicate success +** +** Side Effects: +** deletes backing file, frees memory. +** +** Sets errno: +** never. +*/ + +int +_bfclose(cookie) + void *cookie; +{ + struct bf *bfp; + + /* Cast cookie back to correct type */ + bfp = (struct bf *)cookie; + + /* Need to clean up the file */ + if (bfp->bf_ondisk && !bfp->bf_committed) + unlink(bfp->bf_filename); + + /* Need to free the buffer */ + if (bfp->bf_bufsize > 0) + free(bfp->bf_buf); + + /* Finally, free the structure */ + free(bfp); + + return 0; +} + +/* +** _BFREAD -- read a buffered file +** +** Parameters: +** cookie -- cookie of file to read +** buf -- buffer to fill +** nbytes -- how many bytes to read +** +** Returns: +** number of bytes read or -1 indicate failure +** +** Side Effects: +** none. +** +*/ + +int +_bfread(cookie, buf, nbytes) + void *cookie; + char *buf; + int nbytes; +{ + struct bf *bfp; + int count = 0; /* Number of bytes put in buf so far */ + int retval; + + /* Cast cookie back to correct type */ + bfp = (struct bf *)cookie; + + if (bfp->bf_offset < bfp->bf_buffilled) + { + /* Need to grab some from buffer */ + count = nbytes; + if ((bfp->bf_offset + count) > bfp->bf_buffilled) + count = bfp->bf_buffilled - bfp->bf_offset; + + memcpy(buf, bfp->bf_buf + bfp->bf_offset, count); + } + + if ((bfp->bf_offset + nbytes) > bfp->bf_buffilled) + { + /* Need to grab some from file */ + + if (!bfp->bf_ondisk) + { + /* Oops, the file doesn't exist. EOF. */ + goto finished; + } + + /* Catch a read() on an earlier failed write to disk */ + if (bfp->bf_disk_fd < 0) + { + errno = EIO; + return -1; + } + + if (lseek(bfp->bf_disk_fd, + bfp->bf_offset + count, SEEK_SET) < 0) + { + if ((errno == EINVAL) || (errno == ESPIPE)) + { + /* + ** stdio won't be expecting these + ** errnos from read()! Change them + ** into something it can understand. + */ + + errno = EIO; + } + return -1; + } + + while (count < nbytes) + { + retval = read(bfp->bf_disk_fd, + buf + count, + nbytes - count); + if (retval < 0) + { + /* errno is set implicitly by read() */ + return -1; + } + else if (retval == 0) + goto finished; + else + count += retval; + } + } + +finished: + bfp->bf_offset += count; + return count; +} + +/* +** _BFSEEK -- seek to a position in a buffered file +** +** Parameters: +** cookie -- cookie of file to seek +** offset -- position to seek to +** whence -- how to seek +** +** Returns: +** new file offset or -1 indicate failure +** +** Side Effects: +** none. +** +*/ + +fpos_t +_bfseek(cookie, offset, whence) + void *cookie; + fpos_t offset; + int whence; + +{ + struct bf *bfp; + + /* Cast cookie back to correct type */ + bfp = (struct bf *)cookie; + + switch (whence) + { + case SEEK_SET: + bfp->bf_offset = offset; + break; + + case SEEK_CUR: + bfp->bf_offset += offset; + break; + + case SEEK_END: + bfp->bf_offset = bfp->bf_size + offset; + break; + + default: + errno = EINVAL; + return -1; + } + return bfp->bf_offset; +} + +/* +** _BFWRITE -- write to a buffered file +** +** Parameters: +** cookie -- cookie of file to write +** buf -- data buffer +** nbytes -- how many bytes to write +** +** Returns: +** number of bytes written or -1 indicate failure +** +** Side Effects: +** may create backing file if over memory limit for file. +** +*/ + +int +_bfwrite(cookie, buf, nbytes) + void *cookie; + const char *buf; + int nbytes; +{ + struct bf *bfp; + int count = 0; /* Number of bytes written so far */ + int retval; + + /* Cast cookie back to correct type */ + bfp = (struct bf *)cookie; + + /* If committed, go straight to disk */ + if (bfp->bf_committed) + { + if (lseek(bfp->bf_disk_fd, bfp->bf_offset, SEEK_SET) < 0) + { + if ((errno == EINVAL) || (errno == ESPIPE)) + { + /* + ** stdio won't be expecting these + ** errnos from write()! Change them + ** into something it can understand. + */ + + errno = EIO; + } + return -1; + } + + count = write(bfp->bf_disk_fd, buf, nbytes); + if (count < 0) + { + /* errno is set implicitly by write() */ + return -1; + } + goto finished; + } + + if (bfp->bf_offset < bfp->bf_bufsize) + { + /* Need to put some in buffer */ + count = nbytes; + if ((bfp->bf_offset + count) > bfp->bf_bufsize) + count = bfp->bf_bufsize - bfp->bf_offset; + + memcpy(bfp->bf_buf + bfp->bf_offset, buf, count); + if ((bfp->bf_offset + count) > bfp->bf_buffilled) + bfp->bf_buffilled = bfp->bf_offset + count; + } + + if ((bfp->bf_offset + nbytes) > bfp->bf_bufsize) + { + /* Need to put some in file */ + if (!bfp->bf_ondisk) + { + /* Oops, the file doesn't exist. */ + if (tTd(58, 8)) + dprintf("_bfwrite(%s): to disk\n", + bfp->bf_filename); + + retval = OPEN(bfp->bf_filename, + O_RDWR | O_CREAT | O_TRUNC, + bfp->bf_filemode, bfp->bf_flags); + + /* Couldn't create file: failure */ + if (retval < 0) + { + /* + ** stdio may not be expecting these + ** errnos from write()! Change to + ** something which it can understand. + ** Note that ENOSPC and EDQUOT are saved + ** because they are actually valid for + ** write(). + */ + + if (!((errno == ENOSPC) || (errno == EDQUOT))) + errno = EIO; + + return -1; + } + bfp->bf_disk_fd = retval; + bfp->bf_ondisk = TRUE; + } + + /* Catch a write() on an earlier failed write to disk */ + if (bfp->bf_ondisk && bfp->bf_disk_fd < 0) + { + errno = EIO; + return -1; + } + + if (lseek(bfp->bf_disk_fd, + bfp->bf_offset + count, SEEK_SET) < 0) + { + if ((errno == EINVAL) || (errno == ESPIPE)) + { + /* + ** stdio won't be expecting these + ** errnos from write()! Change them into + ** something which it can understand. + */ + + errno = EIO; + } + return -1; + } + + while (count < nbytes) + { + retval = write(bfp->bf_disk_fd, buf + count, + nbytes - count); + if (retval < 0) + { + /* errno is set implicitly by write() */ + return -1; + } + else + count += retval; + } + } + +finished: + bfp->bf_offset += count; + if (bfp->bf_offset > bfp->bf_size) + bfp->bf_size = bfp->bf_offset; + return count; +} diff --git a/contrib/sendmail/src/bf_torek.h b/contrib/sendmail/src/bf_torek.h new file mode 100644 index 0000000000000..48c3995b0d1f9 --- /dev/null +++ b/contrib/sendmail/src/bf_torek.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 1999 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. + * + * $Id: bf_torek.h,v 8.6 1999/11/04 19:31:25 ca Exp $ + * + * Contributed by Exactis.com, Inc. + * + */ + +#ifndef BF_TOREK_H +#define BF_TOREK_H 1 +/* +** Data structure for storing information about each buffered file +*/ + +struct bf +{ + bool bf_committed; /* Has this buffered file been committed? */ + bool bf_ondisk; /* On disk: committed or buffer overflow */ + int bf_flags; + int bf_disk_fd; /* If on disk, associated file descriptor */ + char *bf_buf; /* Memory buffer */ + int bf_bufsize; /* Length of above buffer */ + int bf_buffilled; /* Bytes of buffer actually filled */ + char *bf_filename; /* Name of buffered file, if ever committed */ + mode_t bf_filemode; /* Mode of buffered file, if ever committed */ + fpos_t bf_offset; /* Currect file offset */ + int bf_size; /* Total current size of file */ + int bf_refcount; /* Reference count */ +}; + +/* Our lower-level I/O routines */ +extern int _bfclose __P((void *)); +extern int _bfread __P((void *, char *, int)); +extern fpos_t _bfseek __P((void *, fpos_t, int)); +extern int _bfwrite __P((void *, const char *, int)); +#endif /* BF_TOREK_H */ diff --git a/contrib/sendmail/src/helpfile b/contrib/sendmail/src/helpfile new file mode 100644 index 0000000000000..2563d2e37a591 --- /dev/null +++ b/contrib/sendmail/src/helpfile @@ -0,0 +1,136 @@ +#vers 2 +cpyr +cpyr Copyright (c) 1998, 1999 Sendmail, Inc. and its suppliers. +cpyr All rights reserved. +cpyr Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved. +cpyr Copyright (c) 1988, 1993 +cpyr The Regents of the University of California. All rights reserved. +cpyr +cpyr +cpyr By using this file, you agree to the terms and conditions set +cpyr forth in the LICENSE file which can be found at the top level of +cpyr the sendmail distribution. +cpyr +cpyr $$Id: helpfile,v 8.31.16.3 2000/07/19 18:54:55 gshapiro Exp $$ +cpyr +smtp This is sendmail version $v +smtp Topics: +smtp HELO EHLO MAIL RCPT DATA +smtp RSET NOOP QUIT HELP VRFY +smtp EXPN VERB ETRN DSN AUTH +smtp STARTTLS +smtp For more info use "HELP <topic>". +smtp To report bugs in the implementation send email to +smtp sendmail-bugs@sendmail.org. +smtp For local information send email to Postmaster at your site. +help HELP [ <topic> ] +help The HELP command gives help info. +helo HELO <hostname> +helo Introduce yourself. +ehlo EHLO <hostname> +ehlo Introduce yourself, and request extended SMTP mode. +ehlo Possible replies include: +ehlo SEND Send as mail [RFC821] +ehlo SOML Send as mail or terminal [RFC821] +ehlo SAML Send as mail and terminal [RFC821] +ehlo EXPN Expand the mailing list [RFC821] +ehlo HELP Supply helpful information [RFC821] +ehlo TURN Turn the operation around [RFC821] +ehlo 8BITMIME Use 8-bit data [RFC1652] +ehlo SIZE Message size declaration [RFC1870] +ehlo VERB Verbose [Allman] +ehlo ONEX One message transaction only [Allman] +ehlo CHUNKING Chunking [RFC1830] +ehlo BINARYMIME Binary MIME [RFC1830] +ehlo PIPELINING Command Pipelining [RFC1854] +ehlo DSN Delivery Status Notification [RFC1891] +ehlo ETRN Remote Message Queue Starting [RFC1985] +ehlo STARTTLS Secure SMTP [RFC2487] +ehlo AUTH Authentication [RFC2554] +ehlo XUSR Initial (user) submission [Allman] +ehlo ENHANCEDSTATUSCODES Enhanced status codes [RFC2034] +mail MAIL FROM: <sender> [ <parameters> ] +mail Specifies the sender. Parameters are ESMTP extensions. +mail See "HELP DSN" for details. +rcpt RCPT TO: <recipient> [ <parameters> ] +rcpt Specifies the recipient. Can be used any number of times. +rcpt Parameters are ESMTP extensions. See "HELP DSN" for details. +data DATA +data Following text is collected as the message. +data End with a single dot. +rset RSET +rset Resets the system. +quit QUIT +quit Exit sendmail (SMTP). +auth AUTH mechanism [initial-response] +auth Start authentication. +starttls STARTTLS +starttls Start TLS negotiation. +verb VERB +verb Go into verbose mode. This sends 0xy responses that are +verb not RFC821 standard (but should be) They are recognized +verb by humans and other sendmail implementations. +vrfy VRFY <recipient> +vrfy Verify an address. If you want to see what it aliases +vrfy to, use EXPN instead. +expn EXPN <recipient> +expn Expand an address. If the address indicates a mailing +expn list, return the contents of that list. +noop NOOP +noop Do nothing. +send SEND FROM: <sender> +send replaces the MAIL command, and can be used to send +send directly to a users terminal. Not supported in this +send implementation. +soml SOML FROM: <sender> +soml Send or mail. If the user is logged in, send directly, +soml otherwise mail. Not supported in this implementation. +saml SAML FROM: <sender> +saml Send and mail. Send directly to the user's terminal, +saml and also mail a letter. Not supported in this +saml implementation. +turn TURN +turn Reverses the direction of the connection. Not currently +turn implemented. +etrn ETRN [ <hostname> | @<domain> | #<queuename> ] +etrn Run the queue for the specified <hostname>, or +etrn all hosts within a given <domain>, or a specially-named +etrn <queuename> (implementation-specific). +dsn MAIL FROM: <sender> [ RET={ FULL | HDRS} ] [ ENVID=<envid> ] +dsn RCPT TO: <recipient> [ NOTIFY={NEVER,SUCCESS,FAILURE,DELAY} ] +dsn [ ORCPT=<recipient> ] +dsn SMTP Delivery Status Notifications. +dsn Descriptions: +dsn RET Return either the full message or only headers. +dsn ENVID Sender's "envelope identifier" for tracking. +dsn NOTIFY When to send a DSN. Multiple options are OK, comma- +dsn delimited. NEVER must appear by itself. +dsn ORCPT Original recipient. +-bt Help for test mode: +-bt ? :this help message. +-bt .Dmvalue :define macro `m' to `value'. +-bt .Ccvalue :add `value' to class `c'. +-bt =Sruleset :dump the contents of the indicated ruleset. +-bt =M :display the known mailers. +-bt -ddebug-spec :equivalent to the command-line -d debug flag. +-bt $$m :print the value of macro $$m. +-bt $$=c :print the contents of class $$=c. +-bt /mx host :returns the MX records for `host'. +-bt /parse address :parse address, returning the value of crackaddr, and +-bt the parsed address. +-bt /try mailer addr :rewrite address into the form it will have when +-bt presented to the indicated mailer. +-bt /tryflags flags :set flags used by parsing. The flags can be `H' for +-bt Header or `E' for Envelope, and `S' for Sender or `R' +-bt for Recipient. These can be combined, `HR' sets +-bt flags for header recipients. +-bt /canon hostname :try to canonify hostname. +-bt /map mapname key :look up `key' in the indicated `mapname'. +-bt /quit :quit address test mode. +-bt rules addr :run the indicated address through the named rules. +-bt Rules can be a comma separated list of rules. +control Help for smcontrol: +control help This message. +control restart Restart sendmail. +control shutdown Shutdown sendmail. +control status Show sendmail status. diff --git a/contrib/sendmail/src/milter.c b/contrib/sendmail/src/milter.c new file mode 100644 index 0000000000000..82f1574c4fb30 --- /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 */ diff --git a/contrib/sendmail/src/sfsasl.c b/contrib/sendmail/src/sfsasl.c new file mode 100644 index 0000000000000..43a65d198b0dd --- /dev/null +++ b/contrib/sendmail/src/sfsasl.c @@ -0,0 +1,367 @@ +/* + * 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: sfsasl.c,v 8.17.4.7 2000/07/18 18:44:51 gshapiro Exp $"; +#endif /* ! lint */ + +#if SFIO +# include <sfio/stdio.h> +#endif /* SFIO */ + +#include <stdlib.h> +#include <sendmail.h> + +#if SASL && SFIO +/* +** SASL +*/ + +# include <sasl.h> +# include "sfsasl.h" + +static ssize_t +sasl_read(f, buf, size, disc) + Sfio_t *f; + Void_t *buf; + size_t size; + Sfdisc_t *disc; +{ + int len, result; + char *outbuf; + unsigned int outlen; + Sasldisc_t *sd = (Sasldisc_t *) disc; + + len = sfrd(f, buf, size, disc); + + if (len <= 0) + return len; + + result = sasl_decode(sd->conn, buf, len, &outbuf, &outlen); + + if (result != SASL_OK) + { + /* eventually, we'll want an exception here */ + return -1; + } + + if (outbuf != NULL) + { + (void)memcpy(buf, outbuf, outlen); + free(outbuf); + } + return outlen; +} + +static ssize_t +sasl_write(f, buf, size, disc) + Sfio_t *f; + const Void_t *buf; + size_t size; + Sfdisc_t *disc; +{ + int result; + char *outbuf; + unsigned int outlen; + Sasldisc_t *sd = (Sasldisc_t *) disc; + + result = sasl_encode(sd->conn, buf, size, &outbuf, &outlen); + + if (result != SASL_OK) + { + /* eventually, we'll want an exception here */ + return -1; + } + + if (outbuf != NULL) + { + sfwr(f, outbuf, outlen, disc); + free(outbuf); + } + return size; +} + +int +sfdcsasl(fin, fout, conn) + Sfio_t *fin; + Sfio_t *fout; + sasl_conn_t *conn; +{ + Sasldisc_t *saslin, *saslout; + + if (conn == NULL) + { + /* no need to do anything */ + return 0; + } + + if ((saslin = (Sasldisc_t *) malloc(sizeof(Sasldisc_t))) == NULL) + return -1; + if ((saslout = (Sasldisc_t *) malloc(sizeof(Sasldisc_t))) == NULL) + { + free(saslin); + return -1; + } + + saslin->disc.readf = sasl_read; + saslin->disc.writef = sasl_write; + saslin->disc.seekf = NULL; + saslin->disc.exceptf = NULL; + + saslout->disc.readf = sasl_read; + saslout->disc.writef = sasl_write; + saslout->disc.seekf = NULL; + saslout->disc.exceptf = NULL; + + saslin->conn = conn; + saslout->conn = conn; + + if (sfdisc(fin, (Sfdisc_t *) saslin) != (Sfdisc_t *) saslin || + sfdisc(fout, (Sfdisc_t *) saslout) != (Sfdisc_t *) saslout) + { + free(saslin); + free(saslout); + return -1; + } + return 0; +} +#endif /* SASL && SFIO */ + +#if STARTTLS && (SFIO || _FFR_TLS_TOREK) +/* +** STARTTLS +*/ + +# include "sfsasl.h" +# include <openssl/err.h> + +static ssize_t +# if SFIO +tls_read(f, buf, size, disc) + Sfio_t *f; + Void_t *buf; + size_t size; + Sfdisc_t *disc; +# else /* SFIO */ +tls_read(disc, buf, size) + void *disc; + void *buf; + size_t size; +# endif /* SFIO */ +{ + int r; + Tlsdisc_t *sd; + + /* Cast back to correct type */ + sd = (Tlsdisc_t *) disc; + + r = SSL_read(sd->con, (char *) buf, size); + if (r < 0 && LogLevel > 7) + { + char *err; + + err = NULL; + switch (SSL_get_error(sd->con, r)) + { + case SSL_ERROR_NONE: + break; + case SSL_ERROR_WANT_WRITE: + err = "write W BLOCK"; + break; + case SSL_ERROR_WANT_READ: + err = "write R BLOCK"; + break; + case SSL_ERROR_WANT_X509_LOOKUP: + err = "write X BLOCK"; + break; + case SSL_ERROR_ZERO_RETURN: + break; + case SSL_ERROR_SYSCALL: + err = "syscall error"; +/* + get_last_socket_error()); +*/ + break; + case SSL_ERROR_SSL: + err = "generic SSL error"; + break; + } + if (err != NULL) + sm_syslog(LOG_WARNING, NOQID, "TLS: read error: %s", + err); + } + return r; +} + +static ssize_t +# if SFIO +tls_write(f, buf, size, disc) + Sfio_t *f; + const Void_t *buf; + size_t size; + Sfdisc_t *disc; +# else /* SFIO */ +tls_write(disc, buf, size) + void *disc; + const void *buf; + size_t size; +# endif /* SFIO */ +{ + int r; + Tlsdisc_t *sd; + + /* Cast back to correct type */ + sd = (Tlsdisc_t *) disc; + + r = SSL_write(sd->con, (char *)buf, size); + if (r < 0 && LogLevel > 7) + { + char *err; + + err = NULL; + switch (SSL_get_error(sd->con, r)) + { + case SSL_ERROR_NONE: + break; + case SSL_ERROR_WANT_WRITE: + err = "write W BLOCK"; + break; + case SSL_ERROR_WANT_READ: + err = "write R BLOCK"; + break; + case SSL_ERROR_WANT_X509_LOOKUP: + err = "write X BLOCK"; + break; + case SSL_ERROR_ZERO_RETURN: + break; + case SSL_ERROR_SYSCALL: + err = "syscall error"; +/* + get_last_socket_error()); +*/ + break; + case SSL_ERROR_SSL: + err = "generic SSL error"; +/* + ERR_GET_REASON(ERR_peek_error())); +*/ + break; + } + if (err != NULL) + sm_syslog(LOG_WARNING, NOQID, "TLS: write error: %s", + err); + } + return r; +} + +# if !SFIO +static int +tls_close(cookie) + void *cookie; +{ + int retval = 0; + Tlsdisc_t *tc; + + /* Cast back to correct type */ + tc = (Tlsdisc_t *)cookie; + + if (tc->fp != NULL) + { + retval = fclose(tc->fp); + tc->fp = NULL; + } + + free(tc); + return retval; +} +# endif /* !SFIO */ + +int +sfdctls(fin, fout, con) +# if SFIO + Sfio_t *fin; + Sfio_t *fout; +# else /* SFIO */ + FILE **fin; + FILE **fout; +# endif /* SFIO */ + SSL *con; +{ + Tlsdisc_t *tlsin, *tlsout; +# if !SFIO + FILE *fp; +# endif /* !SFIO */ + + if (con == NULL) + return 0; + + if ((tlsin = (Tlsdisc_t *) malloc(sizeof(Tlsdisc_t))) == NULL) + return -1; + if ((tlsout = (Tlsdisc_t *) malloc(sizeof(Tlsdisc_t))) == NULL) + { + free(tlsin); + return -1; + } + +# if SFIO + tlsin->disc.readf = tls_read; + tlsin->disc.writef = tls_write; + tlsin->disc.seekf = NULL; + tlsin->disc.exceptf = NULL; + tlsin->con = con; + + tlsout->disc.readf = tls_read; + tlsout->disc.writef = tls_write; + tlsout->disc.seekf = NULL; + tlsout->disc.exceptf = NULL; + tlsout->con = con; + + SSL_set_rfd(con, fileno(fin)); /* fileno or sffileno? XXX */ + SSL_set_wfd(con, fileno(fout)); + if (sfdisc(fin, (Sfdisc_t *) tlsin) != (Sfdisc_t *) tlsin || + sfdisc(fout, (Sfdisc_t *) tlsout) != (Sfdisc_t *) tlsout) + { + free(tlsin); + free(tlsout); + return -1; + } +# else /* SFIO */ + tlsin->fp = *fin; + tlsin->con = con; + fp = funopen(tlsin, tls_read, tls_write, NULL, tls_close); + if (fp == NULL) + { + free(tlsin); + return -1; + } + *fin = fp; + + tlsout->fp = *fout; + tlsout->con = con; + fp = funopen(tlsout, tls_read, tls_write, NULL, tls_close); + if (fp == NULL) + { + FILE *save; + + /* Hack: Don't close underlying fp */ + save = tlsin->fp; + tlsin->fp = NULL; + fclose(*fin); + *fin = save; + free(tlsout); + return -1; + } + *fout = fp; + SSL_set_rfd(con, fileno(tlsin->fp)); + SSL_set_wfd(con, fileno(tlsout->fp)); +# endif /* SFIO */ + return 0; +} +#endif /* STARTTLS && (SFIO || _FFR_TLS_TOREK) */ diff --git a/contrib/sendmail/src/sfsasl.h b/contrib/sendmail/src/sfsasl.h new file mode 100644 index 0000000000000..b276e27c03bd7 --- /dev/null +++ b/contrib/sendmail/src/sfsasl.h @@ -0,0 +1,60 @@ +/* + * 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. + * + * $Id: sfsasl.h,v 8.13.4.4 2000/07/18 18:44:51 gshapiro Exp $" + */ + +#ifndef SFSASL_H +# define SFSASL_H + +# if SFIO +# include <sfio.h> +# endif /* SFIO */ + +# if SASL +# if SFIO + +/* sf discipline to add sasl */ +typedef struct _sasldisc +{ + Sfdisc_t disc; + sasl_conn_t *conn; +} Sasldisc_t; + +extern int sfdcsasl __P((Sfio_t *, Sfio_t *, sasl_conn_t *)); + +# endif /* SFIO */ +# endif /* SASL */ + +# if STARTTLS +# if SFIO + +/* sf discipline to add tls */ +typedef struct _tlsdisc +{ + Sfdisc_t disc; + SSL *con; +} Tlsdisc_t; + +extern int sfdctls __P((Sfio_t *, Sfio_t *, SSL *)); + +# else /* SFIO */ +# if _FFR_TLS_TOREK + +typedef struct tls_conn +{ + FILE *fp; /* original FILE * */ + SSL *con; /* SSL context */ +} Tlsdisc_t; + +extern int sfdctls __P((FILE **, FILE **, SSL *)); + +# endif /* _FFR_TLS_TOREK */ +# endif /* SFIO */ +# endif /* STARTTLS */ +#endif /* ! SFSASL_H */ diff --git a/contrib/sendmail/src/shmticklib.c b/contrib/sendmail/src/shmticklib.c new file mode 100644 index 0000000000000..a3e1ad5602671 --- /dev/null +++ b/contrib/sendmail/src/shmticklib.c @@ -0,0 +1,84 @@ +/* + * 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. + * + * Contributed by Exactis.com, Inc. + * + */ + +#ifndef lint +static char id[] = "@(#)$Id: shmticklib.c,v 8.6 2000/02/26 01:32:27 gshapiro Exp $"; +#endif /* ! lint */ + +#if _FFR_SHM_STATUS +# if SFIO +# include <sfio/stdio.h> +# else /* !SFIO */ +# include <stdio.h> +# endif /* SFIO */ +# include <sys/types.h> +# include <sys/ipc.h> +# include <sys/shm.h> + +# include "statusd_shm.h" + +/* +** SHMTICK -- increment a shared memory variable +** +** Parameters: +** inc_me -- identity of shared memory segment +** what -- which variable to increment +** +** Returns: +** none +*/ + +void +shmtick(inc_me, what) + int inc_me; + int what; +{ + static int shmid = -1; + static STATUSD_SHM *sp = (STATUSD_SHM *)-1; + static unsigned int cookie = 0; + + if (shmid < 0) + { + int size = sizeof(STATUSD_SHM); + + shmid = shmget(STATUSD_SHM_KEY, size, 0); + if (shmid < 0) + return; + } + if ((unsigned long *)sp == (unsigned long *)-1) + { + sp = (STATUSD_SHM *)shmat(shmid, NULL, 0); + if ((unsigned long *)sp == (unsigned long *)-1) + return; + } + if (sp->magic != STATUSD_MAGIC) + { + /* + ** possible race condition, wait for + ** statusd to initialize. + */ + + return; + } + if (what >= STATUSD_LONGS) + what = STATUSD_LONGS - 1; + if (inc_me >= STATUSD_LONGS) + inc_me = STATUSD_LONGS - 1; + + if (sp->ul[STATUSD_COOKIE] != cookie) + { + cookie = sp->ul[STATUSD_COOKIE]; + ++(sp->ul[inc_me]); + } + ++(sp->ul[what]); +} +#endif /* _FFR_SHM_STATUS */ diff --git a/contrib/sendmail/src/statusd_shm.h b/contrib/sendmail/src/statusd_shm.h new file mode 100644 index 0000000000000..c48b2fd782c84 --- /dev/null +++ b/contrib/sendmail/src/statusd_shm.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 1999 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. + * + * $Id: statusd_shm.h,v 8.4 1999/05/18 08:00:04 gshapiro Exp $ + * + * Contributed by Exactis.com, Inc. + * + */ + +/* +** The shared memory part of statusd. +** +** Attach to STATUSD_SHM_KEY and update the counter appropriate +** for your type of service. +** +*/ + +#define STATUSD_MAGIC 110946 +#define STATUSD_SHM_KEY (key_t)(13) +#define STATUSD_LONGS (2) + +typedef struct { + unsigned long magic; + unsigned long ul[STATUSD_LONGS]; +} STATUSD_SHM; + +/* +** Offsets into ul[]. The appropriate program +** increments these as appropriate. +*/ + +#define STATUSD_COOKIE (0) /* reregister cookie */ + +/* sendmail */ +#define STATUSD_SM_NSENDMAIL (1) /* how many running */ + +extern void shmtick __P((int, int)); + diff --git a/contrib/sendmail/src/timers.c b/contrib/sendmail/src/timers.c new file mode 100644 index 0000000000000..74d0ccf78ee01 --- /dev/null +++ b/contrib/sendmail/src/timers.c @@ -0,0 +1,229 @@ +/* + * Copyright (c) 1999 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. + * + * Contributed by Exactis.com, Inc. + * + */ + +#ifndef lint +static char id[] = "@(#)$Id: timers.c,v 8.13 1999/11/23 07:22:28 gshapiro Exp $"; +#endif /* ! lint */ + +#if _FFR_TIMERS +# include <sys/types.h> +# include <sys/time.h> +# include "sendmail.h" +# include <sys/resource.h> /* Must be after sendmail.h for NCR MP-RAS */ + +static TIMER BaseTimer; /* current baseline */ +static int NTimers; /* current pointer into stack */ +static TIMER *TimerStack[MAXTIMERSTACK]; + +static void +# ifdef __STDC__ +warntimer(const char *msg, ...) +# else /* __STDC__ */ +warntimer(msg, va_alist) + const char *msg; + va_dcl +# endif /* __STDC__ */ +{ + char buf[MAXLINE]; + VA_LOCAL_DECL + +# if 0 + if (!tTd(98, 30)) + return; +# endif /* 0 */ + VA_START(msg); + vsnprintf(buf, sizeof buf, msg, ap); + VA_END; + sm_syslog(LOG_NOTICE, CurEnv->e_id, "%s; e_timers=0x%lx", + buf, (u_long) &CurEnv->e_timers); +} + +static void +zerotimer(ptimer) + TIMER *ptimer; +{ + memset(ptimer, '\0', sizeof *ptimer); +} + +static void +addtimer(ta, tb) + TIMER *ta; + TIMER *tb; +{ + tb->ti_wall_sec += ta->ti_wall_sec; + tb->ti_wall_usec += ta->ti_wall_usec; + if (tb->ti_wall_usec > 1000000) + { + tb->ti_wall_sec++; + tb->ti_wall_usec -= 1000000; + } + tb->ti_cpu_sec += ta->ti_cpu_sec; + tb->ti_cpu_usec += ta->ti_cpu_usec; + if (tb->ti_cpu_usec > 1000000) + { + tb->ti_cpu_sec++; + tb->ti_cpu_usec -= 1000000; + } +} + +static void +subtimer(ta, tb) + TIMER *ta; + TIMER *tb; +{ + tb->ti_wall_sec -= ta->ti_wall_sec; + tb->ti_wall_usec -= ta->ti_wall_usec; + if (tb->ti_wall_usec < 0) + { + tb->ti_wall_sec--; + tb->ti_wall_usec += 1000000; + } + tb->ti_cpu_sec -= ta->ti_cpu_sec; + tb->ti_cpu_usec -= ta->ti_cpu_usec; + if (tb->ti_cpu_usec < 0) + { + tb->ti_cpu_sec--; + tb->ti_cpu_usec += 1000000; + } +} + +static int +getcurtimer(ptimer) + TIMER *ptimer; +{ + struct rusage ru; + struct timeval now; + + if (getrusage(RUSAGE_SELF, &ru) < 0 || gettimeofday(&now, NULL) < 0) + return -1; + ptimer->ti_wall_sec = now.tv_sec; + ptimer->ti_wall_usec = now.tv_usec; + ptimer->ti_cpu_sec = ru.ru_utime.tv_sec + ru.ru_stime.tv_sec; + ptimer->ti_cpu_usec = ru.ru_utime.tv_usec + ru.ru_stime.tv_usec; + if (ptimer->ti_cpu_usec > 1000000) + { + ptimer->ti_cpu_sec++; + ptimer->ti_cpu_usec -= 1000000; + } + return 0; +} + +static void +getinctimer(ptimer) + TIMER *ptimer; +{ + TIMER cur; + + if (getcurtimer(&cur) < 0) + { + zerotimer(ptimer); + return; + } + if (BaseTimer.ti_wall_sec == 0) + { + /* first call */ + memset(ptimer, '\0', sizeof *ptimer); + } + else + { + *ptimer = cur; + subtimer(&BaseTimer, ptimer); + } + BaseTimer = cur; +} + +void +flushtimers() +{ + NTimers = 0; + (void) getcurtimer(&BaseTimer); +} + +void +pushtimer(ptimer) + TIMER *ptimer; +{ + int i; + int save_errno = errno; + TIMER incr; + + /* find how much time has changed since last call */ + getinctimer(&incr); + + /* add that into the old timers */ + i = NTimers; + if (i > MAXTIMERSTACK) + i = MAXTIMERSTACK; + while (--i >= 0) + { + addtimer(&incr, TimerStack[i]); + if (TimerStack[i] == ptimer) + { + warntimer("Timer@0x%lx already on stack, index=%d, NTimers=%d", + (u_long) ptimer, i, NTimers); + errno = save_errno; + return; + } + } + errno = save_errno; + + /* handle stack overflow */ + if (NTimers >= MAXTIMERSTACK) + return; + + /* now add the timer to the stack */ + TimerStack[NTimers++] = ptimer; +} + +void +poptimer(ptimer) + TIMER *ptimer; +{ + int i; + int save_errno = errno; + TIMER incr; + + /* find how much time has changed since last call */ + getinctimer(&incr); + + /* add that into the old timers */ + i = NTimers; + if (i > MAXTIMERSTACK) + i = MAXTIMERSTACK; + while (--i >= 0) + addtimer(&incr, TimerStack[i]); + + /* pop back to this timer */ + for (i = 0; i < NTimers; i++) + if (TimerStack[i] == ptimer) + break; + if (i != NTimers - 1) + warntimer("poptimer: odd pop (timer=0x%lx, index=%d, NTimers=%d)", + (u_long) ptimer, i, NTimers); + NTimers = i; + + /* clean up and return */ + errno = save_errno; +} + +char * +strtimer(ptimer) + TIMER *ptimer; +{ + static char buf[40]; + + snprintf(buf, sizeof buf, "%ld.%06ldr/%ld.%06ldc", + ptimer->ti_wall_sec, ptimer->ti_wall_usec, + ptimer->ti_cpu_sec, ptimer->ti_cpu_usec); + return buf; +} +#endif /* _FFR_TIMERS */ diff --git a/contrib/sendmail/src/timers.h b/contrib/sendmail/src/timers.h new file mode 100644 index 0000000000000..e86b6ec3d1327 --- /dev/null +++ b/contrib/sendmail/src/timers.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 1999 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. + * + * $Id: timers.h,v 8.4 1999/11/04 19:31:26 ca Exp $ + * + * Contributed by Exactis.com, Inc. + * + */ + +#ifndef TIMERS_H +#define TIMERS_H 1 + +#define MAXTIMERSTACK 20 /* maximum timer depth */ + +#define TIMER struct _timer + +TIMER +{ + long ti_wall_sec; /* wall clock seconds */ + long ti_wall_usec; /* ... microseconds */ + long ti_cpu_sec; /* cpu time seconds */ + long ti_cpu_usec; /* ... microseconds */ +}; + +extern void pushtimer __P((TIMER *)); +extern void poptimer __P((TIMER *)); +extern char *strtimer __P((TIMER *)); +#endif /* TIMERS_H */ |
