README revision 80785
190792SgshapiroThis directory contains the source files for libmilter.
290792Sgshapiro
390792SgshapiroThe sendmail Mail Filter API (Milter) is designed to allow third-party
490792Sgshapiroprograms access to mail messages as they are being processed in order to
590792Sgshapirofilter meta-information and content.
690792Sgshapiro
790792SgshapiroThis README file describes the steps needed to compile and run a filter,
890792Sgshapirothrough reference to a sample filter which is attached at the end of this
990792Sgshapirofile.  It is necessary to first build libmilter.a, which can be done by
1090792Sgshapiroissuing the './Build' command in SRCDIR/libmilter .
1190792Sgshapiro
1290792SgshapiroNOTE: Both libmilter and the callouts in sendmail are marked as an FFR (For
1390792SgshapiroFuture Release).  If you intend to use them in 8.11.X, you must compiled
1490792Sgshapiroboth libmilter and sendmail with -D_FFR_MILTER defined.  You can do this by
1590792Sgshapiroadding the following to your devtools/Site/site.config.m4 file:
1690792Sgshapiro
1790792Sgshapiro	dnl Milter
1890792Sgshapiro	APPENDDEF(`conf_sendmail_ENVDEF', `-D_FFR_MILTER=1')
1990792Sgshapiro	APPENDDEF(`conf_libmilter_ENVDEF', `-D_FFR_MILTER=1') 
2090792Sgshapiro
2190792SgshapiroYou will also need to define _FFR_MILTER when building your .cf file using
2290792Sgshapirom4.
23
24+-------------------+
25| BUILDING A FILTER |
26+-------------------+
27
28The following command presumes that the sample code from the end of this
29README is saved to a file named 'sample.c' and built in the local platform-
30specific build subdirectory (SRCDIR/obj.*/libmilter).
31
32	cc -I../../sendmail -I../../include -o sample sample.c libmilter.a ../libsmutil/libsmutil.a -pthread
33
34It is recommended that you build your filters in a location outside of
35the sendmail source tree.  Modify the compiler include references (-I)
36and the library locations accordingly.  Also, some operating systems may
37require additional libraries.  For example, SunOS 5.X requires '-lresolv
38-lsocket -lnsl'.  Depending on your OS you may need a library instead
39of the option -pthread, e.g., -lpthread.
40
41Filters must be thread-safe!  Many operating systems now provide support for
42POSIX threads in the standard C libraries.  The compiler flag to link with
43threading support differs according to the compiler and linker used.  Check
44the Makefile in your appropriate obj.*/libmilter build subdirectory if you
45are unsure of the local flag used.
46
47Note that since filters use threads, it may be necessary to alter per
48process limits in your filter.  For example, you might look at using
49setrlimit() to increase the number of open file descriptors if your filter
50is going to be busy.
51
52
53+----------------------------------------+
54| SPECIFYING FILTERS IN SENDMAIL CONFIGS |
55+----------------------------------------+
56
57Filters are specified with a key letter ``X'' (for ``eXternal'').
58
59For example:
60
61	Xfilter1, S=local:/var/run/f1.sock, F=R
62	Xfilter2, S=inet6:999@localhost, F=T, T=C:10m;S:1s;R:1s;E:5m
63	Xfilter3, S=inet:3333@localhost
64
65specifies three filters.  Filters can be specified in your .mc file using
66the following:
67
68	INPUT_MAIL_FILTER(`filter1', `S=local:/var/run/f1.sock, F=R')
69	INPUT_MAIL_FILTER(`filter2', `S=inet6:999@localhost, F=T, T=C:10m;S:1s;R:1s;E:5m')
70	INPUT_MAIL_FILTER(`filter3', `S=inet:3333@localhost')
71
72The first attaches to a Unix-domain socket in the /var/run directory; the
73second uses an IPv6 socket on port 999 of localhost, and the third uses an
74IPv4 socket on port 3333 of localhost.  The current flags (F=) are:
75
76	R		Reject connection if filter unavailable
77	T		Temporary fail connection if filter unavailable
78
79If neither F=R nor F=T is specified, the message is passed through sendmail
80as if the filter were not present.
81
82Finally, you can override the default timeouts used by sendmail when
83talking to the filters using the T= equate.  There are four fields inside
84of the T= equate:
85
86Letter          Meaning
87  C		Timeout for connecting to a filter (if 0, use system timeout)
88  S		Timeout for sending information from the MTA to a filter
89  R		Timeout for reading reply from the filter
90  E		Overall timeout between sending end-of-message to filter
91		and waiting for the final acknowledgment
92
93Note the separator between each is a ';' as a ',' already separates equates
94and therefore can't separate timeouts.  The default values (if not set in
95the config) are:
96
97T=C:0m;S:10s;R:10s;E:5m
98
99where 's' is seconds and 'm' is minutes.
100
101Which filters are invoked and their sequencing is handled by the 
102InputMailFilters option. Note: if InputMailFilters is not defined no filters
103will be used.
104
105	O InputMailFilters=filter1, filter2, filter3
106
107This is is set automatically according to the order of the
108INPUT_MAIL_FILTER commands in your .mc file.  Alternatively, you can
109reset its value by setting confINPUT_MAIL_FILTERS in your .mc file.
110This options causes the three filters to be called in the same order
111they were specified.  It allows for possible future filtering on output
112(although this is not intended for this release).
113
114Also note that a filter can be defined without adding it to the input
115filter list by using MAIL_FILTER() instead of INPUT_MAIL_FILTER() in your
116.mc file.
117
118To test sendmail with the sample filter, the following might be added (in
119the appropriate locations) to your .mc file:
120
121	INPUT_MAIL_FILTER(`sample', `S=local:/var/run/f1.sock')
122
123
124+------------------+
125| TESTING A FILTER |
126+------------------+
127
128Once you have compiled a filter, modified your .mc file and restarted
129the sendmail process, you will want to test that the filter performs as
130intended.
131
132The sample filter takes one argument -p, which indicates the local port
133on which to create a listening socket for the filter.  Maintaining
134consistency with the suggested options for sendmail.cf, this would be the
135UNIX domain socket located in /var/run/f1.sock.
136
137	% ./sample -p local:/var/run/f1.sock
138
139If the sample filter returns immediately to a command line, there was either
140an error with your command or a problem creating the specified socket.
141Further logging can be captured through the syslogd daemon.  Using the
142'netstat -a' command can ensure that your filter process is listening on
143the appropriate local socket.
144
145Email messages must be injected via SMTP to be filtered.  There are two
146simple means of doing this; either using the 'sendmail -bs' command, or
147by telnetting to port 25 of the machine configured for milter.  Once
148connected via one of these options, the session can be continued through
149the use of standard SMTP commands.
150
151% sendmail -bs
152220 test.sendmail.com ESMTP Sendmail 8.11.0/8.11.0; Tue, 10 Nov 1970 13:05:23 -0500 (EST)
153HELO localhost
154250 test.sendmail.com Hello testy@localhost, pleased to meet you
155MAIL From:<testy>
156250 2.1.0 <testy>... Sender ok
157RCPT To:<root>
158250 2.1.5 <root>... Recipient ok
159DATA
160354 Enter mail, end with "." on a line by itself
161From: testy@test.sendmail.com
162To: root@test.sendmail.com
163Subject: testing sample filter
164
165Sample body
166.
167250 2.0.0 dB73Zxi25236 Message accepted for delivery
168QUIT
169221 2.0.0 test.sendmail.com closing connection
170
171In the above example, the lines beginning with numbers are output by the
172mail server, and those without are your input.  If everything is working
173properly, you will find a file in /tmp by the name of msg.XXXXXXXX (where
174the Xs represent any combination of letters and numbers).  This file should
175contain the message body and headers from the test email entered above.
176
177If the sample filter did not log your test email, there are a number of
178methods to narrow down the source of the problem.  Check your system
179logs written by syslogd and see if there are any pertinent lines.  You
180may need to reconfigure syslogd to capture all relevant data.  Additionally,
181the logging level of sendmail can be raised with the LogLevel option.
182See the sendmail(8) manual page for more information.
183
184
185+--------------------------+
186| SOURCE FOR SAMPLE FILTER |
187+--------------------------+
188
189Note that the filter below may not be thread safe on some operating
190systems.  You should check your system man pages for the functions used
191below to verify the functions are thread safe.
192
193/* A trivial filter that logs all email to a file. */
194
195#include <sys/types.h>
196#include <stdio.h>
197#include <stdlib.h>
198#include <string.h>
199#include <sysexits.h>
200#include <unistd.h>
201
202#include "libmilter/mfapi.h"
203
204typedef int bool;
205
206#ifndef FALSE
207# define FALSE	0
208#endif /* ! FALSE*/
209#ifndef TRUE
210# define TRUE	1
211#endif /* ! TRUE*/
212
213struct mlfiPriv
214{
215	char	*mlfi_fname;
216	FILE	*mlfi_fp;
217};
218
219#define MLFIPRIV	((struct mlfiPriv *) smfi_getpriv(ctx))
220
221extern sfsistat	 mlfi_cleanup(SMFICTX *, bool);
222
223sfsistat
224mlfi_envfrom(ctx, envfrom)
225	SMFICTX *ctx;
226	char **envfrom;
227{
228	struct mlfiPriv *priv;
229	int fd = -1;
230
231	/* allocate some private memory */
232	priv = malloc(sizeof *priv);
233	if (priv == NULL)
234	{
235		/* can't accept this message right now */
236		return SMFIS_TEMPFAIL;
237	}
238	memset(priv, '\0', sizeof *priv);
239
240	/* open a file to store this message */
241	priv->mlfi_fname = strdup("/tmp/msg.XXXXXXXX");
242	if (priv->mlfi_fname == NULL)
243	{
244		free(priv);
245		return SMFIS_TEMPFAIL;
246	}
247	if ((fd = mkstemp(priv->mlfi_fname)) < 0 ||
248	    (priv->mlfi_fp = fdopen(fd, "w+")) == NULL)
249	{
250		if (fd >= 0)
251			(void) close(fd);
252		free(priv->mlfi_fname);
253		free(priv);
254		return SMFIS_TEMPFAIL;
255	}
256
257	/* save the private data */
258	smfi_setpriv(ctx, priv);
259
260	/* continue processing */
261	return SMFIS_CONTINUE;
262}
263
264sfsistat
265mlfi_header(ctx, headerf, headerv)
266	SMFICTX *ctx;
267	char *headerf;
268	char *headerv;
269{
270	/* write the header to the log file */
271	fprintf(MLFIPRIV->mlfi_fp, "%s: %s\r\n", headerf, headerv);
272
273	/* continue processing */
274	return SMFIS_CONTINUE;
275}
276
277sfsistat
278mlfi_eoh(ctx)
279	SMFICTX *ctx;
280{
281	/* output the blank line between the header and the body */
282	fprintf(MLFIPRIV->mlfi_fp, "\r\n");
283
284	/* continue processing */
285	return SMFIS_CONTINUE;
286}
287
288sfsistat
289mlfi_body(ctx, bodyp, bodylen)
290	SMFICTX *ctx;
291	u_char *bodyp;
292	size_t bodylen;
293{
294	/* output body block to log file */
295	if (fwrite(bodyp, bodylen, 1, MLFIPRIV->mlfi_fp) <= 0)
296	{
297		/* write failed */
298		(void) mlfi_cleanup(ctx, FALSE);
299		return SMFIS_TEMPFAIL;
300	}
301
302	/* continue processing */
303	return SMFIS_CONTINUE;
304}
305
306sfsistat
307mlfi_eom(ctx)
308	SMFICTX *ctx;
309{
310	return mlfi_cleanup(ctx, TRUE);
311}
312
313sfsistat
314mlfi_close(ctx)
315	SMFICTX *ctx;
316{
317	return SMFIS_ACCEPT;
318}
319
320sfsistat
321mlfi_abort(ctx)
322	SMFICTX *ctx;
323{
324	return mlfi_cleanup(ctx, FALSE);
325}
326
327sfsistat
328mlfi_cleanup(ctx, ok)
329	SMFICTX *ctx;
330	bool ok;
331{
332	sfsistat rstat = SMFIS_CONTINUE;
333	struct mlfiPriv *priv = MLFIPRIV;
334	char *p;
335	char host[512];
336	char hbuf[1024];
337
338	if (priv == NULL)
339		return rstat;
340
341	/* close the archive file */
342	if (priv->mlfi_fp != NULL && fclose(priv->mlfi_fp) == EOF)
343	{
344		/* failed; we have to wait until later */
345		rstat = SMFIS_TEMPFAIL;
346		(void) unlink(priv->mlfi_fname);
347	}
348	else if (ok)
349	{
350		/* add a header to the message announcing our presence */
351		if (gethostname(host, sizeof host) < 0)
352			strlcpy(host, "localhost", sizeof host);
353		p = strrchr(priv->mlfi_fname, '/');
354		if (p == NULL)
355			p = priv->mlfi_fname;
356		else
357			p++;
358		snprintf(hbuf, sizeof hbuf, "%s@%s", p, host);
359		smfi_addheader(ctx, "X-Archived", hbuf);
360	}
361	else
362	{
363		/* message was aborted -- delete the archive file */
364		(void) unlink(priv->mlfi_fname);
365	}
366
367	/* release private memory */
368	free(priv->mlfi_fname);
369	free(priv);
370	smfi_setpriv(ctx, NULL);
371
372	/* return status */
373	return rstat;
374}
375
376struct smfiDesc smfilter =
377{
378	"SampleFilter",	/* filter name */
379	SMFI_VERSION,	/* version code -- do not change */
380	SMFIF_ADDHDRS,	/* flags */
381	NULL,		/* connection info filter */
382	NULL,		/* SMTP HELO command filter */
383	mlfi_envfrom,	/* envelope sender filter */
384	NULL,		/* envelope recipient filter */
385	mlfi_header,	/* header filter */
386	mlfi_eoh,	/* end of header */
387	mlfi_body,	/* body block filter */
388	mlfi_eom,	/* end of message */
389	mlfi_abort,	/* message aborted */
390	mlfi_close	/* connection cleanup */
391};
392
393
394int
395main(argc, argv)
396	int argc;
397	char *argv[];
398{
399	int c;
400	const char *args = "p:";
401
402	/* Process command line options */
403	while ((c = getopt(argc, argv, args)) != -1)
404	{
405		switch (c)
406		{
407		  case 'p':
408			if (optarg == NULL || *optarg == '\0')
409			{
410				(void) fprintf(stderr, "Illegal conn: %s\n",
411					       optarg);
412				exit(EX_USAGE);
413			}
414			(void) smfi_setconn(optarg);
415			break;
416
417		}
418	}
419	if (smfi_register(smfilter) == MI_FAILURE)
420	{
421		fprintf(stderr, "smfi_register failed\n");
422		exit(EX_UNAVAILABLE);
423	}
424	return smfi_main();
425}
426
427/* eof */
428
429$Revision: 8.9.2.1.2.19 $, Last updated $Date: 2001/06/28 22:25:14 $
430