diff options
author | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
---|---|---|
committer | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
commit | 7c478bd95313f5f23a4c958a745db2134aa03244 (patch) | |
tree | c871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/cmd/sendmail/libmilter | |
download | illumos-joyent-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz |
OpenSolaris Launch
Diffstat (limited to 'usr/src/cmd/sendmail/libmilter')
-rw-r--r-- | usr/src/cmd/sendmail/libmilter/Makefile | 60 | ||||
-rw-r--r-- | usr/src/cmd/sendmail/libmilter/Makefile.com | 66 | ||||
-rw-r--r-- | usr/src/cmd/sendmail/libmilter/README | 420 | ||||
-rw-r--r-- | usr/src/cmd/sendmail/libmilter/comm.c | 362 | ||||
-rw-r--r-- | usr/src/cmd/sendmail/libmilter/engine.c | 1252 | ||||
-rw-r--r-- | usr/src/cmd/sendmail/libmilter/handler.c | 68 | ||||
-rw-r--r-- | usr/src/cmd/sendmail/libmilter/i386/Makefile | 29 | ||||
-rw-r--r-- | usr/src/cmd/sendmail/libmilter/libmilter.h | 189 | ||||
-rw-r--r-- | usr/src/cmd/sendmail/libmilter/listener.c | 955 | ||||
-rw-r--r-- | usr/src/cmd/sendmail/libmilter/llib-lmilter | 32 | ||||
-rw-r--r-- | usr/src/cmd/sendmail/libmilter/main.c | 245 | ||||
-rw-r--r-- | usr/src/cmd/sendmail/libmilter/signal.c | 223 | ||||
-rw-r--r-- | usr/src/cmd/sendmail/libmilter/sm_gethost.c | 150 | ||||
-rw-r--r-- | usr/src/cmd/sendmail/libmilter/smfi.c | 650 | ||||
-rw-r--r-- | usr/src/cmd/sendmail/libmilter/sparc/Makefile | 29 |
15 files changed, 4730 insertions, 0 deletions
diff --git a/usr/src/cmd/sendmail/libmilter/Makefile b/usr/src/cmd/sendmail/libmilter/Makefile new file mode 100644 index 0000000000..cf41698dbe --- /dev/null +++ b/usr/src/cmd/sendmail/libmilter/Makefile @@ -0,0 +1,60 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License, Version 1.0 only +# (the "License"). You may not use this file except in compliance +# with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2004 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include $(SRC)/lib/Makefile.lib + +HDRS = mfapi.h mfdef.h +HDRDIR = ../include/libmilter +ROOTHDRDIR = $(ROOT)/usr/include/libmilter + +SUBDIRS = $(MACH) + +all := TARGET = all +clean := TARGET = clean +clobber := TARGET = clobber +install := TARGET = install +lint := TARGET = lint + +.KEEP_STATE: + +all clean clobber lint: $(SUBDIRS) +install: $(SUBDIRS) $(ROOTHDRDIR)/README + +install_h: $(ROOTHDRS) + +check: $(CHECKHDRS) + +$(ROOTHDRDIR)/README%: README% + $(INS.file) + +$(SUBDIRS): FRC + @cd $@; pwd; $(MAKE) $(TARGET) + +FRC: + +include $(SRC)/lib/Makefile.targ diff --git a/usr/src/cmd/sendmail/libmilter/Makefile.com b/usr/src/cmd/sendmail/libmilter/Makefile.com new file mode 100644 index 0000000000..c4ac692bfc --- /dev/null +++ b/usr/src/cmd/sendmail/libmilter/Makefile.com @@ -0,0 +1,66 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License, Version 1.0 only +# (the "License"). You may not use this file except in compliance +# with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2005 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +LIBRARY= libmilter.a +VERS= .1 +LOCOBJS= main.o engine.o listener.o handler.o comm.o smfi.o signal.o \ + sm_gethost.o +REMOBJS= errstring.o strl.o +OBJECTS= $(LOCOBJS) $(REMOBJS) +SENDMAIL= $(SRC)/cmd/sendmail + +include $(SENDMAIL)/Makefile.cmd +include $(SRC)/lib/Makefile.lib + +REMDIR= $(SENDMAIL)/libsm +SRCDIR= $(SENDMAIL)/libmilter + +SRCS= $(LOCOBJS:%.o=$(SRCDIR)/%.c) $(REMOBJS:%.o=$(REMDIR)/%.c) + +INCPATH= -I$(SENDMAIL)/src -I$(SENDMAIL)/include +ENVDEF= $(RLS_DEF) -DMILTER -DNETINET6 -DNOT_SENDMAIL \ + -Dsm_snprintf=snprintf -D_REENTRANT -D_FFR_MILTER_ROOT_UNSAFE +CPPFLAGS += $(INCPATH) $(ENVDEF) + +LIBS= $(DYNLIB) $(LINTLIB) +LDLIBS += -lc -lsocket -lnsl +$(LINTLIB) := SRCS= $(SRCDIR)/$(LINTSRC) + +.KEEP_STATE: + +all: $(LIBS) + +install: all .WAIT $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT) + +lint: lintcheck + +include $(SRC)/lib/Makefile.targ + +pics/%.o: $(REMDIR)/%.c + $(COMPILE.c) -o $@ $< + $(POST_PROCESS_O) diff --git a/usr/src/cmd/sendmail/libmilter/README b/usr/src/cmd/sendmail/libmilter/README new file mode 100644 index 0000000000..4079c4f9ef --- /dev/null +++ b/usr/src/cmd/sendmail/libmilter/README @@ -0,0 +1,420 @@ +# +# Copyright 2004 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +The sendmail Mail Filter API (Milter) is designed to allow third-party +programs access to mail messages as they are being processed in order to +filter meta-information and content. + +This README file describes the steps needed to compile and run a filter, +through reference to a sample filter which is attached at the end of this +file. + ++----------------+ +| SECURITY HINTS | ++----------------+ + +Note: we strongly recommend not to run any milter as root. Libmilter +does not need root access to communicate with sendmail. It is a +good security practice to run a program only with root privileges +if really necessary. A milter should probably check first whether +it runs as root and refuse to start in that case. libmilter will +not unlink a socket when running as root. + ++-------------------+ +| BUILDING A FILTER | ++-------------------+ + +The following command presumes that the sample code from the end of this +README is saved to a file named 'sample.c'. + + cc -D_REENTRANT -o sample sample.c -lmilter + +Filters must be thread-safe! + +Note that since filters use threads, it may be necessary to alter per +process limits in your filter. For example, you might look at using +setrlimit() to increase the number of open file descriptors if your filter +is going to be busy. + + ++----------------------------------------+ +| SPECIFYING FILTERS IN SENDMAIL CONFIGS | ++----------------------------------------+ + +Filters are specified with a key letter ``X'' (for ``eXternal''). + +For example: + + Xfilter1, S=local:/var/run/f1.sock, F=R + Xfilter2, S=inet6:999@localhost, F=T, T=C:10m;S:1s;R:1s;E:5m + Xfilter3, S=inet:3333@localhost + +specifies three filters. Filters can be specified in your .mc file using +the following: + + INPUT_MAIL_FILTER(`filter1', `S=local:/var/run/f1.sock, F=R') + INPUT_MAIL_FILTER(`filter2', `S=inet6:999@localhost, F=T, T=C:10m;S:1s;R:1s;E:5m') + INPUT_MAIL_FILTER(`filter3', `S=inet:3333@localhost') + +The first attaches to a Unix-domain socket in the /var/run directory; the +second uses an IPv6 socket on port 999 of localhost, and the third uses an +IPv4 socket on port 3333 of localhost. The current flags (F=) are: + + R Reject connection if filter unavailable + T Temporary fail connection if filter unavailable + +If neither F=R nor F=T is specified, the message is passed through sendmail +in case of filter errors as if the failing filters were not present. + +Finally, you can override the default timeouts used by sendmail when +talking to the filters using the T= equate. There are four fields inside +of the T= equate: + +Letter Meaning + C Timeout for connecting to a filter (if 0, use system timeout) + S Timeout for sending information from the MTA to a filter + R Timeout for reading reply from the filter + E Overall timeout between sending end-of-message to filter + and waiting for the final acknowledgment + +Note the separator between each is a ';' as a ',' already separates equates +and therefore can't separate timeouts. The default values (if not set in +the config) are: + +T=C:5m;S:10s;R:10s;E:5m + +where 's' is seconds and 'm' is minutes. + +Which filters are invoked and their sequencing is handled by the +InputMailFilters option. Note: if InputMailFilters is not defined no filters +will be used. + + O InputMailFilters=filter1, filter2, filter3 + +This is is set automatically according to the order of the +INPUT_MAIL_FILTER commands in your .mc file. Alternatively, you can +reset its value by setting confINPUT_MAIL_FILTERS in your .mc file. +This options causes the three filters to be called in the same order +they were specified. It allows for possible future filtering on output +(although this is not intended for this release). + +Also note that a filter can be defined without adding it to the input +filter list by using MAIL_FILTER() instead of INPUT_MAIL_FILTER() in your +.mc file. + +To test sendmail with the sample filter, the following might be added (in +the appropriate locations) to your .mc file: + + INPUT_MAIL_FILTER(`sample', `S=local:/var/run/f1.sock') + + ++------------------+ +| TESTING A FILTER | ++------------------+ + +Once you have compiled a filter, modified your .mc file and restarted +the sendmail process, you will want to test that the filter performs as +intended. + +The sample filter takes one argument -p, which indicates the local port +on which to create a listening socket for the filter. Maintaining +consistency with the suggested options for sendmail.cf, this would be the +UNIX domain socket located in /var/run/f1.sock. + + % ./sample -p local:/var/run/f1.sock + +If the sample filter returns immediately to a command line, there was either +an error with your command or a problem creating the specified socket. +Further logging can be captured through the syslogd daemon. Using the +'netstat -a' command can ensure that your filter process is listening on +the appropriate local socket. + +Email messages must be injected via SMTP to be filtered. There are two +simple means of doing this; either using the 'sendmail -bs' command, or +by telnetting to port 25 of the machine configured for milter. Once +connected via one of these options, the session can be continued through +the use of standard SMTP commands. + +% sendmail -bs +220 test.sendmail.com ESMTP Sendmail 8.11.0/8.11.0; Tue, 10 Nov 1970 13:05:23 -0500 (EST) +HELO localhost +250 test.sendmail.com Hello testy@localhost, pleased to meet you +MAIL From:<testy> +250 2.1.0 <testy>... Sender ok +RCPT To:<root> +250 2.1.5 <root>... Recipient ok +DATA +354 Enter mail, end with "." on a line by itself +From: testy@test.sendmail.com +To: root@test.sendmail.com +Subject: testing sample filter + +Sample body +. +250 2.0.0 dB73Zxi25236 Message accepted for delivery +QUIT +221 2.0.0 test.sendmail.com closing connection + +In the above example, the lines beginning with numbers are output by the +mail server, and those without are your input. If everything is working +properly, you will find a file in /tmp by the name of msg.XXXXXXXX (where +the Xs represent any combination of letters and numbers). This file should +contain the message body and headers from the test email entered above. + +If the sample filter did not log your test email, there are a number of +methods to narrow down the source of the problem. Check your system +logs written by syslogd and see if there are any pertinent lines. You +may need to reconfigure syslogd to capture all relevant data. Additionally, +the logging level of sendmail can be raised with the LogLevel option. +See the sendmail(8) manual page for more information. + + ++--------------------------+ +| SOURCE FOR SAMPLE FILTER | ++--------------------------+ + +/* A trivial filter that logs all email to a file. */ + +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> + +#include "libmilter/mfapi.h" + +#ifndef true +typedef int bool; +# define false 0 +# define true 1 +#endif /* ! true */ + +struct mlfiPriv +{ + char *mlfi_fname; + FILE *mlfi_fp; +}; + +#define MLFIPRIV ((struct mlfiPriv *) smfi_getpriv(ctx)) + +extern sfsistat mlfi_cleanup(SMFICTX *, bool); + +sfsistat +mlfi_envfrom(ctx, envfrom) + SMFICTX *ctx; + char **envfrom; +{ + struct mlfiPriv *priv; + int fd = -1; + + /* allocate some private memory */ + priv = malloc(sizeof *priv); + if (priv == NULL) + { + /* can't accept this message right now */ + return SMFIS_TEMPFAIL; + } + memset(priv, '\0', sizeof *priv); + + /* open a file to store this message */ + priv->mlfi_fname = strdup("/tmp/msg.XXXXXXXX"); + if (priv->mlfi_fname == NULL) + { + free(priv); + return SMFIS_TEMPFAIL; + } + if ((fd = mkstemp(priv->mlfi_fname)) < 0 || + (priv->mlfi_fp = fdopen(fd, "w+")) == NULL) + { + if (fd >= 0) + (void) close(fd); + free(priv->mlfi_fname); + free(priv); + return SMFIS_TEMPFAIL; + } + + /* save the private data */ + smfi_setpriv(ctx, priv); + + /* continue processing */ + return SMFIS_CONTINUE; +} + +sfsistat +mlfi_header(ctx, headerf, headerv) + SMFICTX *ctx; + char *headerf; + char *headerv; +{ + /* write the header to the log file */ + fprintf(MLFIPRIV->mlfi_fp, "%s: %s\r\n", headerf, headerv); + + /* continue processing */ + return SMFIS_CONTINUE; +} + +sfsistat +mlfi_eoh(ctx) + SMFICTX *ctx; +{ + /* output the blank line between the header and the body */ + fprintf(MLFIPRIV->mlfi_fp, "\r\n"); + + /* continue processing */ + return SMFIS_CONTINUE; +} + +sfsistat +mlfi_body(ctx, bodyp, bodylen) + SMFICTX *ctx; + u_char *bodyp; + size_t bodylen; +{ + /* output body block to log file */ + if (fwrite(bodyp, bodylen, 1, MLFIPRIV->mlfi_fp) == 0) + { + /* write failed */ + (void) mlfi_cleanup(ctx, false); + return SMFIS_TEMPFAIL; + } + + /* continue processing */ + return SMFIS_CONTINUE; +} + +sfsistat +mlfi_eom(ctx) + SMFICTX *ctx; +{ + return mlfi_cleanup(ctx, true); +} + +sfsistat +mlfi_close(ctx) + SMFICTX *ctx; +{ + return SMFIS_ACCEPT; +} + +sfsistat +mlfi_abort(ctx) + SMFICTX *ctx; +{ + return mlfi_cleanup(ctx, false); +} + +sfsistat +mlfi_cleanup(ctx, ok) + SMFICTX *ctx; + bool ok; +{ + sfsistat rstat = SMFIS_CONTINUE; + struct mlfiPriv *priv = MLFIPRIV; + char *p; + char host[512]; + char hbuf[1024]; + + if (priv == NULL) + return rstat; + + /* close the archive file */ + if (priv->mlfi_fp != NULL && fclose(priv->mlfi_fp) == EOF) + { + /* failed; we have to wait until later */ + rstat = SMFIS_TEMPFAIL; + (void) unlink(priv->mlfi_fname); + } + else if (ok) + { + /* add a header to the message announcing our presence */ + if (gethostname(host, sizeof host) < 0) + snprintf(host, sizeof host, "localhost"); + p = strrchr(priv->mlfi_fname, '/'); + if (p == NULL) + p = priv->mlfi_fname; + else + p++; + snprintf(hbuf, sizeof hbuf, "%s@%s", p, host); + smfi_addheader(ctx, "X-Archived", hbuf); + } + else + { + /* message was aborted -- delete the archive file */ + (void) unlink(priv->mlfi_fname); + } + + /* release private memory */ + free(priv->mlfi_fname); + free(priv); + smfi_setpriv(ctx, NULL); + + /* return status */ + return rstat; +} + +struct smfiDesc smfilter = +{ + "SampleFilter", /* filter name */ + SMFI_VERSION, /* version code -- do not change */ + SMFIF_ADDHDRS, /* flags */ + NULL, /* connection info filter */ + NULL, /* SMTP HELO command filter */ + mlfi_envfrom, /* envelope sender filter */ + NULL, /* envelope recipient filter */ + mlfi_header, /* header filter */ + mlfi_eoh, /* end of header */ + mlfi_body, /* body block filter */ + mlfi_eom, /* end of message */ + mlfi_abort, /* message aborted */ + mlfi_close /* connection cleanup */ +}; + + +int +main(argc, argv) + int argc; + char *argv[]; +{ + bool setconn = false; + int c; + const char *args = "p:"; + + /* Process command line options */ + while ((c = getopt(argc, argv, args)) != -1) + { + switch (c) + { + case 'p': + if (optarg == NULL || *optarg == '\0') + { + (void) fprintf(stderr, "Illegal conn: %s\n", + optarg); + exit(EX_USAGE); + } + (void) smfi_setconn(optarg); + setconn = true; + break; + + } + } + if (!setconn) + { + fprintf(stderr, "%s: Missing required -p argument\n", argv[0]); + exit(EX_USAGE); + } + if (smfi_register(smfilter) == MI_FAILURE) + { + fprintf(stderr, "smfi_register failed\n"); + exit(EX_UNAVAILABLE); + } + return smfi_main(); +} + +/* eof */ + +$Revision: 8.35.2.2 $, Last updated $Date: 2003/05/26 04:10:06 $ diff --git a/usr/src/cmd/sendmail/libmilter/comm.c b/usr/src/cmd/sendmail/libmilter/comm.c new file mode 100644 index 0000000000..fd64922d37 --- /dev/null +++ b/usr/src/cmd/sendmail/libmilter/comm.c @@ -0,0 +1,362 @@ +/* + * Copyright (c) 1999-2004 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. + * + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sm/gen.h> +SM_RCSID("@(#)$Id: comm.c,v 8.66 2004/08/20 20:38:35 ca Exp $") + +#include "libmilter.h" +#include <sm/errstring.h> +#include <sys/uio.h> + +static ssize_t retry_writev __P((socket_t, struct iovec *, int, struct timeval *)); +static size_t Maxdatasize = MILTER_MAX_DATA_SIZE; + +#if _FFR_MAXDATASIZE +/* +** SMFI_SETMAXDATASIZE -- set limit for milter data read/write. +** +** Parameters: +** sz -- new limit. +** +** Returns: +** old limit +*/ + +size_t +smfi_setmaxdatasize(sz) + size_t sz; +{ + size_t old; + + old = Maxdatasize; + Maxdatasize = sz; + return old; +} +#endif /* _FFR_MAXDATASIZE */ + +/* +** MI_RD_CMD -- read a command +** +** Parameters: +** sd -- socket descriptor +** timeout -- maximum time to wait +** cmd -- single character command read from sd +** rlen -- pointer to length of result +** name -- name of milter +** +** Returns: +** buffer with rest of command +** (malloc()ed here, should be free()d) +** hack: encode error in cmd +*/ + +char * +mi_rd_cmd(sd, timeout, cmd, rlen, name) + socket_t sd; + struct timeval *timeout; + char *cmd; + size_t *rlen; + char *name; +{ + ssize_t len; + mi_int32 expl; + ssize_t i; + FD_RD_VAR(rds, excs); + int ret; + int save_errno; + char *buf; + char data[MILTER_LEN_BYTES + 1]; + + *cmd = '\0'; + *rlen = 0; + + i = 0; + for (;;) + { + FD_RD_INIT(sd, rds, excs); + ret = FD_RD_READY(sd, rds, excs, timeout); + if (ret == 0) + break; + else if (ret < 0) + { + if (errno == EINTR) + continue; + break; + } + if (FD_IS_RD_EXC(sd, rds, excs)) + { + *cmd = SMFIC_SELECT; + return NULL; + } + + len = MI_SOCK_READ(sd, data + i, sizeof data - i); + if (MI_SOCK_READ_FAIL(len)) + { + smi_log(SMI_LOG_ERR, + "%s, mi_rd_cmd: read returned %d: %s", + name, (int) len, sm_errstring(errno)); + *cmd = SMFIC_RECVERR; + return NULL; + } + if (len == 0) + { + *cmd = SMFIC_EOF; + return NULL; + } + if (len >= (ssize_t) sizeof data - i) + break; + i += len; + } + if (ret == 0) + { + *cmd = SMFIC_TIMEOUT; + return NULL; + } + else if (ret < 0) + { + smi_log(SMI_LOG_ERR, + "%s: mi_rd_cmd: select returned %d: %s", + name, ret, sm_errstring(errno)); + *cmd = SMFIC_RECVERR; + return NULL; + } + + *cmd = data[MILTER_LEN_BYTES]; + data[MILTER_LEN_BYTES] = '\0'; + (void) memcpy((void *) &expl, (void *) &(data[0]), MILTER_LEN_BYTES); + expl = ntohl(expl) - 1; + if (expl <= 0) + return NULL; + if (expl > Maxdatasize) + { + *cmd = SMFIC_TOOBIG; + return NULL; + } +#if _FFR_ADD_NULL + buf = malloc(expl + 1); +#else /* _FFR_ADD_NULL */ + buf = malloc(expl); +#endif /* _FFR_ADD_NULL */ + if (buf == NULL) + { + *cmd = SMFIC_MALLOC; + return NULL; + } + + i = 0; + for (;;) + { + FD_RD_INIT(sd, rds, excs); + ret = FD_RD_READY(sd, rds, excs, timeout); + if (ret == 0) + break; + else if (ret < 0) + { + if (errno == EINTR) + continue; + break; + } + if (FD_IS_RD_EXC(sd, rds, excs)) + { + *cmd = SMFIC_SELECT; + free(buf); + return NULL; + } + len = MI_SOCK_READ(sd, buf + i, expl - i); + if (MI_SOCK_READ_FAIL(len)) + { + smi_log(SMI_LOG_ERR, + "%s: mi_rd_cmd: read returned %d: %s", + name, (int) len, sm_errstring(errno)); + ret = -1; + break; + } + if (len == 0) + { + *cmd = SMFIC_EOF; + free(buf); + return NULL; + } + if (len > expl - i) + { + *cmd = SMFIC_RECVERR; + free(buf); + return NULL; + } + if (len >= expl - i) + { + *rlen = expl; +#if _FFR_ADD_NULL + /* makes life simpler for common string routines */ + buf[expl] = '\0'; +#endif /* _FFR_ADD_NULL */ + return buf; + } + i += len; + } + + save_errno = errno; + free(buf); + + /* select returned 0 (timeout) or < 0 (error) */ + if (ret == 0) + { + *cmd = SMFIC_TIMEOUT; + return NULL; + } + if (ret < 0) + { + smi_log(SMI_LOG_ERR, + "%s: mi_rd_cmd: select returned %d: %s", + name, ret, sm_errstring(save_errno)); + *cmd = SMFIC_RECVERR; + return NULL; + } + *cmd = SMFIC_UNKNERR; + return NULL; +} + +/* +** RETRY_WRITEV -- Keep calling the writev() system call +** until all the data is written out or an error occurs. +** +** Parameters: +** fd -- socket descriptor +** iov -- io vector +** iovcnt -- number of elements in io vector +** must NOT exceed UIO_MAXIOV. +** timeout -- maximum time to wait +** +** Returns: +** success: number of bytes written +** otherwise: MI_FAILURE +*/ + +static ssize_t +retry_writev(fd, iov, iovcnt, timeout) + socket_t fd; + struct iovec *iov; + int iovcnt; + struct timeval *timeout; +{ + int i; + ssize_t n, written; + FD_WR_VAR(wrs); + + written = 0; + for (;;) + { + while (iovcnt > 0 && iov[0].iov_len == 0) + { + iov++; + iovcnt--; + } + if (iovcnt <= 0) + return written; + + /* + ** We don't care much about the timeout here, + ** it's very long anyway; correct solution would be + ** to take the time before the loop and reduce the + ** timeout after each invocation. + ** FD_SETSIZE is checked when socket is created. + */ + + FD_WR_INIT(fd, wrs); + i = FD_WR_READY(fd, wrs, timeout); + if (i == 0) + return MI_FAILURE; + if (i < 0) + { + if (errno == EINTR || errno == EAGAIN) + continue; + return MI_FAILURE; + } + n = writev(fd, iov, iovcnt); + if (n == -1) + { + if (errno == EINTR || errno == EAGAIN) + continue; + return MI_FAILURE; + } + + written += n; + for (i = 0; i < iovcnt; i++) + { + if (iov[i].iov_len > (unsigned int) n) + { + iov[i].iov_base = (char *)iov[i].iov_base + n; + iov[i].iov_len -= (unsigned int) n; + break; + } + n -= (int) iov[i].iov_len; + iov[i].iov_len = 0; + } + if (i == iovcnt) + return written; + } +} + +/* +** MI_WR_CMD -- write a cmd to sd +** +** Parameters: +** sd -- socket descriptor +** timeout -- maximum time to wait +** cmd -- single character command to write +** buf -- buffer with further data +** len -- length of buffer (without cmd!) +** +** Returns: +** MI_SUCCESS/MI_FAILURE +*/ + +int +mi_wr_cmd(sd, timeout, cmd, buf, len) + socket_t sd; + struct timeval *timeout; + int cmd; + char *buf; + size_t len; +{ + size_t sl, i; + ssize_t l; + mi_int32 nl; + int iovcnt; + struct iovec iov[2]; + char data[MILTER_LEN_BYTES + 1]; + + if (len > Maxdatasize || (len > 0 && buf == NULL)) + return MI_FAILURE; + + nl = htonl(len + 1); /* add 1 for the cmd char */ + (void) memcpy(data, (void *) &nl, MILTER_LEN_BYTES); + data[MILTER_LEN_BYTES] = (char) cmd; + i = 0; + sl = MILTER_LEN_BYTES + 1; + + /* set up the vector for the size / command */ + iov[0].iov_base = (void *) data; + iov[0].iov_len = sl; + iovcnt = 1; + if (len >= 0 && buf != NULL) + { + iov[1].iov_base = (void *) buf; + iov[1].iov_len = len; + iovcnt = 2; + } + + l = retry_writev(sd, iov, iovcnt, timeout); + if (l == MI_FAILURE) + return MI_FAILURE; + return MI_SUCCESS; +} diff --git a/usr/src/cmd/sendmail/libmilter/engine.c b/usr/src/cmd/sendmail/libmilter/engine.c new file mode 100644 index 0000000000..19f8218bc9 --- /dev/null +++ b/usr/src/cmd/sendmail/libmilter/engine.c @@ -0,0 +1,1252 @@ +/* + * Copyright (c) 1999-2003 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. + * + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sm/gen.h> +SM_RCSID("@(#)$Id: engine.c,v 8.120 2004/10/20 21:09:00 ca Exp $") + +#include "libmilter.h" + +#if NETINET || NETINET6 +# include <arpa/inet.h> +#endif /* NETINET || NETINET6 */ + +/* generic argument for functions in the command table */ +struct arg_struct +{ + size_t a_len; /* length of buffer */ + char *a_buf; /* argument string */ + int a_idx; /* index for macro array */ + SMFICTX_PTR a_ctx; /* context */ +}; + +typedef struct arg_struct genarg; + +/* structure for commands received from MTA */ +struct cmdfct_t +{ + char cm_cmd; /* command */ + int cm_argt; /* type of arguments expected */ + int cm_next; /* next state */ + int cm_todo; /* what to do next */ + int cm_macros; /* index for macros */ + int (*cm_fct) __P((genarg *)); /* function to execute */ +}; + +typedef struct cmdfct_t cmdfct; + +/* possible values for cm_argt */ +#define CM_ARG0 0 /* no args */ +#define CM_ARG1 1 /* one arg (string) */ +#define CM_ARG2 2 /* two args (strings) */ +#define CM_ARGA 4 /* one string and _SOCK_ADDR */ +#define CM_ARGO 5 /* two integers */ +#define CM_ARGV 8 /* \0 separated list of args, NULL-terminated */ +#define CM_ARGN 9 /* \0 separated list of args (strings) */ + +/* possible values for cm_todo */ +#define CT_CONT 0x0000 /* continue reading commands */ +#define CT_IGNO 0x0001 /* continue even when error */ + +/* not needed right now, done via return code instead */ +#define CT_KEEP 0x0004 /* keep buffer (contains symbols) */ +#define CT_END 0x0008 /* start replying */ + +/* index in macro array: macros only for these commands */ +#define CI_NONE (-1) +#define CI_CONN 0 +#define CI_HELO 1 +#define CI_MAIL 2 +#define CI_RCPT 3 +#define CI_EOM 4 +#if CI_EOM >= MAX_MACROS_ENTRIES +ERROR: do not compile with CI_EOM >= MAX_MACROS_ENTRIES +#endif + +/* function prototypes */ +static int st_abortfct __P((genarg *)); +static int st_macros __P((genarg *)); +static int st_optionneg __P((genarg *)); +static int st_bodychunk __P((genarg *)); +static int st_connectinfo __P((genarg *)); +static int st_bodyend __P((genarg *)); +static int st_helo __P((genarg *)); +static int st_header __P((genarg *)); +static int st_sender __P((genarg *)); +static int st_rcpt __P((genarg *)); +#if SMFI_VERSION > 2 +static int st_unknown __P((genarg *)); +#endif /* SMFI_VERSION > 2 */ +#if SMFI_VERSION > 3 +static int st_data __P((genarg *)); +#endif /* SMFI_VERSION > 3 */ +static int st_eoh __P((genarg *)); +static int st_quit __P((genarg *)); +static int sendreply __P((sfsistat, socket_t, struct timeval *, SMFICTX_PTR)); +static void fix_stm __P((SMFICTX_PTR)); +static bool trans_ok __P((int, int)); +static char **dec_argv __P((char *, size_t)); +static int dec_arg2 __P((char *, size_t, char **, char **)); + +/* states */ +#define ST_NONE (-1) +#define ST_INIT 0 /* initial state */ +#define ST_OPTS 1 /* option negotiation */ +#define ST_CONN 2 /* connection info */ +#define ST_HELO 3 /* helo */ +#define ST_MAIL 4 /* mail from */ +#define ST_RCPT 5 /* rcpt to */ +#define ST_DATA 6 /* data */ +#define ST_HDRS 7 /* headers */ +#define ST_EOHS 8 /* end of headers */ +#define ST_BODY 9 /* body */ +#define ST_ENDM 10 /* end of message */ +#define ST_QUIT 11 /* quit */ +#define ST_ABRT 12 /* abort */ +#define ST_UNKN 13 /* unknown SMTP command */ +#define ST_LAST ST_UNKN /* last valid state */ +#define ST_SKIP 15 /* not a state but required for the state table */ + +/* in a mail transaction? must be before eom according to spec. */ +#define ST_IN_MAIL(st) ((st) >= ST_MAIL && (st) < ST_ENDM) + +/* +** set of next states +** each state (ST_*) corresponds to bit in an int value (1 << state) +** each state has a set of allowed transitions ('or' of bits of states) +** so a state transition is valid if the mask of the next state +** is set in the NX_* value +** this function is coded in trans_ok(), see below. +*/ + +#define MI_MASK(x) (0x0001 << (x)) /* generate a bit "mask" for a state */ +#define NX_INIT (MI_MASK(ST_OPTS)) +#define NX_OPTS (MI_MASK(ST_CONN) | MI_MASK(ST_UNKN)) +#define NX_CONN (MI_MASK(ST_HELO) | MI_MASK(ST_MAIL) | MI_MASK(ST_UNKN)) +#define NX_HELO (MI_MASK(ST_HELO) | MI_MASK(ST_MAIL) | MI_MASK(ST_UNKN)) +#define NX_MAIL (MI_MASK(ST_RCPT) | MI_MASK(ST_ABRT) | MI_MASK(ST_UNKN)) +#define NX_RCPT (MI_MASK(ST_HDRS) | MI_MASK(ST_EOHS) | MI_MASK(ST_DATA) | \ + MI_MASK(ST_BODY) | MI_MASK(ST_ENDM) | \ + MI_MASK(ST_RCPT) | MI_MASK(ST_ABRT) | MI_MASK(ST_UNKN)) +#define NX_DATA (MI_MASK(ST_EOHS) | MI_MASK(ST_HDRS) | MI_MASK(ST_ABRT)) +#define NX_HDRS (MI_MASK(ST_EOHS) | MI_MASK(ST_HDRS) | MI_MASK(ST_ABRT)) +#define NX_EOHS (MI_MASK(ST_BODY) | MI_MASK(ST_ENDM) | MI_MASK(ST_ABRT)) +#define NX_BODY (MI_MASK(ST_ENDM) | MI_MASK(ST_BODY) | MI_MASK(ST_ABRT)) +#define NX_ENDM (MI_MASK(ST_QUIT) | MI_MASK(ST_MAIL) | MI_MASK(ST_UNKN)) +#define NX_QUIT 0 +#define NX_ABRT 0 +#define NX_UNKN (MI_MASK(ST_HELO) | MI_MASK(ST_MAIL) | \ + MI_MASK(ST_RCPT) | MI_MASK(ST_ABRT) | \ + MI_MASK(ST_DATA) | \ + MI_MASK(ST_BODY) | MI_MASK(ST_UNKN) | \ + MI_MASK(ST_ABRT) | MI_MASK(ST_QUIT)) +#define NX_SKIP MI_MASK(ST_SKIP) + +static int next_states[] = +{ + NX_INIT, + NX_OPTS, + NX_CONN, + NX_HELO, + NX_MAIL, + NX_RCPT, + NX_DATA, + NX_HDRS, + NX_EOHS, + NX_BODY, + NX_ENDM, + NX_QUIT, + NX_ABRT, + NX_UNKN +}; + +/* commands received by milter */ +static cmdfct cmds[] = +{ +{SMFIC_ABORT, CM_ARG0, ST_ABRT, CT_CONT, CI_NONE, st_abortfct }, +{SMFIC_MACRO, CM_ARGV, ST_NONE, CT_KEEP, CI_NONE, st_macros }, +{SMFIC_BODY, CM_ARG1, ST_BODY, CT_CONT, CI_NONE, st_bodychunk }, +{SMFIC_CONNECT, CM_ARG2, ST_CONN, CT_CONT, CI_CONN, st_connectinfo }, +{SMFIC_BODYEOB, CM_ARG1, ST_ENDM, CT_CONT, CI_EOM, st_bodyend }, +{SMFIC_HELO, CM_ARG1, ST_HELO, CT_CONT, CI_HELO, st_helo }, +{SMFIC_HEADER, CM_ARG2, ST_HDRS, CT_CONT, CI_NONE, st_header }, +{SMFIC_MAIL, CM_ARGV, ST_MAIL, CT_CONT, CI_MAIL, st_sender }, +{SMFIC_OPTNEG, CM_ARGO, ST_OPTS, CT_CONT, CI_NONE, st_optionneg }, +{SMFIC_EOH, CM_ARG0, ST_EOHS, CT_CONT, CI_NONE, st_eoh }, +{SMFIC_QUIT, CM_ARG0, ST_QUIT, CT_END, CI_NONE, st_quit }, +#if SMFI_VERSION > 3 +{SMFIC_DATA, CM_ARG0, ST_DATA, CT_CONT, CI_NONE, st_data }, +#endif /* SMFI_VERSION > 3 */ +{SMFIC_RCPT, CM_ARGV, ST_RCPT, CT_IGNO, CI_RCPT, st_rcpt } +#if SMFI_VERSION > 2 +,{SMFIC_UNKNOWN,CM_ARG1, ST_UNKN, CT_IGNO, CI_NONE, st_unknown } +#endif /* SMFI_VERSION > 2 */ +}; + +/* additional (internal) reply codes */ +#define _SMFIS_KEEP 20 +#define _SMFIS_ABORT 21 +#define _SMFIS_OPTIONS 22 +#define _SMFIS_NOREPLY 23 +#define _SMFIS_FAIL (-1) +#define _SMFIS_NONE (-2) + +/* +** MI_ENGINE -- receive commands and process them +** +** Parameters: +** ctx -- context structure +** +** Returns: +** MI_FAILURE/MI_SUCCESS +*/ +int +mi_engine(ctx) + SMFICTX_PTR ctx; +{ + size_t len; + int i; + socket_t sd; + int ret = MI_SUCCESS; + int ncmds = sizeof(cmds) / sizeof(cmdfct); + int curstate = ST_INIT; + int newstate; + bool call_abort; + sfsistat r; + char cmd; + char *buf = NULL; + genarg arg; + struct timeval timeout; + int (*f) __P((genarg *)); + sfsistat (*fi_abort) __P((SMFICTX *)); + sfsistat (*fi_close) __P((SMFICTX *)); + + arg.a_ctx = ctx; + sd = ctx->ctx_sd; + fi_abort = ctx->ctx_smfi->xxfi_abort; + mi_clr_macros(ctx, 0); + fix_stm(ctx); + r = _SMFIS_NONE; + do + { + /* call abort only if in a mail transaction */ + call_abort = ST_IN_MAIL(curstate); + timeout.tv_sec = ctx->ctx_timeout; + timeout.tv_usec = 0; + if (mi_stop() == MILTER_ABRT) + { + if (ctx->ctx_dbg > 3) + sm_dprintf("[%d] milter_abort\n", + (int) ctx->ctx_id); + ret = MI_FAILURE; + break; + } + + /* + ** Notice: buf is allocated by mi_rd_cmd() and it will + ** usually be free()d after it has been used in f(). + ** However, if the function returns _SMFIS_KEEP then buf + ** contains macros and will not be free()d. + ** Hence r must be set to _SMFIS_NONE if a new buf is + ** allocated to avoid problem with housekeeping, esp. + ** if the code "break"s out of the loop. + */ + + r = _SMFIS_NONE; + if ((buf = mi_rd_cmd(sd, &timeout, &cmd, &len, + ctx->ctx_smfi->xxfi_name)) == NULL && + cmd < SMFIC_VALIDCMD) + { + if (ctx->ctx_dbg > 5) + sm_dprintf("[%d] mi_engine: mi_rd_cmd error (%x)\n", + (int) ctx->ctx_id, (int) cmd); + + /* + ** eof is currently treated as failure -> + ** abort() instead of close(), otherwise use: + ** if (cmd != SMFIC_EOF) + */ + + ret = MI_FAILURE; + break; + } + if (ctx->ctx_dbg > 4) + sm_dprintf("[%d] got cmd '%c' len %d\n", + (int) ctx->ctx_id, cmd, (int) len); + for (i = 0; i < ncmds; i++) + { + if (cmd == cmds[i].cm_cmd) + break; + } + if (i >= ncmds) + { + /* unknown command */ + if (ctx->ctx_dbg > 1) + sm_dprintf("[%d] cmd '%c' unknown\n", + (int) ctx->ctx_id, cmd); + ret = MI_FAILURE; + break; + } + if ((f = cmds[i].cm_fct) == NULL) + { + /* stop for now */ + if (ctx->ctx_dbg > 1) + sm_dprintf("[%d] cmd '%c' not impl\n", + (int) ctx->ctx_id, cmd); + ret = MI_FAILURE; + break; + } + + /* is new state ok? */ + newstate = cmds[i].cm_next; + if (ctx->ctx_dbg > 5) + sm_dprintf("[%d] cur %x new %x nextmask %x\n", + (int) ctx->ctx_id, + curstate, newstate, next_states[curstate]); + + if (newstate != ST_NONE && !trans_ok(curstate, newstate)) + { + if (ctx->ctx_dbg > 1) + sm_dprintf("[%d] abort: cur %d (%x) new %d (%x) next %x\n", + (int) ctx->ctx_id, + curstate, MI_MASK(curstate), + newstate, MI_MASK(newstate), + next_states[curstate]); + + /* call abort only if in a mail transaction */ + if (fi_abort != NULL && call_abort) + (void) (*fi_abort)(ctx); + + /* + ** try to reach the new state from HELO + ** if it can't be reached, ignore the command. + */ + + curstate = ST_HELO; + if (!trans_ok(curstate, newstate)) + { + if (buf != NULL) + { + free(buf); + buf = NULL; + } + continue; + } + } + arg.a_len = len; + arg.a_buf = buf; + if (newstate != ST_NONE) + { + curstate = newstate; + ctx->ctx_state = curstate; + } + arg.a_idx = cmds[i].cm_macros; + call_abort = ST_IN_MAIL(curstate); + + /* call function to deal with command */ + r = (*f)(&arg); + if (r != _SMFIS_KEEP && buf != NULL) + { + free(buf); + buf = NULL; + } + if (sendreply(r, sd, &timeout, ctx) != MI_SUCCESS) + { + ret = MI_FAILURE; + break; + } + + if (r == SMFIS_ACCEPT) + { + /* accept mail, no further actions taken */ + curstate = ST_HELO; + } + else if (r == SMFIS_REJECT || r == SMFIS_DISCARD || + r == SMFIS_TEMPFAIL) + { + /* + ** further actions depend on current state + ** if the IGNO bit is set: "ignore" the error, + ** i.e., stay in the current state + */ + if (!bitset(CT_IGNO, cmds[i].cm_todo)) + curstate = ST_HELO; + } + else if (r == _SMFIS_ABORT) + { + if (ctx->ctx_dbg > 5) + sm_dprintf("[%d] function returned abort\n", + (int) ctx->ctx_id); + ret = MI_FAILURE; + break; + } + } while (!bitset(CT_END, cmds[i].cm_todo)); + + if (ret != MI_SUCCESS) + { + /* call abort only if in a mail transaction */ + if (fi_abort != NULL && call_abort) + (void) (*fi_abort)(ctx); + } + + /* close must always be called */ + if ((fi_close = ctx->ctx_smfi->xxfi_close) != NULL) + (void) (*fi_close)(ctx); + if (r != _SMFIS_KEEP && buf != NULL) + free(buf); + mi_clr_macros(ctx, 0); + return ret; +} +/* +** SENDREPLY -- send a reply to the MTA +** +** Parameters: +** r -- reply code +** sd -- socket descriptor +** timeout_ptr -- (ptr to) timeout to use for sending +** ctx -- context structure +** +** Returns: +** MI_SUCCESS/MI_FAILURE +*/ + +static int +sendreply(r, sd, timeout_ptr, ctx) + sfsistat r; + socket_t sd; + struct timeval *timeout_ptr; + SMFICTX_PTR ctx; +{ + int ret = MI_SUCCESS; + + switch (r) + { + case SMFIS_CONTINUE: + ret = mi_wr_cmd(sd, timeout_ptr, SMFIR_CONTINUE, NULL, 0); + break; + case SMFIS_TEMPFAIL: + case SMFIS_REJECT: + if (ctx->ctx_reply != NULL && + ((r == SMFIS_TEMPFAIL && *ctx->ctx_reply == '4') || + (r == SMFIS_REJECT && *ctx->ctx_reply == '5'))) + { + ret = mi_wr_cmd(sd, timeout_ptr, SMFIR_REPLYCODE, + ctx->ctx_reply, + strlen(ctx->ctx_reply) + 1); + free(ctx->ctx_reply); + ctx->ctx_reply = NULL; + } + else + { + ret = mi_wr_cmd(sd, timeout_ptr, r == SMFIS_REJECT ? + SMFIR_REJECT : SMFIR_TEMPFAIL, NULL, 0); + } + break; + case SMFIS_DISCARD: + ret = mi_wr_cmd(sd, timeout_ptr, SMFIR_DISCARD, NULL, 0); + break; + case SMFIS_ACCEPT: + ret = mi_wr_cmd(sd, timeout_ptr, SMFIR_ACCEPT, NULL, 0); + break; + case _SMFIS_OPTIONS: + { + char buf[MILTER_OPTLEN]; + mi_int32 v; + + v = htonl(ctx->ctx_smfi->xxfi_version); + (void) memcpy(&(buf[0]), (void *) &v, MILTER_LEN_BYTES); + v = htonl(ctx->ctx_smfi->xxfi_flags); + (void) memcpy(&(buf[MILTER_LEN_BYTES]), (void *) &v, + MILTER_LEN_BYTES); + v = htonl(ctx->ctx_pflags); + (void) memcpy(&(buf[MILTER_LEN_BYTES * 2]), (void *) &v, + MILTER_LEN_BYTES); + ret = mi_wr_cmd(sd, timeout_ptr, SMFIC_OPTNEG, buf, + MILTER_OPTLEN); + } + break; + default: /* don't send a reply */ + break; + } + return ret; +} + +/* +** CLR_MACROS -- clear set of macros starting from a given index +** +** Parameters: +** ctx -- context structure +** m -- index from which to clear all macros +** +** Returns: +** None. +*/ +void +mi_clr_macros(ctx, m) + SMFICTX_PTR ctx; + int m; +{ + int i; + + for (i = m; i < MAX_MACROS_ENTRIES; i++) + { + if (ctx->ctx_mac_ptr[i] != NULL) + { + free(ctx->ctx_mac_ptr[i]); + ctx->ctx_mac_ptr[i] = NULL; + } + if (ctx->ctx_mac_buf[i] != NULL) + { + free(ctx->ctx_mac_buf[i]); + ctx->ctx_mac_buf[i] = NULL; + } + } +} +/* +** ST_OPTIONNEG -- negotiate options +** +** Parameters: +** g -- generic argument structure +** +** Returns: +** abort/send options/continue +*/ + +static int +st_optionneg(g) + genarg *g; +{ + mi_int32 i, v; + + if (g == NULL || g->a_ctx->ctx_smfi == NULL) + return SMFIS_CONTINUE; + mi_clr_macros(g->a_ctx, g->a_idx + 1); + + /* check for minimum length */ + if (g->a_len < MILTER_OPTLEN) + { + smi_log(SMI_LOG_ERR, + "%s: st_optionneg[%d]: len too short %d < %d", + g->a_ctx->ctx_smfi->xxfi_name, + (int) g->a_ctx->ctx_id, (int) g->a_len, + MILTER_OPTLEN); + return _SMFIS_ABORT; + } + + (void) memcpy((void *) &i, (void *) &(g->a_buf[0]), + MILTER_LEN_BYTES); + v = ntohl(i); + if (v < g->a_ctx->ctx_smfi->xxfi_version) + { + /* hard failure for now! */ + smi_log(SMI_LOG_ERR, + "%s: st_optionneg[%d]: version mismatch MTA: %d < milter: %d", + g->a_ctx->ctx_smfi->xxfi_name, + (int) g->a_ctx->ctx_id, (int) v, + g->a_ctx->ctx_smfi->xxfi_version); + return _SMFIS_ABORT; + } + + (void) memcpy((void *) &i, (void *) &(g->a_buf[MILTER_LEN_BYTES]), + MILTER_LEN_BYTES); + v = ntohl(i); + + /* no flags? set to default value for V1 actions */ + if (v == 0) + v = SMFI_V1_ACTS; + i = g->a_ctx->ctx_smfi->xxfi_flags; + if ((v & i) != i) + { + smi_log(SMI_LOG_ERR, + "%s: st_optionneg[%d]: 0x%x does not fulfill action requirements 0x%x", + g->a_ctx->ctx_smfi->xxfi_name, + (int) g->a_ctx->ctx_id, v, i); + return _SMFIS_ABORT; + } + + (void) memcpy((void *) &i, (void *) &(g->a_buf[MILTER_LEN_BYTES * 2]), + MILTER_LEN_BYTES); + v = ntohl(i); + + /* no flags? set to default value for V1 protocol */ + if (v == 0) + v = SMFI_V1_PROT; + i = g->a_ctx->ctx_pflags; + if ((v & i) != i) + { + smi_log(SMI_LOG_ERR, + "%s: st_optionneg[%d]: 0x%x does not fulfill protocol requirements 0x%x", + g->a_ctx->ctx_smfi->xxfi_name, + (int) g->a_ctx->ctx_id, v, i); + return _SMFIS_ABORT; + } + + return _SMFIS_OPTIONS; +} +/* +** ST_CONNECTINFO -- receive connection information +** +** Parameters: +** g -- generic argument structure +** +** Returns: +** continue or filter-specified value +*/ + +static int +st_connectinfo(g) + genarg *g; +{ + size_t l; + size_t i; + char *s, family; + unsigned short port = 0; + _SOCK_ADDR sockaddr; + sfsistat (*fi_connect) __P((SMFICTX *, char *, _SOCK_ADDR *)); + + if (g == NULL) + return _SMFIS_ABORT; + mi_clr_macros(g->a_ctx, g->a_idx + 1); + if (g->a_ctx->ctx_smfi == NULL || + (fi_connect = g->a_ctx->ctx_smfi->xxfi_connect) == NULL) + return SMFIS_CONTINUE; + + s = g->a_buf; + i = 0; + l = g->a_len; + while (s[i] != '\0' && i <= l) + ++i; + if (i + 1 >= l) + return _SMFIS_ABORT; + + /* Move past trailing \0 in host string */ + i++; + family = s[i++]; + (void) memset(&sockaddr, '\0', sizeof sockaddr); + if (family != SMFIA_UNKNOWN) + { + if (i + sizeof port >= l) + { + smi_log(SMI_LOG_ERR, + "%s: connect[%d]: wrong len %d >= %d", + g->a_ctx->ctx_smfi->xxfi_name, + (int) g->a_ctx->ctx_id, (int) i, (int) l); + return _SMFIS_ABORT; + } + (void) memcpy((void *) &port, (void *) (s + i), + sizeof port); + i += sizeof port; + + /* make sure string is terminated */ + if (s[l - 1] != '\0') + return _SMFIS_ABORT; +# if NETINET + if (family == SMFIA_INET) + { + if (inet_aton(s + i, (struct in_addr *) &sockaddr.sin.sin_addr) + != 1) + { + smi_log(SMI_LOG_ERR, + "%s: connect[%d]: inet_aton failed", + g->a_ctx->ctx_smfi->xxfi_name, + (int) g->a_ctx->ctx_id); + return _SMFIS_ABORT; + } + sockaddr.sa.sa_family = AF_INET; + if (port > 0) + sockaddr.sin.sin_port = port; + } + else +# endif /* NETINET */ +# if NETINET6 + if (family == SMFIA_INET6) + { + if (mi_inet_pton(AF_INET6, s + i, + &sockaddr.sin6.sin6_addr) != 1) + { + smi_log(SMI_LOG_ERR, + "%s: connect[%d]: mi_inet_pton failed", + g->a_ctx->ctx_smfi->xxfi_name, + (int) g->a_ctx->ctx_id); + return _SMFIS_ABORT; + } + sockaddr.sa.sa_family = AF_INET6; + if (port > 0) + sockaddr.sin6.sin6_port = port; + } + else +# endif /* NETINET6 */ +# if NETUNIX + if (family == SMFIA_UNIX) + { + if (sm_strlcpy(sockaddr.sunix.sun_path, s + i, + sizeof sockaddr.sunix.sun_path) >= + sizeof sockaddr.sunix.sun_path) + { + smi_log(SMI_LOG_ERR, + "%s: connect[%d]: path too long", + g->a_ctx->ctx_smfi->xxfi_name, + (int) g->a_ctx->ctx_id); + return _SMFIS_ABORT; + } + sockaddr.sunix.sun_family = AF_UNIX; + } + else +# endif /* NETUNIX */ + { + smi_log(SMI_LOG_ERR, + "%s: connect[%d]: unknown family %d", + g->a_ctx->ctx_smfi->xxfi_name, + (int) g->a_ctx->ctx_id, family); + return _SMFIS_ABORT; + } + } + return (*fi_connect)(g->a_ctx, g->a_buf, + family != SMFIA_UNKNOWN ? &sockaddr : NULL); +} + +/* +** ST_EOH -- end of headers +** +** Parameters: +** g -- generic argument structure +** +** Returns: +** continue or filter-specified value +*/ + +static int +st_eoh(g) + genarg *g; +{ + sfsistat (*fi_eoh) __P((SMFICTX *)); + + if (g == NULL) + return _SMFIS_ABORT; + if (g->a_ctx->ctx_smfi != NULL && + (fi_eoh = g->a_ctx->ctx_smfi->xxfi_eoh) != NULL) + return (*fi_eoh)(g->a_ctx); + return SMFIS_CONTINUE; +} + +#if SMFI_VERSION > 3 +/* +** ST_DATA -- DATA command +** +** Parameters: +** g -- generic argument structure +** +** Returns: +** continue or filter-specified value +*/ + +static int +st_data(g) + genarg *g; +{ + sfsistat (*fi_data) __P((SMFICTX *)); + + if (g == NULL) + return _SMFIS_ABORT; + if (g->a_ctx->ctx_smfi != NULL && + (fi_data = g->a_ctx->ctx_smfi->xxfi_data) != NULL) + return (*fi_data)(g->a_ctx); + return SMFIS_CONTINUE; +} +#endif /* SMFI_VERSION > 3 */ + +/* +** ST_HELO -- helo/ehlo command +** +** Parameters: +** g -- generic argument structure +** +** Returns: +** continue or filter-specified value +*/ +static int +st_helo(g) + genarg *g; +{ + sfsistat (*fi_helo) __P((SMFICTX *, char *)); + + if (g == NULL) + return _SMFIS_ABORT; + mi_clr_macros(g->a_ctx, g->a_idx + 1); + if (g->a_ctx->ctx_smfi != NULL && + (fi_helo = g->a_ctx->ctx_smfi->xxfi_helo) != NULL) + { + /* paranoia: check for terminating '\0' */ + if (g->a_len == 0 || g->a_buf[g->a_len - 1] != '\0') + return MI_FAILURE; + return (*fi_helo)(g->a_ctx, g->a_buf); + } + return SMFIS_CONTINUE; +} +/* +** ST_HEADER -- header line +** +** Parameters: +** g -- generic argument structure +** +** Returns: +** continue or filter-specified value +*/ + +static int +st_header(g) + genarg *g; +{ + char *hf, *hv; + sfsistat (*fi_header) __P((SMFICTX *, char *, char *)); + + if (g == NULL) + return _SMFIS_ABORT; + if (g->a_ctx->ctx_smfi == NULL || + (fi_header = g->a_ctx->ctx_smfi->xxfi_header) == NULL) + return SMFIS_CONTINUE; + if (dec_arg2(g->a_buf, g->a_len, &hf, &hv) == MI_SUCCESS) + return (*fi_header)(g->a_ctx, hf, hv); + else + return _SMFIS_ABORT; +} + +#define ARGV_FCT(lf, rf, idx) \ + char **argv; \ + sfsistat (*lf) __P((SMFICTX *, char **)); \ + int r; \ + \ + if (g == NULL) \ + return _SMFIS_ABORT; \ + mi_clr_macros(g->a_ctx, g->a_idx + 1); \ + if (g->a_ctx->ctx_smfi == NULL || \ + (lf = g->a_ctx->ctx_smfi->rf) == NULL) \ + return SMFIS_CONTINUE; \ + if ((argv = dec_argv(g->a_buf, g->a_len)) == NULL) \ + return _SMFIS_ABORT; \ + r = (*lf)(g->a_ctx, argv); \ + free(argv); \ + return r; + +/* +** ST_SENDER -- MAIL FROM command +** +** Parameters: +** g -- generic argument structure +** +** Returns: +** continue or filter-specified value +*/ + +static int +st_sender(g) + genarg *g; +{ + ARGV_FCT(fi_envfrom, xxfi_envfrom, CI_MAIL) +} +/* +** ST_RCPT -- RCPT TO command +** +** Parameters: +** g -- generic argument structure +** +** Returns: +** continue or filter-specified value +*/ + +static int +st_rcpt(g) + genarg *g; +{ + ARGV_FCT(fi_envrcpt, xxfi_envrcpt, CI_RCPT) +} + +#if SMFI_VERSION > 2 +/* +** ST_UNKNOWN -- unrecognized or unimplemented command +** +** Parameters: +** g -- generic argument structure +** +** Returns: +** continue or filter-specified value +*/ + +static int +st_unknown(g) + genarg *g; +{ + sfsistat (*fi_unknown) __P((SMFICTX *, char *)); + + if (g == NULL) + return _SMFIS_ABORT; + mi_clr_macros(g->a_ctx, g->a_idx + 1); + if (g->a_ctx->ctx_smfi != NULL && + (fi_unknown = g->a_ctx->ctx_smfi->xxfi_unknown) != NULL) + return (*fi_unknown)(g->a_ctx, g->a_buf); + return SMFIS_CONTINUE; +} +#endif /* SMFI_VERSION > 2 */ + +/* +** ST_MACROS -- deal with macros received from the MTA +** +** Parameters: +** g -- generic argument structure +** +** Returns: +** continue/keep +** +** Side effects: +** set pointer in macro array to current values. +*/ + +static int +st_macros(g) + genarg *g; +{ + int i; + char **argv; + + if (g == NULL || g->a_len < 1) + return _SMFIS_FAIL; + if ((argv = dec_argv(g->a_buf + 1, g->a_len - 1)) == NULL) + return _SMFIS_FAIL; + switch (g->a_buf[0]) + { + case SMFIC_CONNECT: + i = CI_CONN; + break; + case SMFIC_HELO: + i = CI_HELO; + break; + case SMFIC_MAIL: + i = CI_MAIL; + break; + case SMFIC_RCPT: + i = CI_RCPT; + break; + case SMFIC_BODYEOB: + i = CI_EOM; + break; + default: + free(argv); + return _SMFIS_FAIL; + } + if (g->a_ctx->ctx_mac_ptr[i] != NULL) + free(g->a_ctx->ctx_mac_ptr[i]); + if (g->a_ctx->ctx_mac_buf[i] != NULL) + free(g->a_ctx->ctx_mac_buf[i]); + g->a_ctx->ctx_mac_ptr[i] = argv; + g->a_ctx->ctx_mac_buf[i] = g->a_buf; + return _SMFIS_KEEP; +} +/* +** ST_QUIT -- quit command +** +** Parameters: +** g -- generic argument structure +** +** Returns: +** noreply +*/ + +/* ARGSUSED */ +static int +st_quit(g) + genarg *g; +{ + return _SMFIS_NOREPLY; +} +/* +** ST_BODYCHUNK -- deal with a piece of the mail body +** +** Parameters: +** g -- generic argument structure +** +** Returns: +** continue or filter-specified value +*/ + +static int +st_bodychunk(g) + genarg *g; +{ + sfsistat (*fi_body) __P((SMFICTX *, unsigned char *, size_t)); + + if (g == NULL) + return _SMFIS_ABORT; + if (g->a_ctx->ctx_smfi != NULL && + (fi_body = g->a_ctx->ctx_smfi->xxfi_body) != NULL) + return (*fi_body)(g->a_ctx, (unsigned char *)g->a_buf, + g->a_len); + return SMFIS_CONTINUE; +} +/* +** ST_BODYEND -- deal with the last piece of the mail body +** +** Parameters: +** g -- generic argument structure +** +** Returns: +** continue or filter-specified value +** +** Side effects: +** sends a reply for the body part (if non-empty). +*/ + +static int +st_bodyend(g) + genarg *g; +{ + sfsistat r; + sfsistat (*fi_body) __P((SMFICTX *, unsigned char *, size_t)); + sfsistat (*fi_eom) __P((SMFICTX *)); + + if (g == NULL) + return _SMFIS_ABORT; + r = SMFIS_CONTINUE; + if (g->a_ctx->ctx_smfi != NULL) + { + if ((fi_body = g->a_ctx->ctx_smfi->xxfi_body) != NULL && + g->a_len > 0) + { + socket_t sd; + struct timeval timeout; + + timeout.tv_sec = g->a_ctx->ctx_timeout; + timeout.tv_usec = 0; + sd = g->a_ctx->ctx_sd; + r = (*fi_body)(g->a_ctx, (unsigned char *)g->a_buf, + g->a_len); + if (r != SMFIS_CONTINUE && + sendreply(r, sd, &timeout, g->a_ctx) != MI_SUCCESS) + return _SMFIS_ABORT; + } + } + if (r == SMFIS_CONTINUE && + (fi_eom = g->a_ctx->ctx_smfi->xxfi_eom) != NULL) + return (*fi_eom)(g->a_ctx); + return r; +} +/* +** ST_ABORTFCT -- deal with aborts +** +** Parameters: +** g -- generic argument structure +** +** Returns: +** abort or filter-specified value +*/ + +static int +st_abortfct(g) + genarg *g; +{ + sfsistat (*fi_abort) __P((SMFICTX *)); + + if (g == NULL) + return _SMFIS_ABORT; + if (g != NULL && g->a_ctx->ctx_smfi != NULL && + (fi_abort = g->a_ctx->ctx_smfi->xxfi_abort) != NULL) + (void) (*fi_abort)(g->a_ctx); + return _SMFIS_NOREPLY; +} +/* +** TRANS_OK -- is the state transition ok? +** +** Parameters: +** old -- old state +** new -- new state +** +** Returns: +** state transition ok +*/ + +static bool +trans_ok(old, new) + int old, new; +{ + int s, n; + + s = old; + do + { + /* is this state transition allowed? */ + if ((MI_MASK(new) & next_states[s]) != 0) + return true; + + /* + ** no: try next state; + ** this works since the relevant states are ordered + ** strict sequentially + */ + + n = s + 1; + + /* + ** can we actually "skip" this state? + ** see fix_stm() which sets this bit for those + ** states which the filter program is not interested in + */ + + if (bitset(NX_SKIP, next_states[n])) + s = n; + else + return false; + } while (s <= ST_LAST); + return false; +} +/* +** FIX_STM -- add "skip" bits to the state transition table +** +** Parameters: +** ctx -- context structure +** +** Returns: +** None. +** +** Side effects: +** may change state transition table. +*/ + +static void +fix_stm(ctx) + SMFICTX_PTR ctx; +{ + unsigned long fl; + + if (ctx == NULL || ctx->ctx_smfi == NULL) + return; + fl = ctx->ctx_pflags; + if (bitset(SMFIP_NOCONNECT, fl)) + next_states[ST_CONN] |= NX_SKIP; + if (bitset(SMFIP_NOHELO, fl)) + next_states[ST_HELO] |= NX_SKIP; + if (bitset(SMFIP_NOMAIL, fl)) + next_states[ST_MAIL] |= NX_SKIP; + if (bitset(SMFIP_NORCPT, fl)) + next_states[ST_RCPT] |= NX_SKIP; + if (bitset(SMFIP_NOHDRS, fl)) + next_states[ST_HDRS] |= NX_SKIP; + if (bitset(SMFIP_NOEOH, fl)) + next_states[ST_EOHS] |= NX_SKIP; + if (bitset(SMFIP_NOBODY, fl)) + next_states[ST_BODY] |= NX_SKIP; +} +/* +** DEC_ARGV -- split a buffer into a list of strings, NULL terminated +** +** Parameters: +** buf -- buffer with several strings +** len -- length of buffer +** +** Returns: +** array of pointers to the individual strings +*/ + +static char ** +dec_argv(buf, len) + char *buf; + size_t len; +{ + char **s; + size_t i; + int elem, nelem; + + nelem = 0; + for (i = 0; i < len; i++) + { + if (buf[i] == '\0') + ++nelem; + } + if (nelem == 0) + return NULL; + + /* last entry is only for the name */ + s = (char **)malloc((nelem + 1) * (sizeof *s)); + if (s == NULL) + return NULL; + s[0] = buf; + for (i = 0, elem = 0; i < len && elem < nelem; i++) + { + if (buf[i] == '\0') + { + ++elem; + if (i + 1 >= len) + s[elem] = NULL; + else + s[elem] = &(buf[i + 1]); + } + } + + /* overwrite last entry (already done above, just paranoia) */ + s[elem] = NULL; + return s; +} +/* +** DEC_ARG2 -- split a buffer into two strings +** +** Parameters: +** buf -- buffer with two strings +** len -- length of buffer +** s1,s2 -- pointer to result strings +** +** Returns: +** MI_FAILURE/MI_SUCCESS +*/ + +static int +dec_arg2(buf, len, s1, s2) + char *buf; + size_t len; + char **s1; + char **s2; +{ + size_t i; + + /* paranoia: check for terminating '\0' */ + if (len == 0 || buf[len - 1] != '\0') + return MI_FAILURE; + *s1 = buf; + for (i = 1; i < len && buf[i] != '\0'; i++) + continue; + if (i >= len - 1) + return MI_FAILURE; + *s2 = buf + i + 1; + return MI_SUCCESS; +} +/* +** SENDOK -- is it ok for the filter to send stuff to the MTA? +** +** Parameters: +** ctx -- context structure +** flag -- flag to check +** +** Returns: +** sending allowed (in current state) +*/ + +bool +mi_sendok(ctx, flag) + SMFICTX_PTR ctx; + int flag; +{ + if (ctx == NULL || ctx->ctx_smfi == NULL) + return false; + + /* did the milter request this operation? */ + if (flag != 0 && !bitset(flag, ctx->ctx_smfi->xxfi_flags)) + return false; + + /* are we in the correct state? It must be "End of Message". */ + return ctx->ctx_state == ST_ENDM; +} diff --git a/usr/src/cmd/sendmail/libmilter/handler.c b/usr/src/cmd/sendmail/libmilter/handler.c new file mode 100644 index 0000000000..4823b378b2 --- /dev/null +++ b/usr/src/cmd/sendmail/libmilter/handler.c @@ -0,0 +1,68 @@ +/* + * Copyright (c) 1999-2003 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. + * + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sm/gen.h> +SM_RCSID("@(#)$Id: handler.c,v 8.30.2.4 2003/01/23 22:28:36 ca Exp $") + +#include "libmilter.h" + + +/* +** HANDLE_SESSION -- Handle a connected session in its own context +** +** Parameters: +** ctx -- context structure +** +** Returns: +** MI_SUCCESS/MI_FAILURE +*/ + +int +mi_handle_session(ctx) + SMFICTX_PTR ctx; +{ + int ret; + + if (ctx == NULL) + return MI_FAILURE; + ctx->ctx_id = (sthread_t) sthread_get_id(); + + /* + ** Detach so resources are free when the thread returns. + ** If we ever "wait" for threads, this call must be removed. + */ + + if (pthread_detach(ctx->ctx_id) != 0) + ret = MI_FAILURE; + else + ret = mi_engine(ctx); + if (ValidSocket(ctx->ctx_sd)) + { + (void) closesocket(ctx->ctx_sd); + ctx->ctx_sd = INVALID_SOCKET; + } + if (ctx->ctx_reply != NULL) + { + free(ctx->ctx_reply); + ctx->ctx_reply = NULL; + } + if (ctx->ctx_privdata != NULL) + { + smi_log(SMI_LOG_WARN, + "%s: private data not NULL", + ctx->ctx_smfi->xxfi_name); + } + mi_clr_macros(ctx, 0); + free(ctx); + ctx = NULL; + return ret; +} diff --git a/usr/src/cmd/sendmail/libmilter/i386/Makefile b/usr/src/cmd/sendmail/libmilter/i386/Makefile new file mode 100644 index 0000000000..e86ae6a9f7 --- /dev/null +++ b/usr/src/cmd/sendmail/libmilter/i386/Makefile @@ -0,0 +1,29 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License, Version 1.0 only +# (the "License"). You may not use this file except in compliance +# with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# Copyright 2004 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" + +include ../Makefile.com + +install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT) diff --git a/usr/src/cmd/sendmail/libmilter/libmilter.h b/usr/src/cmd/sendmail/libmilter/libmilter.h new file mode 100644 index 0000000000..da8a72ba5d --- /dev/null +++ b/usr/src/cmd/sendmail/libmilter/libmilter.h @@ -0,0 +1,189 @@ +/* + * Copyright (c) 1999-2003 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. + */ + +/* +** LIBMILTER.H -- include file for mail filter library functions +*/ + +#ifndef _LIBMILTER_H +# define _LIBMILTER_H 1 + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sm/gen.h> + +#ifdef _DEFINE +# define EXTERN +# define INIT(x) = x +SM_IDSTR(MilterlId, "@(#)$Id: libmilter.h,v 8.50 2003/12/11 18:14:34 ca Exp $") +#else /* _DEFINE */ +# define EXTERN extern +# define INIT(x) +#endif /* _DEFINE */ + + +#define NOT_SENDMAIL 1 +#define _SOCK_ADDR union bigsockaddr +#include "sendmail.h" + +#include "libmilter/milter.h" + +# define ValidSocket(sd) ((sd) >= 0) +# define INVALID_SOCKET (-1) +# define closesocket close +# define MI_SOCK_READ(s, b, l) read(s, b, l) +# define MI_SOCK_READ_FAIL(x) ((x) < 0) +# define MI_SOCK_WRITE(s, b, l) write(s, b, l) + +# define thread_create(ptid,wr,arg) pthread_create(ptid, NULL, wr, arg) +# define sthread_get_id() pthread_self() + +typedef pthread_mutex_t smutex_t; +# define smutex_init(mp) (pthread_mutex_init(mp, NULL) == 0) +# define smutex_destroy(mp) (pthread_mutex_destroy(mp) == 0) +# define smutex_lock(mp) (pthread_mutex_lock(mp) == 0) +# define smutex_unlock(mp) (pthread_mutex_unlock(mp) == 0) +# define smutex_trylock(mp) (pthread_mutex_trylock(mp) == 0) + +#if SM_CONF_POLL + +# include <poll.h> +# define MI_POLLSELECT "poll" + +# define MI_POLL_RD_FLAGS (POLLIN | POLLPRI) +# define MI_POLL_WR_FLAGS (POLLOUT) +# define MI_MS(timeout) (((timeout)->tv_sec * 1000) + (timeout)->tv_usec) + +# define FD_RD_VAR(rds, excs) struct pollfd rds +# define FD_WR_VAR(wrs) struct pollfd wrs + +# define FD_RD_INIT(sd, rds, excs) \ + (rds).fd = (sd); \ + (rds).events = MI_POLL_RD_FLAGS; \ + (rds).revents = 0 + +# define FD_WR_INIT(sd, wrs) \ + (wrs).fd = (sd); \ + (wrs).events = MI_POLL_WR_FLAGS; \ + (wrs).revents = 0 + +# define FD_IS_RD_EXC(sd, rds, excs) \ + (((rds).revents & (POLLERR | POLLHUP | POLLNVAL)) != 0) + +# define FD_IS_WR_RDY(sd, wrs) \ + (((wrs).revents & MI_POLL_WR_FLAGS) != 0) + +# define FD_IS_RD_RDY(sd, rds, excs) \ + (((rds).revents & MI_POLL_RD_FLAGS) != 0) + +# define FD_WR_READY(sd, excs, timeout) \ + poll(&(wrs), 1, MI_MS(timeout)) + +# define FD_RD_READY(sd, rds, excs, timeout) \ + poll(&(rds), 1, MI_MS(timeout)) + +#else /* SM_CONF_POLL */ + +# include <sm/fdset.h> +# define MI_POLLSELECT "select" + +# define FD_RD_VAR(rds, excs) fd_set rds, excs +# define FD_WR_VAR(wrs) fd_set wrs + +# define FD_RD_INIT(sd, rds, excs) \ + FD_ZERO(&(rds)); \ + FD_SET((unsigned int) (sd), &(rds)); \ + FD_ZERO(&(excs)); \ + FD_SET((unsigned int) (sd), &(excs)) + +# define FD_WR_INIT(sd, wrs) \ + FD_ZERO(&(wrs)); \ + FD_SET((unsigned int) (sd), &(wrs)); \ + +# define FD_IS_RD_EXC(sd, rds, excs) FD_ISSET(sd, &(excs)) +# define FD_IS_WR_RDY(sd, wrs) FD_ISSET((sd), &(wrs)) +# define FD_IS_RD_RDY(sd, rds, excs) FD_ISSET((sd), &(rds)) + +# define FD_WR_READY(sd, wrs, timeout) \ + select((sd) + 1, NULL, &(wrs), NULL, (timeout)) +# define FD_RD_READY(sd, rds, excs, timeout) \ + select((sd) + 1, &(rds), NULL, &(excs), (timeout)) + +#endif /* SM_CONF_POLL */ + +#include <sys/time.h> + +/* version info */ +#define MILTER_PRODUCT_NAME "libmilter" +#define MILTER_VERSION 100 + +/* some defaults */ +#define MI_TIMEOUT 7210 /* default timeout for read/write */ +#define MI_CHK_TIME 5 /* checking whether to terminate */ + +#ifndef MI_SOMAXCONN +# if SOMAXCONN > 20 +# define MI_SOMAXCONN SOMAXCONN +# else /* SOMAXCONN */ +# define MI_SOMAXCONN 20 +# endif /* SOMAXCONN */ +#endif /* ! MI_SOMAXCONN */ + +/* maximum number of repeated failures in mi_listener() */ +#define MAX_FAILS_M 16 /* malloc() */ +#define MAX_FAILS_T 16 /* thread creation */ +#define MAX_FAILS_A 16 /* accept() */ +#define MAX_FAILS_S 16 /* select() */ + +/* internal "commands", i.e., error codes */ +#define SMFIC_TIMEOUT ((char) 1) /* timeout */ +#define SMFIC_SELECT ((char) 2) /* select error */ +#define SMFIC_MALLOC ((char) 3) /* malloc error */ +#define SMFIC_RECVERR ((char) 4) /* recv() error */ +#define SMFIC_EOF ((char) 5) /* eof */ +#define SMFIC_UNKNERR ((char) 6) /* unknown error */ +#define SMFIC_TOOBIG ((char) 7) /* body chunk too big */ +#define SMFIC_VALIDCMD ' ' /* first valid command */ + +/* hack */ +#define smi_log syslog +#define sm_dprintf (void) printf +#define milter_ret int +#define SMI_LOG_ERR LOG_ERR +#define SMI_LOG_FATAL LOG_ERR +#define SMI_LOG_WARN LOG_WARNING +#define SMI_LOG_INFO LOG_INFO +#define SMI_LOG_DEBUG LOG_DEBUG + +/* stop? */ +#define MILTER_CONT 0 +#define MILTER_STOP 1 +#define MILTER_ABRT 2 + +/* functions */ +extern int mi_handle_session __P((SMFICTX_PTR)); +extern int mi_engine __P((SMFICTX_PTR)); +extern int mi_listener __P((char *, int, smfiDesc_ptr, time_t, int)); +extern void mi_clr_macros __P((SMFICTX_PTR, int)); +extern int mi_stop __P((void)); +extern int mi_control_startup __P((char *)); +extern void mi_stop_milters __P((int)); +extern void mi_clean_signals __P((void)); +extern struct hostent *mi_gethostbyname __P((char *, int)); +extern int mi_inet_pton __P((int, const char *, void *)); +extern void mi_closener __P((void)); +extern int mi_opensocket __P((char *, int, int, bool, smfiDesc_ptr)); + +/* communication functions */ +extern char *mi_rd_cmd __P((socket_t, struct timeval *, char *, size_t *, char *)); +extern int mi_wr_cmd __P((socket_t, struct timeval *, int, char *, size_t)); +extern bool mi_sendok __P((SMFICTX_PTR, int)); + + +#endif /* ! _LIBMILTER_H */ diff --git a/usr/src/cmd/sendmail/libmilter/listener.c b/usr/src/cmd/sendmail/libmilter/listener.c new file mode 100644 index 0000000000..1a30004ac2 --- /dev/null +++ b/usr/src/cmd/sendmail/libmilter/listener.c @@ -0,0 +1,955 @@ +/* + * Copyright (c) 1999-2004 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. + * + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sm/gen.h> +SM_RCSID("@(#)$Id: listener.c,v 8.111 2004/09/20 21:11:15 msk Exp $") + +/* +** listener.c -- threaded network listener +*/ + +#include "libmilter.h" +#include <sm/errstring.h> + +#include <sys/types.h> +#include <sys/stat.h> + + +# if NETINET || NETINET6 +# include <arpa/inet.h> +# endif /* NETINET || NETINET6 */ + +static smutex_t L_Mutex; +static int L_family; +static SOCKADDR_LEN_T L_socksize; +static socket_t listenfd = INVALID_SOCKET; + +static socket_t mi_milteropen __P((char *, int, bool, char *)); +static void *mi_thread_handle_wrapper __P((void *)); + +/* +** MI_OPENSOCKET -- create the socket where this filter and the MTA will meet +** +** Parameters: +** conn -- connection description +** backlog -- listen backlog +** dbg -- debug level +** rmsocket -- if true, try to unlink() the socket first +** (UNIX domain sockets only) +** smfi -- filter structure to use +** +** Return value: +** MI_SUCCESS/MI_FAILURE +*/ + +int +mi_opensocket(conn, backlog, dbg, rmsocket, smfi) + char *conn; + int backlog; + int dbg; + bool rmsocket; + smfiDesc_ptr smfi; +{ + if (smfi == NULL || conn == NULL) + return MI_FAILURE; + + if (ValidSocket(listenfd)) + return MI_SUCCESS; + + if (dbg > 0) + { + smi_log(SMI_LOG_DEBUG, + "%s: Opening listen socket on conn %s", + smfi->xxfi_name, conn); + } + (void) smutex_init(&L_Mutex); + (void) smutex_lock(&L_Mutex); + listenfd = mi_milteropen(conn, backlog, rmsocket, smfi->xxfi_name); + if (!ValidSocket(listenfd)) + { + smi_log(SMI_LOG_FATAL, + "%s: Unable to create listening socket on conn %s", + smfi->xxfi_name, conn); + (void) smutex_unlock(&L_Mutex); + return MI_FAILURE; + } +#if !SM_CONF_POLL + if (!SM_FD_OK_SELECT(listenfd)) + { + smi_log(SMI_LOG_ERR, "%s: fd %d is larger than FD_SETSIZE %d", + smfi->xxfi_name, listenfd, FD_SETSIZE); + (void) smutex_unlock(&L_Mutex); + return MI_FAILURE; + } +#endif /* !SM_CONF_POLL */ + (void) smutex_unlock(&L_Mutex); + return MI_SUCCESS; +} + +/* +** MI_MILTEROPEN -- setup socket to listen on +** +** Parameters: +** conn -- connection description +** backlog -- listen backlog +** rmsocket -- if true, try to unlink() the socket first +** (UNIX domain sockets only) +** name -- name for logging +** +** Returns: +** socket upon success, error code otherwise. +** +** Side effect: +** sets sockpath if UNIX socket. +*/ + +#if NETUNIX +static char *sockpath = NULL; +#endif /* NETUNIX */ + +static socket_t +mi_milteropen(conn, backlog, rmsocket, name) + char *conn; + int backlog; + bool rmsocket; + char *name; +{ + socket_t sock; + int sockopt = 1; + int fdflags; + size_t len = 0; + char *p; + char *colon; + char *at; + SOCKADDR addr; + + if (conn == NULL || conn[0] == '\0') + { + smi_log(SMI_LOG_ERR, "%s: empty or missing socket information", + name); + return INVALID_SOCKET; + } + (void) memset(&addr, '\0', sizeof addr); + + /* protocol:filename or protocol:port@host */ + p = conn; + colon = strchr(p, ':'); + if (colon != NULL) + { + *colon = '\0'; + + if (*p == '\0') + { +#if NETUNIX + /* default to AF_UNIX */ + addr.sa.sa_family = AF_UNIX; + L_socksize = sizeof (struct sockaddr_un); +#else /* NETUNIX */ +# if NETINET + /* default to AF_INET */ + addr.sa.sa_family = AF_INET; + L_socksize = sizeof addr.sin; +# else /* NETINET */ +# if NETINET6 + /* default to AF_INET6 */ + addr.sa.sa_family = AF_INET6; + L_socksize = sizeof addr.sin6; +# else /* NETINET6 */ + /* no protocols available */ + smi_log(SMI_LOG_ERR, + "%s: no valid socket protocols available", + name); + return INVALID_SOCKET; +# endif /* NETINET6 */ +# endif /* NETINET */ +#endif /* NETUNIX */ + } +#if NETUNIX + else if (strcasecmp(p, "unix") == 0 || + strcasecmp(p, "local") == 0) + { + addr.sa.sa_family = AF_UNIX; + L_socksize = sizeof (struct sockaddr_un); + } +#endif /* NETUNIX */ +#if NETINET + else if (strcasecmp(p, "inet") == 0) + { + addr.sa.sa_family = AF_INET; + L_socksize = sizeof addr.sin; + } +#endif /* NETINET */ +#if NETINET6 + else if (strcasecmp(p, "inet6") == 0) + { + addr.sa.sa_family = AF_INET6; + L_socksize = sizeof addr.sin6; + } +#endif /* NETINET6 */ + else + { + smi_log(SMI_LOG_ERR, "%s: unknown socket type %s", + name, p); + return INVALID_SOCKET; + } + *colon++ = ':'; + } + else + { + colon = p; +#if NETUNIX + /* default to AF_UNIX */ + addr.sa.sa_family = AF_UNIX; + L_socksize = sizeof (struct sockaddr_un); +#else /* NETUNIX */ +# if NETINET + /* default to AF_INET */ + addr.sa.sa_family = AF_INET; + L_socksize = sizeof addr.sin; +# else /* NETINET */ +# if NETINET6 + /* default to AF_INET6 */ + addr.sa.sa_family = AF_INET6; + L_socksize = sizeof addr.sin6; +# else /* NETINET6 */ + smi_log(SMI_LOG_ERR, "%s: unknown socket type %s", + name, p); + return INVALID_SOCKET; +# endif /* NETINET6 */ +# endif /* NETINET */ +#endif /* NETUNIX */ + } + +#if NETUNIX + if (addr.sa.sa_family == AF_UNIX) + { +# if 0 + long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_CREAT|SFF_MUSTOWN; +# endif /* 0 */ + + at = colon; + len = strlen(colon) + 1; + if (len >= sizeof addr.sunix.sun_path) + { + errno = EINVAL; + smi_log(SMI_LOG_ERR, "%s: UNIX socket name %s too long", + name, colon); + return INVALID_SOCKET; + } + (void) sm_strlcpy(addr.sunix.sun_path, colon, + sizeof addr.sunix.sun_path); +# if 0 + errno = safefile(colon, RunAsUid, RunAsGid, RunAsUserName, sff, + S_IRUSR|S_IWUSR, NULL); + + /* if not safe, don't create */ + if (errno != 0) + { + smi_log(SMI_LOG_ERR, + "%s: UNIX socket name %s unsafe", + name, colon); + return INVALID_SOCKET; + } +# endif /* 0 */ + } +#endif /* NETUNIX */ + +#if NETINET || NETINET6 + if ( +# if NETINET + addr.sa.sa_family == AF_INET +# endif /* NETINET */ +# if NETINET && NETINET6 + || +# endif /* NETINET && NETINET6 */ +# if NETINET6 + addr.sa.sa_family == AF_INET6 +# endif /* NETINET6 */ + ) + { + unsigned short port; + + /* Parse port@host */ + at = strchr(colon, '@'); + if (at == NULL) + { + switch (addr.sa.sa_family) + { +# if NETINET + case AF_INET: + addr.sin.sin_addr.s_addr = INADDR_ANY; + break; +# endif /* NETINET */ + +# if NETINET6 + case AF_INET6: + addr.sin6.sin6_addr = in6addr_any; + break; +# endif /* NETINET6 */ + } + } + else + *at = '\0'; + + if (isascii(*colon) && isdigit(*colon)) + port = htons((unsigned short) atoi(colon)); + else + { +# ifdef NO_GETSERVBYNAME + smi_log(SMI_LOG_ERR, "%s: invalid port number %s", + name, colon); + return INVALID_SOCKET; +# else /* NO_GETSERVBYNAME */ + register struct servent *sp; + + sp = getservbyname(colon, "tcp"); + if (sp == NULL) + { + smi_log(SMI_LOG_ERR, + "%s: unknown port name %s", + name, colon); + return INVALID_SOCKET; + } + port = sp->s_port; +# endif /* NO_GETSERVBYNAME */ + } + if (at != NULL) + { + *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 && + mi_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) + { + smi_log(SMI_LOG_ERR, + "%s: Invalid numeric domain spec \"%s\"", + name, at); + return INVALID_SOCKET; + } + } + else + { + smi_log(SMI_LOG_ERR, + "%s: Invalid numeric domain spec \"%s\"", + name, at); + return INVALID_SOCKET; + } + } + else + { + struct hostent *hp = NULL; + + hp = mi_gethostbyname(at, addr.sa.sa_family); + if (hp == NULL) + { + smi_log(SMI_LOG_ERR, + "%s: Unknown host name %s", + name, at); + return INVALID_SOCKET; + } + addr.sa.sa_family = hp->h_addrtype; + switch (hp->h_addrtype) + { +# if NETINET + case AF_INET: + (void) memmove(&addr.sin.sin_addr, + hp->h_addr, + INADDRSZ); + addr.sin.sin_port = port; + break; +# endif /* NETINET */ + +# if NETINET6 + case AF_INET6: + (void) memmove(&addr.sin6.sin6_addr, + hp->h_addr, + IN6ADDRSZ); + addr.sin6.sin6_port = port; + break; +# endif /* NETINET6 */ + + default: + smi_log(SMI_LOG_ERR, + "%s: Unknown protocol for %s (%d)", + name, at, hp->h_addrtype); + return INVALID_SOCKET; + } +# if NETINET6 + freehostent(hp); +# endif /* NETINET6 */ + } + } + else + { + switch (addr.sa.sa_family) + { +# if NETINET + case AF_INET: + addr.sin.sin_port = port; + break; +# endif /* NETINET */ +# if NETINET6 + case AF_INET6: + addr.sin6.sin6_port = port; + break; +# endif /* NETINET6 */ + } + } + } +#endif /* NETINET || NETINET6 */ + + sock = socket(addr.sa.sa_family, SOCK_STREAM, 0); + if (!ValidSocket(sock)) + { + smi_log(SMI_LOG_ERR, + "%s: Unable to create new socket: %s", + name, sm_errstring(errno)); + return INVALID_SOCKET; + } + + if ((fdflags = fcntl(sock, F_GETFD, 0)) == -1 || + fcntl(sock, F_SETFD, fdflags | FD_CLOEXEC) == -1) + { + smi_log(SMI_LOG_ERR, + "%s: Unable to set close-on-exec: %s", name, + sm_errstring(errno)); + (void) closesocket(sock); + return INVALID_SOCKET; + } + + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *) &sockopt, + sizeof(sockopt)) == -1) + { + smi_log(SMI_LOG_ERR, + "%s: Unable to setsockopt: %s", name, + sm_errstring(errno)); + (void) closesocket(sock); + return INVALID_SOCKET; + } + +#if NETUNIX + if (addr.sa.sa_family == AF_UNIX && rmsocket) + { + struct stat s; + + if (stat(colon, &s) != 0) + { + if (errno != ENOENT) + { + smi_log(SMI_LOG_ERR, + "%s: Unable to stat() %s: %s", + name, colon, sm_errstring(errno)); + (void) closesocket(sock); + return INVALID_SOCKET; + } + } + else if (!S_ISSOCK(s.st_mode)) + { + smi_log(SMI_LOG_ERR, + "%s: %s is not a UNIX domain socket", + name, colon); + (void) closesocket(sock); + return INVALID_SOCKET; + } + else if (unlink(colon) != 0) + { + smi_log(SMI_LOG_ERR, + "%s: Unable to remove %s: %s", + name, colon, sm_errstring(errno)); + (void) closesocket(sock); + return INVALID_SOCKET; + } + } +#endif /* NETUNIX */ + + if (bind(sock, &addr.sa, L_socksize) < 0) + { + smi_log(SMI_LOG_ERR, + "%s: Unable to bind to port %s: %s", + name, conn, sm_errstring(errno)); + (void) closesocket(sock); + return INVALID_SOCKET; + } + + if (listen(sock, backlog) < 0) + { + smi_log(SMI_LOG_ERR, + "%s: listen call failed: %s", name, + sm_errstring(errno)); + (void) closesocket(sock); + return INVALID_SOCKET; + } + +#if NETUNIX + if (addr.sa.sa_family == AF_UNIX && len > 0) + { + /* + ** Set global variable sockpath so the UNIX socket can be + ** unlink()ed at exit. + */ + + sockpath = (char *) malloc(len); + if (sockpath != NULL) + (void) sm_strlcpy(sockpath, colon, len); + else + { + smi_log(SMI_LOG_ERR, + "%s: can't malloc(%d) for sockpath: %s", + name, (int) len, sm_errstring(errno)); + (void) closesocket(sock); + return INVALID_SOCKET; + } + } +#endif /* NETUNIX */ + L_family = addr.sa.sa_family; + return sock; +} +/* +** MI_THREAD_HANDLE_WRAPPER -- small wrapper to handle session +** +** Parameters: +** arg -- argument to pass to mi_handle_session() +** +** Returns: +** results from mi_handle_session() +*/ + +static void * +mi_thread_handle_wrapper(arg) + void *arg; +{ + return (void *) mi_handle_session(arg); +} + +/* +** MI_CLOSENER -- close listen socket +** +** NOTE: It is assumed that this function is called from a +** function that has a mutex lock (currently mi_stop_milters()). +** +** Parameters: +** none. +** +** Returns: +** none. +*/ + +void +mi_closener() +{ + (void) smutex_lock(&L_Mutex); + if (ValidSocket(listenfd)) + { +#if NETUNIX + bool removable; + struct stat sockinfo; + struct stat fileinfo; + + removable = sockpath != NULL && + geteuid() != 0 && + fstat(listenfd, &sockinfo) == 0 && + (S_ISFIFO(sockinfo.st_mode) +# ifdef S_ISSOCK + || S_ISSOCK(sockinfo.st_mode) +# endif /* S_ISSOCK */ + ); +#endif /* NETUNIX */ + + (void) closesocket(listenfd); + listenfd = INVALID_SOCKET; + +#if NETUNIX + /* XXX sleep() some time before doing this? */ + if (sockpath != NULL) + { + if (removable && + stat(sockpath, &fileinfo) == 0 && + ((fileinfo.st_dev == sockinfo.st_dev && + fileinfo.st_ino == sockinfo.st_ino) +# ifdef S_ISSOCK + || S_ISSOCK(fileinfo.st_mode) +# endif /* S_ISSOCK */ + ) + && + (S_ISFIFO(fileinfo.st_mode) +# ifdef S_ISSOCK + || S_ISSOCK(fileinfo.st_mode) +# endif /* S_ISSOCK */ + )) + (void) unlink(sockpath); + free(sockpath); + sockpath = NULL; + } +#endif /* NETUNIX */ + } + (void) smutex_unlock(&L_Mutex); +} + +/* +** MI_LISTENER -- Generic listener harness +** +** Open up listen port +** Wait for connections +** +** Parameters: +** conn -- connection description +** dbg -- debug level +** smfi -- filter structure to use +** timeout -- timeout for reads/writes +** backlog -- listen queue backlog size +** +** Returns: +** MI_SUCCESS -- Exited normally +** (session finished or we were told to exit) +** MI_FAILURE -- Network initialization failed. +*/ + +#if BROKEN_PTHREAD_SLEEP + +/* +** Solaris 2.6, perhaps others, gets an internal threads library panic +** when sleep() is used: +** +** thread_create() failed, returned 11 (EINVAL) +** co_enable, thr_create() returned error = 24 +** libthread panic: co_enable failed (PID: 17793 LWP 1) +** stacktrace: +** ef526b10 +** ef52646c +** ef534cbc +** 156a4 +** 14644 +** 1413c +** 135e0 +** 0 +*/ + +# define MI_SLEEP(s) \ +{ \ + int rs = 0; \ + struct timeval st; \ + \ + st.tv_sec = (s); \ + st.tv_usec = 0; \ + if (st.tv_sec > 0) \ + { \ + for (;;) \ + { \ + rs = select(0, NULL, NULL, NULL, &st); \ + if (rs < 0 && errno == EINTR) \ + continue; \ + if (rs != 0) \ + { \ + smi_log(SMI_LOG_ERR, \ + "MI_SLEEP(): select() returned non-zero result %d, errno = %d", \ + rs, errno); \ + } \ + break; \ + } \ + } \ +} +#else /* BROKEN_PTHREAD_SLEEP */ +# define MI_SLEEP(s) sleep((s)) +#endif /* BROKEN_PTHREAD_SLEEP */ + +int +mi_listener(conn, dbg, smfi, timeout, backlog) + char *conn; + int dbg; + smfiDesc_ptr smfi; + time_t timeout; + int backlog; +{ + socket_t connfd = INVALID_SOCKET; +#if _FFR_DUP_FD + socket_t dupfd = INVALID_SOCKET; +#endif /* _FFR_DUP_FD */ + int sockopt = 1; + int r, mistop; + int ret = MI_SUCCESS; + int mcnt = 0; /* error count for malloc() failures */ + int tcnt = 0; /* error count for thread_create() failures */ + int acnt = 0; /* error count for accept() failures */ + int scnt = 0; /* error count for select() failures */ + int save_errno = 0; + sthread_t thread_id; + _SOCK_ADDR cliaddr; + SOCKADDR_LEN_T clilen; + SMFICTX_PTR ctx; + FD_RD_VAR(rds, excs); + struct timeval chktime; + + if (mi_opensocket(conn, backlog, dbg, false, smfi) == MI_FAILURE) + return MI_FAILURE; + + clilen = L_socksize; + while ((mistop = mi_stop()) == MILTER_CONT) + { + (void) smutex_lock(&L_Mutex); + if (!ValidSocket(listenfd)) + { + ret = MI_FAILURE; + smi_log(SMI_LOG_ERR, + "%s: listenfd=%d corrupted, terminating, errno=%d", + smfi->xxfi_name, listenfd, errno); + (void) smutex_unlock(&L_Mutex); + break; + } + + /* select on interface ports */ + FD_RD_INIT(listenfd, rds, excs); + chktime.tv_sec = MI_CHK_TIME; + chktime.tv_usec = 0; + r = FD_RD_READY(listenfd, rds, excs, &chktime); + if (r == 0) /* timeout */ + { + (void) smutex_unlock(&L_Mutex); + continue; /* just check mi_stop() */ + } + if (r < 0) + { + save_errno = errno; + (void) smutex_unlock(&L_Mutex); + if (save_errno == EINTR) + continue; + scnt++; + smi_log(SMI_LOG_ERR, + "%s: select() failed (%s), %s", + smfi->xxfi_name, sm_errstring(save_errno), + scnt >= MAX_FAILS_S ? "abort" : "try again"); + MI_SLEEP(scnt); + if (scnt >= MAX_FAILS_S) + { + ret = MI_FAILURE; + break; + } + continue; + } + if (!FD_IS_RD_RDY(listenfd, rds, excs)) + { + /* some error: just stop for now... */ + ret = MI_FAILURE; + (void) smutex_unlock(&L_Mutex); + smi_log(SMI_LOG_ERR, + "%s: %s() returned exception for socket, abort", + smfi->xxfi_name, MI_POLLSELECT); + break; + } + scnt = 0; /* reset error counter for select() */ + + (void) memset(&cliaddr, '\0', sizeof cliaddr); + connfd = accept(listenfd, (struct sockaddr *) &cliaddr, + &clilen); + save_errno = errno; + (void) smutex_unlock(&L_Mutex); + + /* + ** If remote side closes before + ** accept() finishes, sockaddr + ** might not be fully filled in. + */ + + if (ValidSocket(connfd) && + (clilen == 0 || +# ifdef BSD4_4_SOCKADDR + cliaddr.sa.sa_len == 0 || +# endif /* BSD4_4_SOCKADDR */ + cliaddr.sa.sa_family != L_family)) + { + (void) closesocket(connfd); + connfd = INVALID_SOCKET; + save_errno = EINVAL; + } + +#if !SM_CONF_POLL + /* check if acceptable for select() */ + if (ValidSocket(connfd) && !SM_FD_OK_SELECT(connfd)) + { + (void) closesocket(connfd); + connfd = INVALID_SOCKET; + save_errno = ERANGE; + } +#endif /* !SM_CONF_POLL */ + + if (!ValidSocket(connfd)) + { + if (save_errno == EINTR +#ifdef EAGAIN + || save_errno == EAGAIN +#endif /* EAGAIN */ +#ifdef ECONNABORTED + || save_errno == ECONNABORTED +#endif /* ECONNABORTED */ +#ifdef EMFILE + || save_errno == EMFILE +#endif /* EMFILE */ +#ifdef ENFILE + || save_errno == ENFILE +#endif /* ENFILE */ +#ifdef ENOBUFS + || save_errno == ENOBUFS +#endif /* ENOBUFS */ +#ifdef ENOMEM + || save_errno == ENOMEM +#endif /* ENOMEM */ +#ifdef ENOSR + || save_errno == ENOSR +#endif /* ENOSR */ +#ifdef EWOULDBLOCK + || save_errno == EWOULDBLOCK +#endif /* EWOULDBLOCK */ + ) + continue; + acnt++; + smi_log(SMI_LOG_ERR, + "%s: accept() returned invalid socket (%s), %s", + smfi->xxfi_name, sm_errstring(save_errno), + acnt >= MAX_FAILS_A ? "abort" : "try again"); + MI_SLEEP(acnt); + if (acnt >= MAX_FAILS_A) + { + ret = MI_FAILURE; + break; + } + continue; + } + acnt = 0; /* reset error counter for accept() */ +#if _FFR_DUP_FD + dupfd = fcntl(connfd, F_DUPFD, 256); + if (ValidSocket(dupfd) +# if !SM_CONF_POLL + && SM_FD_OK_SELECT(dupfd) +# endif /* !SM_CONF_POLL */ + ) + { + close(connfd); + connfd = dupfd; + dupfd = INVALID_SOCKET; + } +#endif /* _FFR_DUP_FD */ + + if (setsockopt(connfd, SOL_SOCKET, SO_KEEPALIVE, + (void *) &sockopt, sizeof sockopt) < 0) + { + smi_log(SMI_LOG_WARN, "%s: setsockopt() failed (%s)", + smfi->xxfi_name, sm_errstring(errno)); + /* XXX: continue? */ + } + if ((ctx = (SMFICTX_PTR) malloc(sizeof *ctx)) == NULL) + { + (void) closesocket(connfd); + mcnt++; + smi_log(SMI_LOG_ERR, "%s: malloc(ctx) failed (%s), %s", + smfi->xxfi_name, sm_errstring(save_errno), + mcnt >= MAX_FAILS_M ? "abort" : "try again"); + MI_SLEEP(mcnt); + if (mcnt >= MAX_FAILS_M) + { + ret = MI_FAILURE; + break; + } + continue; + } + mcnt = 0; /* reset error counter for malloc() */ + (void) memset(ctx, '\0', sizeof *ctx); + ctx->ctx_sd = connfd; + ctx->ctx_dbg = dbg; + ctx->ctx_timeout = timeout; + ctx->ctx_smfi = smfi; +#if 0 + if (smfi->xxfi_eoh == NULL) + if (smfi->xxfi_eom == NULL) + if (smfi->xxfi_abort == NULL) + if (smfi->xxfi_close == NULL) +#endif /* 0 */ + if (smfi->xxfi_connect == NULL) + ctx->ctx_pflags |= SMFIP_NOCONNECT; + if (smfi->xxfi_helo == NULL) + ctx->ctx_pflags |= SMFIP_NOHELO; + if (smfi->xxfi_envfrom == NULL) + ctx->ctx_pflags |= SMFIP_NOMAIL; + if (smfi->xxfi_envrcpt == NULL) + ctx->ctx_pflags |= SMFIP_NORCPT; + if (smfi->xxfi_header == NULL) + ctx->ctx_pflags |= SMFIP_NOHDRS; + if (smfi->xxfi_eoh == NULL) + ctx->ctx_pflags |= SMFIP_NOEOH; + if (smfi->xxfi_body == NULL) + ctx->ctx_pflags |= SMFIP_NOBODY; + + if ((r = thread_create(&thread_id, + mi_thread_handle_wrapper, + (void *) ctx)) != 0) + { + tcnt++; + smi_log(SMI_LOG_ERR, + "%s: thread_create() failed: %d, %s", + smfi->xxfi_name, r, + tcnt >= MAX_FAILS_T ? "abort" : "try again"); + MI_SLEEP(tcnt); + (void) closesocket(connfd); + free(ctx); + if (tcnt >= MAX_FAILS_T) + { + ret = MI_FAILURE; + break; + } + continue; + } + tcnt = 0; + } + if (ret != MI_SUCCESS) + mi_stop_milters(MILTER_ABRT); + else + { + if (mistop != MILTER_CONT) + smi_log(SMI_LOG_INFO, "%s: mi_stop=%d", + smfi->xxfi_name, mistop); + mi_closener(); + } + (void) smutex_destroy(&L_Mutex); + return ret; +} diff --git a/usr/src/cmd/sendmail/libmilter/llib-lmilter b/usr/src/cmd/sendmail/libmilter/llib-lmilter new file mode 100644 index 0000000000..0d56627c3e --- /dev/null +++ b/usr/src/cmd/sendmail/libmilter/llib-lmilter @@ -0,0 +1,32 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (the "License"). You may not use this file except in compliance + * with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* LINTLIBRARY */ +/* PROTOLIB1 */ + +#include "libmilter.h" diff --git a/usr/src/cmd/sendmail/libmilter/main.c b/usr/src/cmd/sendmail/libmilter/main.c new file mode 100644 index 0000000000..4115951fee --- /dev/null +++ b/usr/src/cmd/sendmail/libmilter/main.c @@ -0,0 +1,245 @@ +/* + * Copyright (c) 1999-2003 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. + * + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sm/gen.h> +SM_RCSID("@(#)$Id: main.c,v 8.79 2003/10/20 22:25:09 ca Exp $") + +#define _DEFINE 1 +#include "libmilter.h" +#include <fcntl.h> +#include <sys/stat.h> + + +static smfiDesc_ptr smfi = NULL; + +/* +** SMFI_REGISTER -- register a filter description +** +** Parameters: +** smfilter -- description of filter to register +** +** Returns: +** MI_SUCCESS/MI_FAILURE +*/ + +int +smfi_register(smfilter) + smfiDesc_str smfilter; +{ + size_t len; + + if (smfi == NULL) + { + smfi = (smfiDesc_ptr) malloc(sizeof *smfi); + if (smfi == NULL) + return MI_FAILURE; + } + (void) memcpy(smfi, &smfilter, sizeof *smfi); + if (smfilter.xxfi_name == NULL) + smfilter.xxfi_name = "Unknown"; + + len = strlen(smfilter.xxfi_name) + 1; + smfi->xxfi_name = (char *) malloc(len); + if (smfi->xxfi_name == NULL) + return MI_FAILURE; + (void) sm_strlcpy(smfi->xxfi_name, smfilter.xxfi_name, len); + + /* compare milter version with hard coded version */ + if (smfi->xxfi_version != SMFI_VERSION) + { + /* hard failure for now! */ + smi_log(SMI_LOG_ERR, + "%s: smfi_register: version mismatch application: %d != milter: %d", + smfi->xxfi_name, smfi->xxfi_version, + (int) SMFI_VERSION); + + /* XXX how about smfi? */ + free(smfi->xxfi_name); + return MI_FAILURE; + } + + return MI_SUCCESS; +} + +/* +** SMFI_STOP -- stop milter +** +** Parameters: +** none. +** +** Returns: +** success. +*/ + +int +smfi_stop() +{ + mi_stop_milters(MILTER_STOP); + return MI_SUCCESS; +} + +/* +** Default values for some variables. +** Most of these can be changed with the functions below. +*/ + +static int dbg = 0; +static char *conn = NULL; +static int timeout = MI_TIMEOUT; +static int backlog = MI_SOMAXCONN; + +/* +** SMFI_OPENSOCKET -- try the socket setup to make sure we'll be +** able to start up +** +** Parameters: +** rmsocket -- if true, instructs libmilter to attempt +** to remove the socket before creating it; +** only applies for "local:" or "unix:" sockets +** +** Return: +** MI_SUCCESS/MI_FAILURE +*/ + +int +smfi_opensocket(rmsocket) + bool rmsocket; +{ + if (smfi == NULL || conn == NULL) + return MI_FAILURE; + + return mi_opensocket(conn, backlog, dbg, rmsocket, smfi); +} + +/* +** SMFI_SETDBG -- set debug level. +** +** Parameters: +** odbg -- new debug level. +** +** Returns: +** MI_SUCCESS +*/ + +int +smfi_setdbg(odbg) + int odbg; +{ + dbg = odbg; + return MI_SUCCESS; +} + +/* +** SMFI_SETTIMEOUT -- set timeout (for read/write). +** +** Parameters: +** otimeout -- new timeout. +** +** Returns: +** MI_SUCCESS +*/ + +int +smfi_settimeout(otimeout) + int otimeout; +{ + timeout = otimeout; + return MI_SUCCESS; +} + +/* +** SMFI_SETCONN -- set connection information (socket description) +** +** Parameters: +** oconn -- new connection information. +** +** Returns: +** MI_SUCCESS/MI_FAILURE +*/ + +int +smfi_setconn(oconn) + char *oconn; +{ + size_t l; + + if (oconn == NULL || *oconn == '\0') + return MI_FAILURE; + l = strlen(oconn) + 1; + if ((conn = (char *) malloc(l)) == NULL) + return MI_FAILURE; + if (sm_strlcpy(conn, oconn, l) >= l) + return MI_FAILURE; + return MI_SUCCESS; +} + +/* +** SMFI_SETBACKLOG -- set backlog +** +** Parameters: +** obacklog -- new backlog. +** +** Returns: +** MI_SUCCESS/MI_FAILURE +*/ + +int +smfi_setbacklog(obacklog) + int obacklog; +{ + if (obacklog <= 0) + return MI_FAILURE; + backlog = obacklog; + return MI_SUCCESS; +} + + +/* +** SMFI_MAIN -- setup milter connnection and start listener. +** +** Parameters: +** none. +** +** Returns: +** MI_SUCCESS/MI_FAILURE +*/ + +int +smfi_main() +{ + int r; + + (void) signal(SIGPIPE, SIG_IGN); + if (conn == NULL) + { + smi_log(SMI_LOG_FATAL, "%s: missing connection information", + smfi->xxfi_name); + return MI_FAILURE; + } + + (void) atexit(mi_clean_signals); + if (mi_control_startup(smfi->xxfi_name) != MI_SUCCESS) + { + smi_log(SMI_LOG_FATAL, + "%s: Couldn't start signal thread", + smfi->xxfi_name); + return MI_FAILURE; + } + r = MI_SUCCESS; + + /* Startup the listener */ + if (mi_listener(conn, dbg, smfi, timeout, backlog) != MI_SUCCESS) + r = MI_FAILURE; + + return r; +} + diff --git a/usr/src/cmd/sendmail/libmilter/signal.c b/usr/src/cmd/sendmail/libmilter/signal.c new file mode 100644 index 0000000000..e3744cdc21 --- /dev/null +++ b/usr/src/cmd/sendmail/libmilter/signal.c @@ -0,0 +1,223 @@ +/* + * Copyright (c) 1999-2004 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. + * + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sm/gen.h> +SM_RCSID("@(#)$Id: signal.c,v 8.42 2004/08/20 21:10:30 ca Exp $") + +#include "libmilter.h" + +/* +** thread to handle signals +*/ + +static smutex_t M_Mutex; + +static int MilterStop = MILTER_CONT; + +static void *mi_signal_thread __P((void *)); +static int mi_spawn_signal_thread __P((char *)); + +/* +** MI_STOP -- return value of MilterStop +** +** Parameters: +** none. +** +** Returns: +** value of MilterStop +*/ + +int +mi_stop() +{ + return MilterStop; +} +/* +** MI_STOP_MILTERS -- set value of MilterStop +** +** Parameters: +** v -- new value for MilterStop. +** +** Returns: +** none. +*/ + +void +mi_stop_milters(v) + int v; +{ + (void) smutex_lock(&M_Mutex); + if (MilterStop < v) + MilterStop = v; + + /* close listen socket */ + mi_closener(); + (void) smutex_unlock(&M_Mutex); +} +/* +** MI_CLEAN_SIGNALS -- clean up signal handler thread +** +** Parameters: +** none. +** +** Returns: +** none. +*/ + +void +mi_clean_signals() +{ + (void) smutex_destroy(&M_Mutex); +} +/* +** MI_SIGNAL_THREAD -- thread to deal with signals +** +** Parameters: +** name -- name of milter +** +** Returns: +** NULL +*/ + +static void * +mi_signal_thread(name) + void *name; +{ + int sig, errs; + sigset_t set; + + (void) sigemptyset(&set); + (void) sigaddset(&set, SIGHUP); + (void) sigaddset(&set, SIGTERM); + + /* Handle Ctrl-C gracefully for debugging */ + (void) sigaddset(&set, SIGINT); + errs = 0; + + for (;;) + { + sig = 0; +#if defined(SOLARIS) || defined(__svr5__) + if ((sig = sigwait(&set)) < 0) +#else /* defined(SOLARIS) || defined(__svr5__) */ + if (sigwait(&set, &sig) != 0) +#endif /* defined(SOLARIS) || defined(__svr5__) */ + { + /* this can happen on OSF/1 (at least) */ + if (errno == EINTR) + continue; + smi_log(SMI_LOG_ERR, + "%s: sigwait returned error: %d", + (char *)name, errno); + if (++errs > MAX_FAILS_T) + { + mi_stop_milters(MILTER_ABRT); + return NULL; + } + continue; + } + errs = 0; + + switch (sig) + { + case SIGHUP: + case SIGTERM: + mi_stop_milters(MILTER_STOP); + return NULL; + case SIGINT: + mi_stop_milters(MILTER_ABRT); + return NULL; + default: + smi_log(SMI_LOG_ERR, + "%s: sigwait returned unmasked signal: %d", + (char *)name, sig); + break; + } + } + /* NOTREACHED */ +} +/* +** MI_SPAWN_SIGNAL_THREAD -- spawn thread to handle signals +** +** Parameters: +** name -- name of milter +** +** Returns: +** MI_SUCCESS/MI_FAILURE +*/ + +static int +mi_spawn_signal_thread(name) + char *name; +{ + sthread_t tid; + int r; + sigset_t set; + + /* Mask HUP and KILL signals */ + (void) sigemptyset(&set); + (void) sigaddset(&set, SIGHUP); + (void) sigaddset(&set, SIGTERM); + (void) sigaddset(&set, SIGINT); + + if (pthread_sigmask(SIG_BLOCK, &set, NULL) != 0) + { + smi_log(SMI_LOG_ERR, + "%s: Couldn't mask HUP and KILL signals", name); + return MI_FAILURE; + } + r = thread_create(&tid, mi_signal_thread, (void *)name); + if (r != 0) + { + smi_log(SMI_LOG_ERR, + "%s: Couldn't start signal thread: %d", + name, r); + return MI_FAILURE; + } + return MI_SUCCESS; +} +/* +** MI_CONTROL_STARTUP -- startup for thread to handle signals +** +** Parameters: +** name -- name of milter +** +** Returns: +** MI_SUCCESS/MI_FAILURE +*/ + +int +mi_control_startup(name) + char *name; +{ + + if (!smutex_init(&M_Mutex)) + { + smi_log(SMI_LOG_ERR, + "%s: Couldn't initialize control pipe mutex", name); + return MI_FAILURE; + } + + /* + ** spawn_signal_thread must happen before other threads are spawned + ** off so that it can mask the right signals and other threads + ** will inherit that mask. + */ + if (mi_spawn_signal_thread(name) == MI_FAILURE) + { + smi_log(SMI_LOG_ERR, + "%s: Couldn't spawn signal thread", name); + (void) smutex_destroy(&M_Mutex); + return MI_FAILURE; + } + return MI_SUCCESS; +} diff --git a/usr/src/cmd/sendmail/libmilter/sm_gethost.c b/usr/src/cmd/sendmail/libmilter/sm_gethost.c new file mode 100644 index 0000000000..504260ce50 --- /dev/null +++ b/usr/src/cmd/sendmail/libmilter/sm_gethost.c @@ -0,0 +1,150 @@ +/* + * Copyright (c) 1999-2001, 2004 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. + * + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sm/gen.h> +SM_RCSID("@(#)$Id: sm_gethost.c,v 8.27 2004/08/20 21:12:37 ca Exp $") + +#include <sendmail.h> +#if NETINET || NETINET6 +# include <arpa/inet.h> +#endif /* NETINET || NETINET6 */ +#include "libmilter.h" + +/* +** MI_GETHOSTBY{NAME,ADDR} -- compatibility routines for gethostbyXXX +** +** Some operating systems have wierd problems with the gethostbyXXX +** routines. For example, Solaris versions at least through 2.3 +** don't properly deliver a canonical h_name field. This tries to +** work around these problems. +** +** Support IPv6 as well as IPv4. +*/ + +#if NETINET6 && NEEDSGETIPNODE + +static struct hostent *getipnodebyname __P((char *, int, int, int *)); + +# ifndef AI_ADDRCONFIG +# define AI_ADDRCONFIG 0 /* dummy */ +# endif /* ! AI_ADDRCONFIG */ +# ifndef AI_ALL +# define AI_ALL 0 /* dummy */ +# endif /* ! AI_ALL */ +# ifndef AI_DEFAULT +# define AI_DEFAULT 0 /* dummy */ +# endif /* ! AI_DEFAULT */ + +static struct hostent * +getipnodebyname(name, family, flags, err) + char *name; + int family; + int flags; + int *err; +{ + bool resv6 = true; + struct hostent *h; + + if (family == AF_INET6) + { + /* From RFC2133, section 6.1 */ + resv6 = bitset(RES_USE_INET6, _res.options); + _res.options |= RES_USE_INET6; + } + SM_SET_H_ERRNO(0); + h = gethostbyname(name); + if (family == AF_INET6 && !resv6) + _res.options &= ~RES_USE_INET6; + *err = h_errno; + return h; +} + +void +freehostent(h) + struct hostent *h; +{ + /* + ** Stub routine -- if they don't have getipnodeby*(), + ** they probably don't have the free routine either. + */ + + return; +} +#endif /* NEEDSGETIPNODE && NETINET6 */ + +struct hostent * +mi_gethostbyname(name, family) + char *name; + int family; +{ + struct hostent *h = NULL; +#if (SOLARIS > 10000 && SOLARIS < 20400) || (defined(SOLARIS) && SOLARIS < 204) || (defined(sony_news) && defined(__svr4)) +# if SOLARIS == 20300 || SOLARIS == 203 + static struct hostent hp; + static char buf[1000]; + extern struct hostent *_switch_gethostbyname_r(); + + h = _switch_gethostbyname_r(name, &hp, buf, sizeof(buf), &h_errno); +# else /* SOLARIS == 20300 || SOLARIS == 203 */ + extern struct hostent *__switch_gethostbyname(); + + h = __switch_gethostbyname(name); +# endif /* SOLARIS == 20300 || SOLARIS == 203 */ +#else /* (SOLARIS > 10000 && SOLARIS < 20400) || (defined(SOLARIS) && SOLARIS < 204) || (defined(sony_news) && defined(__svr4)) */ +# if NETINET6 + int flags = AI_DEFAULT|AI_ALL; + int err; +# endif /* NETINET6 */ + +# if NETINET6 +# if ADDRCONFIG_IS_BROKEN + flags &= ~AI_ADDRCONFIG; +# endif /* ADDRCONFIG_IS_BROKEN */ + h = getipnodebyname(name, family, flags, &err); + SM_SET_H_ERRNO(err); +# else /* NETINET6 */ + h = gethostbyname(name); +# endif /* NETINET6 */ + +#endif /* (SOLARIS > 10000 && SOLARIS < 20400) || (defined(SOLARIS) && SOLARIS < 204) || (defined(sony_news) && defined(__svr4)) */ + return h; +} + +#if NETINET6 +/* +** MI_INET_PTON -- convert printed form to network address. +** +** Wrapper for inet_pton() which handles IPv6: labels. +** +** Parameters: +** family -- address family +** src -- string +** dst -- destination address structure +** +** Returns: +** 1 if the address was valid +** 0 if the address wasn't parseable +** -1 if error +*/ + +int +mi_inet_pton(family, src, dst) + int family; + const char *src; + void *dst; +{ + if (family == AF_INET6 && + strncasecmp(src, "IPv6:", 5) == 0) + src += 5; + return inet_pton(family, src, dst); +} +#endif /* NETINET6 */ diff --git a/usr/src/cmd/sendmail/libmilter/smfi.c b/usr/src/cmd/sendmail/libmilter/smfi.c new file mode 100644 index 0000000000..a04811b370 --- /dev/null +++ b/usr/src/cmd/sendmail/libmilter/smfi.c @@ -0,0 +1,650 @@ +/* + * Copyright (c) 1999-2004 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. + * + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sm/gen.h> +SM_RCSID("@(#)$Id: smfi.c,v 8.73 2004/09/20 21:26:57 ca Exp $") +#include <sm/varargs.h> +#include "libmilter.h" + +static int smfi_header __P((SMFICTX *, int, int, char *, char *)); +static int myisenhsc __P((const char *, int)); + +/* for smfi_set{ml}reply, let's be generous. 256/16 should be sufficient */ +#define MAXREPLYLEN 980 /* max. length of a reply string */ +#define MAXREPLIES 32 /* max. number of reply strings */ + +/* +** SMFI_HEADER -- send a header to the MTA +** +** Parameters: +** ctx -- Opaque context structure +** cmd -- Header modification command +** hdridx -- Header index +** headerf -- Header field name +** headerv -- Header field value +** +** +** Returns: +** MI_SUCCESS/MI_FAILURE +*/ + +static int +smfi_header(ctx, cmd, hdridx, headerf, headerv) + SMFICTX *ctx; + int cmd; + int hdridx; + char *headerf; + char *headerv; +{ + size_t len, l1, l2, offset; + int r; + mi_int32 v; + char *buf; + struct timeval timeout; + + if (headerf == NULL || *headerf == '\0' || headerv == NULL) + return MI_FAILURE; + timeout.tv_sec = ctx->ctx_timeout; + timeout.tv_usec = 0; + l1 = strlen(headerf) + 1; + l2 = strlen(headerv) + 1; + len = l1 + l2; + if (hdridx >= 0) + len += MILTER_LEN_BYTES; + buf = malloc(len); + if (buf == NULL) + return MI_FAILURE; + offset = 0; + if (hdridx >= 0) + { + v = htonl(hdridx); + (void) memcpy(&(buf[0]), (void *) &v, MILTER_LEN_BYTES); + offset += MILTER_LEN_BYTES; + } + (void) memcpy(buf + offset, headerf, l1); + (void) memcpy(buf + offset + l1, headerv, l2); + r = mi_wr_cmd(ctx->ctx_sd, &timeout, cmd, buf, len); + free(buf); + return r; +} + +/* +** SMFI_ADDHEADER -- send a new header to the MTA +** +** Parameters: +** ctx -- Opaque context structure +** headerf -- Header field name +** headerv -- Header field value +** +** Returns: +** MI_SUCCESS/MI_FAILURE +*/ + +int +smfi_addheader(ctx, headerf, headerv) + SMFICTX *ctx; + char *headerf; + char *headerv; +{ + if (!mi_sendok(ctx, SMFIF_ADDHDRS)) + return MI_FAILURE; + + return smfi_header(ctx, SMFIR_ADDHEADER, -1, headerf, headerv); +} + +/* +** SMFI_INSHEADER -- send a new header to the MTA (to be inserted) +** +** Parameters: +** ctx -- Opaque context structure +** hdridx -- index into header list where insertion should occur +** headerf -- Header field name +** headerv -- Header field value +** +** Returns: +** MI_SUCCESS/MI_FAILURE +*/ + +int +smfi_insheader(ctx, hdridx, headerf, headerv) + SMFICTX *ctx; + int hdridx; + char *headerf; + char *headerv; +{ + if (!mi_sendok(ctx, SMFIF_ADDHDRS) || hdridx < 0) + return MI_FAILURE; + + return smfi_header(ctx, SMFIR_INSHEADER, hdridx, headerf, headerv); +} + +/* +** SMFI_CHGHEADER -- send a changed header to the MTA +** +** Parameters: +** ctx -- Opaque context structure +** headerf -- Header field name +** hdridx -- Header index value +** headerv -- Header field value +** +** Returns: +** MI_SUCCESS/MI_FAILURE +*/ + +int +smfi_chgheader(ctx, headerf, hdridx, headerv) + SMFICTX *ctx; + char *headerf; + mi_int32 hdridx; + char *headerv; +{ + if (!mi_sendok(ctx, SMFIF_CHGHDRS) || hdridx < 0) + return MI_FAILURE; + if (headerv == NULL) + headerv = ""; + + return smfi_header(ctx, SMFIR_CHGHEADER, hdridx, headerf, headerv); +} + +/* +** SMFI_ADDRCPT -- send an additional recipient to the MTA +** +** Parameters: +** ctx -- Opaque context structure +** rcpt -- recipient address +** +** Returns: +** MI_SUCCESS/MI_FAILURE +*/ + +int +smfi_addrcpt(ctx, rcpt) + SMFICTX *ctx; + char *rcpt; +{ + size_t len; + struct timeval timeout; + + if (rcpt == NULL || *rcpt == '\0') + return MI_FAILURE; + if (!mi_sendok(ctx, SMFIF_ADDRCPT)) + return MI_FAILURE; + timeout.tv_sec = ctx->ctx_timeout; + timeout.tv_usec = 0; + len = strlen(rcpt) + 1; + return mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_ADDRCPT, rcpt, len); +} + +/* +** SMFI_DELRCPT -- send a recipient to be removed to the MTA +** +** Parameters: +** ctx -- Opaque context structure +** rcpt -- recipient address +** +** Returns: +** MI_SUCCESS/MI_FAILURE +*/ + +int +smfi_delrcpt(ctx, rcpt) + SMFICTX *ctx; + char *rcpt; +{ + size_t len; + struct timeval timeout; + + if (rcpt == NULL || *rcpt == '\0') + return MI_FAILURE; + if (!mi_sendok(ctx, SMFIF_DELRCPT)) + return MI_FAILURE; + timeout.tv_sec = ctx->ctx_timeout; + timeout.tv_usec = 0; + len = strlen(rcpt) + 1; + return mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_DELRCPT, rcpt, len); +} + +/* +** SMFI_REPLACEBODY -- send a body chunk to the MTA +** +** Parameters: +** ctx -- Opaque context structure +** bodyp -- body chunk +** bodylen -- length of body chunk +** +** Returns: +** MI_SUCCESS/MI_FAILURE +*/ + +int +smfi_replacebody(ctx, bodyp, bodylen) + SMFICTX *ctx; + unsigned char *bodyp; + int bodylen; +{ + int len, off, r; + struct timeval timeout; + + if (bodylen < 0 || + (bodyp == NULL && bodylen > 0)) + return MI_FAILURE; + if (!mi_sendok(ctx, SMFIF_CHGBODY)) + return MI_FAILURE; + timeout.tv_sec = ctx->ctx_timeout; + timeout.tv_usec = 0; + + /* split body chunk if necessary */ + off = 0; + while (bodylen > 0) + { + len = (bodylen >= MILTER_CHUNK_SIZE) ? MILTER_CHUNK_SIZE : + bodylen; + if ((r = mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_REPLBODY, + (char *) (bodyp + off), len)) != MI_SUCCESS) + return r; + off += len; + bodylen -= len; + } + return MI_SUCCESS; +} + +/* +** SMFI_QUARANTINE -- quarantine an envelope +** +** Parameters: +** ctx -- Opaque context structure +** reason -- why? +** +** Returns: +** MI_SUCCESS/MI_FAILURE +*/ + +int +smfi_quarantine(ctx, reason) + SMFICTX *ctx; + char *reason; +{ + size_t len; + int r; + char *buf; + struct timeval timeout; + + if (reason == NULL || *reason == '\0') + return MI_FAILURE; + if (!mi_sendok(ctx, SMFIF_QUARANTINE)) + return MI_FAILURE; + timeout.tv_sec = ctx->ctx_timeout; + timeout.tv_usec = 0; + len = strlen(reason) + 1; + buf = malloc(len); + if (buf == NULL) + return MI_FAILURE; + (void) memcpy(buf, reason, len); + r = mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_QUARANTINE, buf, len); + free(buf); + return r; +} + +/* +** MYISENHSC -- check whether a string contains an enhanced status code +** +** Parameters: +** s -- string with possible enhanced status code. +** delim -- delim for enhanced status code. +** +** Returns: +** 0 -- no enhanced status code. +** >4 -- length of enhanced status code. +** +** Side Effects: +** none. +*/ + +static int +myisenhsc(s, delim) + const char *s; + int delim; +{ + int l, h; + + if (s == NULL) + return 0; + if (!((*s == '2' || *s == '4' || *s == '5') && s[1] == '.')) + return 0; + h = 0; + l = 2; + while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h])) + ++h; + if (h == 0 || s[l + h] != '.') + return 0; + l += h + 1; + h = 0; + while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h])) + ++h; + if (h == 0 || s[l + h] != delim) + return 0; + return l + h; +} + +/* +** SMFI_SETREPLY -- set the reply code for the next reply to the MTA +** +** Parameters: +** ctx -- Opaque context structure +** rcode -- The three-digit (RFC 821) SMTP reply code. +** xcode -- The extended (RFC 2034) reply code. +** message -- The text part of the SMTP reply. +** +** Returns: +** MI_SUCCESS/MI_FAILURE +*/ + +int +smfi_setreply(ctx, rcode, xcode, message) + SMFICTX *ctx; + char *rcode; + char *xcode; + char *message; +{ + size_t len; + char *buf; + + if (rcode == NULL || ctx == NULL) + return MI_FAILURE; + + /* ### <sp> \0 */ + len = strlen(rcode) + 2; + if (len != 5) + return MI_FAILURE; + if ((rcode[0] != '4' && rcode[0] != '5') || + !isascii(rcode[1]) || !isdigit(rcode[1]) || + !isascii(rcode[2]) || !isdigit(rcode[2])) + return MI_FAILURE; + if (xcode != NULL) + { + if (!myisenhsc(xcode, '\0')) + return MI_FAILURE; + len += strlen(xcode) + 1; + } + if (message != NULL) + { + size_t ml; + + /* XXX check also for unprintable chars? */ + if (strpbrk(message, "\r\n") != NULL) + return MI_FAILURE; + ml = strlen(message); + if (ml > MAXREPLYLEN) + return MI_FAILURE; + len += ml + 1; + } + buf = malloc(len); + if (buf == NULL) + return MI_FAILURE; /* oops */ + (void) sm_strlcpy(buf, rcode, len); + (void) sm_strlcat(buf, " ", len); + if (xcode != NULL) + (void) sm_strlcat(buf, xcode, len); + if (message != NULL) + { + if (xcode != NULL) + (void) sm_strlcat(buf, " ", len); + (void) sm_strlcat(buf, message, len); + } + if (ctx->ctx_reply != NULL) + free(ctx->ctx_reply); + ctx->ctx_reply = buf; + return MI_SUCCESS; +} + +/* +** SMFI_SETMLREPLY -- set multiline reply code for the next reply to the MTA +** +** Parameters: +** ctx -- Opaque context structure +** rcode -- The three-digit (RFC 821) SMTP reply code. +** xcode -- The extended (RFC 2034) reply code. +** txt, ... -- The text part of the SMTP reply, +** MUST be terminated with NULL. +** +** Returns: +** MI_SUCCESS/MI_FAILURE +*/ + +int +#if SM_VA_STD +smfi_setmlreply(SMFICTX *ctx, const char *rcode, const char *xcode, ...) +#else /* SM_VA_STD */ +smfi_setmlreply(ctx, rcode, xcode, va_alist) + SMFICTX *ctx; + const char *rcode; + const char *xcode; + va_dcl +#endif /* SM_VA_STD */ +{ + size_t len; + size_t rlen; + int args; + char *buf, *txt; + const char *xc; + char repl[16]; + SM_VA_LOCAL_DECL + + if (rcode == NULL || ctx == NULL) + return MI_FAILURE; + + /* ### <sp> */ + len = strlen(rcode) + 1; + if (len != 4) + return MI_FAILURE; + if ((rcode[0] != '4' && rcode[0] != '5') || + !isascii(rcode[1]) || !isdigit(rcode[1]) || + !isascii(rcode[2]) || !isdigit(rcode[2])) + return MI_FAILURE; + if (xcode != NULL) + { + if (!myisenhsc(xcode, '\0')) + return MI_FAILURE; + xc = xcode; + } + else + { + if (rcode[0] == '4') + xc = "4.0.0"; + else + xc = "5.0.0"; + } + + /* add trailing space */ + len += strlen(xc) + 1; + rlen = len; + args = 0; + SM_VA_START(ap, xcode); + while ((txt = SM_VA_ARG(ap, char *)) != NULL) + { + size_t tl; + + tl = strlen(txt); + if (tl > MAXREPLYLEN) + break; + + /* this text, reply codes, \r\n */ + len += tl + 2 + rlen; + if (++args > MAXREPLIES) + break; + + /* XXX check also for unprintable chars? */ + if (strpbrk(txt, "\r\n") != NULL) + break; + } + SM_VA_END(ap); + if (txt != NULL) + return MI_FAILURE; + + /* trailing '\0' */ + ++len; + buf = malloc(len); + if (buf == NULL) + return MI_FAILURE; /* oops */ + (void) sm_strlcpyn(buf, len, 3, rcode, args == 1 ? " " : "-", xc); + (void) sm_strlcpyn(repl, sizeof repl, 4, rcode, args == 1 ? " " : "-", + xc, " "); + SM_VA_START(ap, xcode); + txt = SM_VA_ARG(ap, char *); + if (txt != NULL) + { + (void) sm_strlcat2(buf, " ", txt, len); + while ((txt = SM_VA_ARG(ap, char *)) != NULL) + { + if (--args <= 1) + repl[3] = ' '; + (void) sm_strlcat2(buf, "\r\n", repl, len); + (void) sm_strlcat(buf, txt, len); + } + } + if (ctx->ctx_reply != NULL) + free(ctx->ctx_reply); + ctx->ctx_reply = buf; + SM_VA_END(ap); + return MI_SUCCESS; +} + +/* +** SMFI_SETPRIV -- set private data +** +** Parameters: +** ctx -- Opaque context structure +** privatedata -- pointer to private data +** +** Returns: +** MI_SUCCESS/MI_FAILURE +*/ + +int +smfi_setpriv(ctx, privatedata) + SMFICTX *ctx; + void *privatedata; +{ + if (ctx == NULL) + return MI_FAILURE; + ctx->ctx_privdata = privatedata; + return MI_SUCCESS; +} + +/* +** SMFI_GETPRIV -- get private data +** +** Parameters: +** ctx -- Opaque context structure +** +** Returns: +** pointer to private data +*/ + +void * +smfi_getpriv(ctx) + SMFICTX *ctx; +{ + if (ctx == NULL) + return NULL; + return ctx->ctx_privdata; +} + +/* +** SMFI_GETSYMVAL -- get the value of a macro +** +** See explanation in mfapi.h about layout of the structures. +** +** Parameters: +** ctx -- Opaque context structure +** symname -- name of macro +** +** Returns: +** value of macro (NULL in case of failure) +*/ + +char * +smfi_getsymval(ctx, symname) + SMFICTX *ctx; + char *symname; +{ + int i; + char **s; + char one[2]; + char braces[4]; + + if (ctx == NULL || symname == NULL || *symname == '\0') + return NULL; + + if (strlen(symname) == 3 && symname[0] == '{' && symname[2] == '}') + { + one[0] = symname[1]; + one[1] = '\0'; + } + else + one[0] = '\0'; + if (strlen(symname) == 1) + { + braces[0] = '{'; + braces[1] = *symname; + braces[2] = '}'; + braces[3] = '\0'; + } + else + braces[0] = '\0'; + + /* search backwards through the macro array */ + for (i = MAX_MACROS_ENTRIES - 1 ; i >= 0; --i) + { + if ((s = ctx->ctx_mac_ptr[i]) == NULL || + ctx->ctx_mac_buf[i] == NULL) + continue; + while (s != NULL && *s != NULL) + { + if (strcmp(*s, symname) == 0) + return *++s; + if (one[0] != '\0' && strcmp(*s, one) == 0) + return *++s; + if (braces[0] != '\0' && strcmp(*s, braces) == 0) + return *++s; + ++s; /* skip over macro value */ + ++s; /* points to next macro name */ + } + } + return NULL; +} + +/* +** SMFI_PROGRESS -- send "progress" message to the MTA to prevent premature +** timeouts during long milter-side operations +** +** Parameters: +** ctx -- Opaque context structure +** +** Return value: +** MI_SUCCESS/MI_FAILURE +*/ + +int +smfi_progress(ctx) + SMFICTX *ctx; +{ + struct timeval timeout; + + if (ctx == NULL) + return MI_FAILURE; + + timeout.tv_sec = ctx->ctx_timeout; + timeout.tv_usec = 0; + + return mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_PROGRESS, NULL, 0); +} diff --git a/usr/src/cmd/sendmail/libmilter/sparc/Makefile b/usr/src/cmd/sendmail/libmilter/sparc/Makefile new file mode 100644 index 0000000000..e86ae6a9f7 --- /dev/null +++ b/usr/src/cmd/sendmail/libmilter/sparc/Makefile @@ -0,0 +1,29 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License, Version 1.0 only +# (the "License"). You may not use this file except in compliance +# with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# Copyright 2004 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" + +include ../Makefile.com + +install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT) |