README revision 110560
164562SgshapiroThis directory contains the source files for libmilter. 264562Sgshapiro 364562SgshapiroThe sendmail Mail Filter API (Milter) is designed to allow third-party 464562Sgshapiroprograms access to mail messages as they are being processed in order to 564562Sgshapirofilter meta-information and content. 664562Sgshapiro 764562SgshapiroThis README file describes the steps needed to compile and run a filter, 864562Sgshapirothrough reference to a sample filter which is attached at the end of this 964562Sgshapirofile. It is necessary to first build libmilter.a, which can be done by 1064562Sgshapiroissuing the './Build' command in SRCDIR/libmilter . 1164562Sgshapiro 1290792SgshapiroNOTE: If you intend to use filters in sendmail, you must compile sendmail 1390792Sgshapirowith -DMILTER defined. You can do this by adding the following to 1490792Sgshapiroyour devtools/Site/site.config.m4 file: 1564562Sgshapiro 1690792Sgshapiro APPENDDEF(`conf_sendmail_ENVDEF', `-DMILTER') 1764562Sgshapiro 1890792Sgshapiro+----------------+ 1990792Sgshapiro| SECURITY HINTS | 2090792Sgshapiro+----------------+ 2164562Sgshapiro 2290792SgshapiroNote: we strongly recommend not to run any milter as root. Libmilter 2390792Sgshapirodoes not need root access to communicate with sendmail. It is a 2490792Sgshapirogood security practice to run a program only with root privileges 2590792Sgshapiroif really necessary. A milter should probably check first whether 2690792Sgshapiroit runs as root and refuse to start in that case. There is a 2790792Sgshapirocompile time option _FFR_MILTER_ROOT_UNSAFE which keeps libmilter 2890792Sgshapirofrom unlinking a socket when running as root. It is recommended 2990792Sgshapiroto turn on this option: 3090792Sgshapiro 3190792Sgshapiro APPENDDEF(`conf_libmilter_ENVDEF', `-D_FFR_MILTER_ROOT_UNSAFE ') 3290792Sgshapiro 3390792Sgshapiro 3464562Sgshapiro+-------------------+ 3564562Sgshapiro| BUILDING A FILTER | 3664562Sgshapiro+-------------------+ 3764562Sgshapiro 3864562SgshapiroThe following command presumes that the sample code from the end of this 3964562SgshapiroREADME is saved to a file named 'sample.c' and built in the local platform- 4064562Sgshapirospecific build subdirectory (SRCDIR/obj.*/libmilter). 4164562Sgshapiro 42110560Sgshapiro cc -I../../include -o sample sample.c libmilter.a ../libsm/libsm.a -pthread 4364562Sgshapiro 4464562SgshapiroIt is recommended that you build your filters in a location outside of 4564562Sgshapirothe sendmail source tree. Modify the compiler include references (-I) 4664562Sgshapiroand the library locations accordingly. Also, some operating systems may 4764562Sgshapirorequire additional libraries. For example, SunOS 5.X requires '-lresolv 4890792Sgshapiro-lsocket -lnsl'. Depending on your operating system you may need a library 4990792Sgshapiroinstead of the option -pthread, e.g., -lpthread. 5064562Sgshapiro 5164562SgshapiroFilters must be thread-safe! Many operating systems now provide support for 5264562SgshapiroPOSIX threads in the standard C libraries. The compiler flag to link with 5364562Sgshapirothreading support differs according to the compiler and linker used. Check 5464562Sgshapirothe Makefile in your appropriate obj.*/libmilter build subdirectory if you 5564562Sgshapiroare unsure of the local flag used. 5664562Sgshapiro 5773188SgshapiroNote that since filters use threads, it may be necessary to alter per 5873188Sgshapiroprocess limits in your filter. For example, you might look at using 5973188Sgshapirosetrlimit() to increase the number of open file descriptors if your filter 6073188Sgshapirois going to be busy. 6164562Sgshapiro 6273188Sgshapiro 6364562Sgshapiro+----------------------------------------+ 6464562Sgshapiro| SPECIFYING FILTERS IN SENDMAIL CONFIGS | 6564562Sgshapiro+----------------------------------------+ 6664562Sgshapiro 6764562SgshapiroFilters are specified with a key letter ``X'' (for ``eXternal''). 6864562Sgshapiro 6964562SgshapiroFor example: 7064562Sgshapiro 7164562Sgshapiro Xfilter1, S=local:/var/run/f1.sock, F=R 7280785Sgshapiro Xfilter2, S=inet6:999@localhost, F=T, T=C:10m;S:1s;R:1s;E:5m 7364562Sgshapiro Xfilter3, S=inet:3333@localhost 7464562Sgshapiro 7564562Sgshapirospecifies three filters. Filters can be specified in your .mc file using 7664562Sgshapirothe following: 7764562Sgshapiro 7864562Sgshapiro INPUT_MAIL_FILTER(`filter1', `S=local:/var/run/f1.sock, F=R') 7980785Sgshapiro INPUT_MAIL_FILTER(`filter2', `S=inet6:999@localhost, F=T, T=C:10m;S:1s;R:1s;E:5m') 8064562Sgshapiro INPUT_MAIL_FILTER(`filter3', `S=inet:3333@localhost') 8164562Sgshapiro 8264562SgshapiroThe first attaches to a Unix-domain socket in the /var/run directory; the 8364562Sgshapirosecond uses an IPv6 socket on port 999 of localhost, and the third uses an 8464562SgshapiroIPv4 socket on port 3333 of localhost. The current flags (F=) are: 8564562Sgshapiro 8664562Sgshapiro R Reject connection if filter unavailable 8764562Sgshapiro T Temporary fail connection if filter unavailable 8864562Sgshapiro 8966494SgshapiroIf neither F=R nor F=T is specified, the message is passed through sendmail 9090792Sgshapiroin case of filter errors as if the failing filters were not present. 9166494Sgshapiro 9264562SgshapiroFinally, you can override the default timeouts used by sendmail when 9380785Sgshapirotalking to the filters using the T= equate. There are four fields inside 9464562Sgshapiroof the T= equate: 9564562Sgshapiro 9690792SgshapiroLetter Meaning 9780785Sgshapiro C Timeout for connecting to a filter (if 0, use system timeout) 9880785Sgshapiro S Timeout for sending information from the MTA to a filter 9980785Sgshapiro R Timeout for reading reply from the filter 10080785Sgshapiro E Overall timeout between sending end-of-message to filter 10180785Sgshapiro and waiting for the final acknowledgment 10264562Sgshapiro 10364562SgshapiroNote the separator between each is a ';' as a ',' already separates equates 10480785Sgshapiroand therefore can't separate timeouts. The default values (if not set in 10580785Sgshapirothe config) are: 10664562Sgshapiro 10790792SgshapiroT=C:5m;S:10s;R:10s;E:5m 10864562Sgshapiro 10964562Sgshapirowhere 's' is seconds and 'm' is minutes. 11064562Sgshapiro 11166494SgshapiroWhich filters are invoked and their sequencing is handled by the 11277349SgshapiroInputMailFilters option. Note: if InputMailFilters is not defined no filters 11377349Sgshapirowill be used. 11464562Sgshapiro 11566494Sgshapiro O InputMailFilters=filter1, filter2, filter3 11666494Sgshapiro 11766494SgshapiroThis is is set automatically according to the order of the 11866494SgshapiroINPUT_MAIL_FILTER commands in your .mc file. Alternatively, you can 11966494Sgshapiroreset its value by setting confINPUT_MAIL_FILTERS in your .mc file. 12066494SgshapiroThis options causes the three filters to be called in the same order 12166494Sgshapirothey were specified. It allows for possible future filtering on output 12266494Sgshapiro(although this is not intended for this release). 12366494Sgshapiro 12464562SgshapiroAlso note that a filter can be defined without adding it to the input 12564562Sgshapirofilter list by using MAIL_FILTER() instead of INPUT_MAIL_FILTER() in your 12664562Sgshapiro.mc file. 12764562Sgshapiro 12864562SgshapiroTo test sendmail with the sample filter, the following might be added (in 12964562Sgshapirothe appropriate locations) to your .mc file: 13064562Sgshapiro 13164562Sgshapiro INPUT_MAIL_FILTER(`sample', `S=local:/var/run/f1.sock') 13264562Sgshapiro 13364562Sgshapiro 13464562Sgshapiro+------------------+ 13564562Sgshapiro| TESTING A FILTER | 13664562Sgshapiro+------------------+ 13764562Sgshapiro 13864562SgshapiroOnce you have compiled a filter, modified your .mc file and restarted 13964562Sgshapirothe sendmail process, you will want to test that the filter performs as 14064562Sgshapirointended. 14164562Sgshapiro 14264562SgshapiroThe sample filter takes one argument -p, which indicates the local port 14364562Sgshapiroon which to create a listening socket for the filter. Maintaining 14464562Sgshapiroconsistency with the suggested options for sendmail.cf, this would be the 14564562SgshapiroUNIX domain socket located in /var/run/f1.sock. 14664562Sgshapiro 14764562Sgshapiro % ./sample -p local:/var/run/f1.sock 14864562Sgshapiro 14964562SgshapiroIf the sample filter returns immediately to a command line, there was either 15064562Sgshapiroan error with your command or a problem creating the specified socket. 15164562SgshapiroFurther logging can be captured through the syslogd daemon. Using the 15264562Sgshapiro'netstat -a' command can ensure that your filter process is listening on 15364562Sgshapirothe appropriate local socket. 15464562Sgshapiro 15564562SgshapiroEmail messages must be injected via SMTP to be filtered. There are two 15664562Sgshapirosimple means of doing this; either using the 'sendmail -bs' command, or 15764562Sgshapiroby telnetting to port 25 of the machine configured for milter. Once 15864562Sgshapiroconnected via one of these options, the session can be continued through 15964562Sgshapirothe use of standard SMTP commands. 16064562Sgshapiro 16164562Sgshapiro% sendmail -bs 16266494Sgshapiro220 test.sendmail.com ESMTP Sendmail 8.11.0/8.11.0; Tue, 10 Nov 1970 13:05:23 -0500 (EST) 16364562SgshapiroHELO localhost 16464562Sgshapiro250 test.sendmail.com Hello testy@localhost, pleased to meet you 16564562SgshapiroMAIL From:<testy> 16664562Sgshapiro250 2.1.0 <testy>... Sender ok 16764562SgshapiroRCPT To:<root> 16864562Sgshapiro250 2.1.5 <root>... Recipient ok 16964562SgshapiroDATA 17064562Sgshapiro354 Enter mail, end with "." on a line by itself 17164562SgshapiroFrom: testy@test.sendmail.com 17264562SgshapiroTo: root@test.sendmail.com 17364562SgshapiroSubject: testing sample filter 17464562Sgshapiro 17564562SgshapiroSample body 17664562Sgshapiro. 17764562Sgshapiro250 2.0.0 dB73Zxi25236 Message accepted for delivery 17864562SgshapiroQUIT 17964562Sgshapiro221 2.0.0 test.sendmail.com closing connection 18064562Sgshapiro 18164562SgshapiroIn the above example, the lines beginning with numbers are output by the 18264562Sgshapiromail server, and those without are your input. If everything is working 18364562Sgshapiroproperly, you will find a file in /tmp by the name of msg.XXXXXXXX (where 18464562Sgshapirothe Xs represent any combination of letters and numbers). This file should 18564562Sgshapirocontain the message body and headers from the test email entered above. 18664562Sgshapiro 18764562SgshapiroIf the sample filter did not log your test email, there are a number of 18864562Sgshapiromethods to narrow down the source of the problem. Check your system 18964562Sgshapirologs written by syslogd and see if there are any pertinent lines. You 19064562Sgshapiromay need to reconfigure syslogd to capture all relevant data. Additionally, 19164562Sgshapirothe logging level of sendmail can be raised with the LogLevel option. 19264562SgshapiroSee the sendmail(8) manual page for more information. 19364562Sgshapiro 19464562Sgshapiro 19590792Sgshapiro+--------------+ 19690792Sgshapiro| REQUIREMENTS | 19790792Sgshapiro+--------------+ 19890792Sgshapiro 19990792Sgshapirolibmilter requires pthread support in the operating system. Moreover, it 20090792Sgshapirorequires that the library functions it uses are thread safe; which is true 20190792Sgshapirofor the operating systems libmilter has been developed and tested on. On 20290792Sgshapirosome operating systems this requires special compile time options (e.g., 20390792Sgshapironot just -pthread). libmilter is currently known to work on (modulo problems 20490792Sgshapiroin the pthread support of some specific versions): 20590792Sgshapiro 20690792SgshapiroFreeBSD 3.x, 4.x 20790792SgshapiroSunOS 5.x (x >= 5) 20890792SgshapiroAIX 4.3.x 20990792SgshapiroHP UX 11.x 21090792SgshapiroLinux (recent versions/distributions) 21190792Sgshapiro 21290792Sgshapirolibmilter is currently not supported on: 21390792Sgshapiro 21490792SgshapiroIRIX 6.x 21590792SgshapiroUltrix 21690792Sgshapiro 21790792SgshapiroFeedback about problems (and possible fixes) is welcome. 21890792Sgshapiro 21964562Sgshapiro+--------------------------+ 22064562Sgshapiro| SOURCE FOR SAMPLE FILTER | 22164562Sgshapiro+--------------------------+ 22264562Sgshapiro 22371345SgshapiroNote that the filter below may not be thread safe on some operating 22471345Sgshapirosystems. You should check your system man pages for the functions used 22571345Sgshapirobelow to verify the functions are thread safe. 22671345Sgshapiro 22764562Sgshapiro/* A trivial filter that logs all email to a file. */ 22864562Sgshapiro 22964562Sgshapiro#include <sys/types.h> 23064562Sgshapiro#include <stdio.h> 23164562Sgshapiro#include <stdlib.h> 23264562Sgshapiro#include <string.h> 23364562Sgshapiro#include <sysexits.h> 23464562Sgshapiro#include <unistd.h> 23564562Sgshapiro 23664562Sgshapiro#include "libmilter/mfapi.h" 23764562Sgshapiro 23890792Sgshapiro#ifndef true 23964562Sgshapirotypedef int bool; 24090792Sgshapiro# define false 0 24190792Sgshapiro# define true 1 24290792Sgshapiro#endif /* ! true */ 24364562Sgshapiro 24464562Sgshapirostruct mlfiPriv 24564562Sgshapiro{ 24664562Sgshapiro char *mlfi_fname; 24764562Sgshapiro FILE *mlfi_fp; 24864562Sgshapiro}; 24964562Sgshapiro 25064562Sgshapiro#define MLFIPRIV ((struct mlfiPriv *) smfi_getpriv(ctx)) 25164562Sgshapiro 25264562Sgshapiroextern sfsistat mlfi_cleanup(SMFICTX *, bool); 25364562Sgshapiro 25464562Sgshapirosfsistat 25564562Sgshapiromlfi_envfrom(ctx, envfrom) 25664562Sgshapiro SMFICTX *ctx; 25764562Sgshapiro char **envfrom; 25864562Sgshapiro{ 25964562Sgshapiro struct mlfiPriv *priv; 26077349Sgshapiro int fd = -1; 26164562Sgshapiro 26264562Sgshapiro /* allocate some private memory */ 26364562Sgshapiro priv = malloc(sizeof *priv); 26464562Sgshapiro if (priv == NULL) 26564562Sgshapiro { 26664562Sgshapiro /* can't accept this message right now */ 26764562Sgshapiro return SMFIS_TEMPFAIL; 26864562Sgshapiro } 26964562Sgshapiro memset(priv, '\0', sizeof *priv); 27064562Sgshapiro 27164562Sgshapiro /* open a file to store this message */ 27264562Sgshapiro priv->mlfi_fname = strdup("/tmp/msg.XXXXXXXX"); 27364562Sgshapiro if (priv->mlfi_fname == NULL) 27464562Sgshapiro { 27564562Sgshapiro free(priv); 27664562Sgshapiro return SMFIS_TEMPFAIL; 27764562Sgshapiro } 27864562Sgshapiro if ((fd = mkstemp(priv->mlfi_fname)) < 0 || 27964562Sgshapiro (priv->mlfi_fp = fdopen(fd, "w+")) == NULL) 28064562Sgshapiro { 28177349Sgshapiro if (fd >= 0) 28277349Sgshapiro (void) close(fd); 28364562Sgshapiro free(priv->mlfi_fname); 28464562Sgshapiro free(priv); 28564562Sgshapiro return SMFIS_TEMPFAIL; 28664562Sgshapiro } 28764562Sgshapiro 28864562Sgshapiro /* save the private data */ 28964562Sgshapiro smfi_setpriv(ctx, priv); 29064562Sgshapiro 29164562Sgshapiro /* continue processing */ 29264562Sgshapiro return SMFIS_CONTINUE; 29364562Sgshapiro} 29464562Sgshapiro 29564562Sgshapirosfsistat 29664562Sgshapiromlfi_header(ctx, headerf, headerv) 29764562Sgshapiro SMFICTX *ctx; 29864562Sgshapiro char *headerf; 29964562Sgshapiro char *headerv; 30064562Sgshapiro{ 30164562Sgshapiro /* write the header to the log file */ 30264562Sgshapiro fprintf(MLFIPRIV->mlfi_fp, "%s: %s\r\n", headerf, headerv); 30364562Sgshapiro 30464562Sgshapiro /* continue processing */ 30564562Sgshapiro return SMFIS_CONTINUE; 30664562Sgshapiro} 30764562Sgshapiro 30864562Sgshapirosfsistat 30964562Sgshapiromlfi_eoh(ctx) 31064562Sgshapiro SMFICTX *ctx; 31164562Sgshapiro{ 31264562Sgshapiro /* output the blank line between the header and the body */ 31364562Sgshapiro fprintf(MLFIPRIV->mlfi_fp, "\r\n"); 31464562Sgshapiro 31564562Sgshapiro /* continue processing */ 31664562Sgshapiro return SMFIS_CONTINUE; 31764562Sgshapiro} 31864562Sgshapiro 31964562Sgshapirosfsistat 32064562Sgshapiromlfi_body(ctx, bodyp, bodylen) 32164562Sgshapiro SMFICTX *ctx; 32264562Sgshapiro u_char *bodyp; 32364562Sgshapiro size_t bodylen; 32464562Sgshapiro{ 32564562Sgshapiro /* output body block to log file */ 32664562Sgshapiro if (fwrite(bodyp, bodylen, 1, MLFIPRIV->mlfi_fp) <= 0) 32764562Sgshapiro { 32864562Sgshapiro /* write failed */ 32990792Sgshapiro (void) mlfi_cleanup(ctx, false); 33064562Sgshapiro return SMFIS_TEMPFAIL; 33164562Sgshapiro } 33264562Sgshapiro 33364562Sgshapiro /* continue processing */ 33464562Sgshapiro return SMFIS_CONTINUE; 33564562Sgshapiro} 33664562Sgshapiro 33764562Sgshapirosfsistat 33864562Sgshapiromlfi_eom(ctx) 33964562Sgshapiro SMFICTX *ctx; 34064562Sgshapiro{ 34190792Sgshapiro return mlfi_cleanup(ctx, true); 34264562Sgshapiro} 34364562Sgshapiro 34464562Sgshapirosfsistat 34564562Sgshapiromlfi_close(ctx) 34664562Sgshapiro SMFICTX *ctx; 34764562Sgshapiro{ 34864562Sgshapiro return SMFIS_ACCEPT; 34964562Sgshapiro} 35064562Sgshapiro 35164562Sgshapirosfsistat 35264562Sgshapiromlfi_abort(ctx) 35364562Sgshapiro SMFICTX *ctx; 35464562Sgshapiro{ 35590792Sgshapiro return mlfi_cleanup(ctx, false); 35664562Sgshapiro} 35764562Sgshapiro 35864562Sgshapirosfsistat 35964562Sgshapiromlfi_cleanup(ctx, ok) 36064562Sgshapiro SMFICTX *ctx; 36164562Sgshapiro bool ok; 36264562Sgshapiro{ 36364562Sgshapiro sfsistat rstat = SMFIS_CONTINUE; 36464562Sgshapiro struct mlfiPriv *priv = MLFIPRIV; 36564562Sgshapiro char *p; 36664562Sgshapiro char host[512]; 36764562Sgshapiro char hbuf[1024]; 36864562Sgshapiro 36964562Sgshapiro if (priv == NULL) 37064562Sgshapiro return rstat; 37164562Sgshapiro 37264562Sgshapiro /* close the archive file */ 37364562Sgshapiro if (priv->mlfi_fp != NULL && fclose(priv->mlfi_fp) == EOF) 37464562Sgshapiro { 37564562Sgshapiro /* failed; we have to wait until later */ 37664562Sgshapiro rstat = SMFIS_TEMPFAIL; 37764562Sgshapiro (void) unlink(priv->mlfi_fname); 37864562Sgshapiro } 37964562Sgshapiro else if (ok) 38064562Sgshapiro { 38164562Sgshapiro /* add a header to the message announcing our presence */ 38264562Sgshapiro if (gethostname(host, sizeof host) < 0) 38390792Sgshapiro snprintf(host, sizeof host, "localhost"); 38464562Sgshapiro p = strrchr(priv->mlfi_fname, '/'); 38564562Sgshapiro if (p == NULL) 38664562Sgshapiro p = priv->mlfi_fname; 38764562Sgshapiro else 38864562Sgshapiro p++; 38964562Sgshapiro snprintf(hbuf, sizeof hbuf, "%s@%s", p, host); 39064562Sgshapiro smfi_addheader(ctx, "X-Archived", hbuf); 39164562Sgshapiro } 39264562Sgshapiro else 39364562Sgshapiro { 39464562Sgshapiro /* message was aborted -- delete the archive file */ 39564562Sgshapiro (void) unlink(priv->mlfi_fname); 39664562Sgshapiro } 39764562Sgshapiro 39864562Sgshapiro /* release private memory */ 39964562Sgshapiro free(priv->mlfi_fname); 40064562Sgshapiro free(priv); 40164562Sgshapiro smfi_setpriv(ctx, NULL); 40264562Sgshapiro 40364562Sgshapiro /* return status */ 40464562Sgshapiro return rstat; 40564562Sgshapiro} 40664562Sgshapiro 40764562Sgshapirostruct smfiDesc smfilter = 40864562Sgshapiro{ 40964562Sgshapiro "SampleFilter", /* filter name */ 41064562Sgshapiro SMFI_VERSION, /* version code -- do not change */ 41164562Sgshapiro SMFIF_ADDHDRS, /* flags */ 41264562Sgshapiro NULL, /* connection info filter */ 41364562Sgshapiro NULL, /* SMTP HELO command filter */ 41464562Sgshapiro mlfi_envfrom, /* envelope sender filter */ 41564562Sgshapiro NULL, /* envelope recipient filter */ 41664562Sgshapiro mlfi_header, /* header filter */ 41764562Sgshapiro mlfi_eoh, /* end of header */ 41864562Sgshapiro mlfi_body, /* body block filter */ 41964562Sgshapiro mlfi_eom, /* end of message */ 42064562Sgshapiro mlfi_abort, /* message aborted */ 42164562Sgshapiro mlfi_close /* connection cleanup */ 42264562Sgshapiro}; 42364562Sgshapiro 42464562Sgshapiro 42564562Sgshapiroint 42664562Sgshapiromain(argc, argv) 42764562Sgshapiro int argc; 42864562Sgshapiro char *argv[]; 42964562Sgshapiro{ 43064562Sgshapiro int c; 43164562Sgshapiro const char *args = "p:"; 43264562Sgshapiro 43364562Sgshapiro /* Process command line options */ 43464562Sgshapiro while ((c = getopt(argc, argv, args)) != -1) 43564562Sgshapiro { 43664562Sgshapiro switch (c) 43764562Sgshapiro { 43864562Sgshapiro case 'p': 43964562Sgshapiro if (optarg == NULL || *optarg == '\0') 44064562Sgshapiro { 44164562Sgshapiro (void) fprintf(stderr, "Illegal conn: %s\n", 44264562Sgshapiro optarg); 44364562Sgshapiro exit(EX_USAGE); 44464562Sgshapiro } 44564562Sgshapiro (void) smfi_setconn(optarg); 44664562Sgshapiro break; 44764562Sgshapiro 44864562Sgshapiro } 44964562Sgshapiro } 45064562Sgshapiro if (smfi_register(smfilter) == MI_FAILURE) 45164562Sgshapiro { 45264562Sgshapiro fprintf(stderr, "smfi_register failed\n"); 45364562Sgshapiro exit(EX_UNAVAILABLE); 45464562Sgshapiro } 45564562Sgshapiro return smfi_main(); 45664562Sgshapiro} 45764562Sgshapiro 45864562Sgshapiro/* eof */ 45964562Sgshapiro 460110560Sgshapiro$Revision: 8.35.2.1 $, Last updated $Date: 2002/10/21 14:31:57 $ 461