README revision 73188
1This directory contains the source files for libmilter.
2
3The sendmail Mail Filter API (Milter) is designed to allow third-party
4programs access to mail messages as they are being processed in order to
5filter meta-information and content.
6
7This README file describes the steps needed to compile and run a filter,
8through reference to a sample filter which is attached at the end of this
9file.  It is necessary to first build libmilter.a, which can be done by
10issuing the './Build' command in SRCDIR/libmilter .
11
12NOTE: Both libmilter and the callouts in sendmail are marked as an FFR (For
13Future Release).  If you intend to use them in 8.10.X, you must compiled
14both libmilter and sendmail with -D_FFR_MILTER defined.  You can do this by
15adding the following to your devtools/Site/site.config.m4 file:
16
17	dnl Milter
18	APPENDDEF(`conf_sendmail_ENVDEF', `-D_FFR_MILTER=1')
19	APPENDDEF(`conf_libmilter_ENVDEF', `-D_FFR_MILTER=1') 
20
21You will also need to define _FFR_MILTER when building your .cf file using
22m4.
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=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=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 three fields inside
84of the T= equate:
85
86Letter          Meaning
87  S             Timeout for sending information from the MTA to a filter
88  R             Timeout for reading reply from the filter
89  E             Overall timeout between sending end-of-message to filter
90                and waiting for the final acknowledgment
91
92Note the separator between each is a ';' as a ',' already separates equates
93and therefore can't separate timeouts.  The default values (if not set in the config) are:
94
95T=S:10s;R:10s;E:5m
96
97where 's' is seconds and 'm' is minutes.
98
99Which filters are invoked and their sequencing is handled by the 
100InputMailFilters option.
101
102	O InputMailFilters=filter1, filter2, filter3
103
104This is is set automatically according to the order of the
105INPUT_MAIL_FILTER commands in your .mc file.  Alternatively, you can
106reset its value by setting confINPUT_MAIL_FILTERS in your .mc file.
107This options causes the three filters to be called in the same order
108they were specified.  It allows for possible future filtering on output
109(although this is not intended for this release).
110
111Also note that a filter can be defined without adding it to the input
112filter list by using MAIL_FILTER() instead of INPUT_MAIL_FILTER() in your
113.mc file.
114
115To test sendmail with the sample filter, the following might be added (in
116the appropriate locations) to your .mc file:
117
118	INPUT_MAIL_FILTER(`sample', `S=local:/var/run/f1.sock')
119
120
121+------------------+
122| TESTING A FILTER |
123+------------------+
124
125Once you have compiled a filter, modified your .mc file and restarted
126the sendmail process, you will want to test that the filter performs as
127intended.
128
129The sample filter takes one argument -p, which indicates the local port
130on which to create a listening socket for the filter.  Maintaining
131consistency with the suggested options for sendmail.cf, this would be the
132UNIX domain socket located in /var/run/f1.sock.
133
134	% ./sample -p local:/var/run/f1.sock
135
136If the sample filter returns immediately to a command line, there was either
137an error with your command or a problem creating the specified socket.
138Further logging can be captured through the syslogd daemon.  Using the
139'netstat -a' command can ensure that your filter process is listening on
140the appropriate local socket.
141
142Email messages must be injected via SMTP to be filtered.  There are two
143simple means of doing this; either using the 'sendmail -bs' command, or
144by telnetting to port 25 of the machine configured for milter.  Once
145connected via one of these options, the session can be continued through
146the use of standard SMTP commands.
147
148% sendmail -bs
149220 test.sendmail.com ESMTP Sendmail 8.11.0/8.11.0; Tue, 10 Nov 1970 13:05:23 -0500 (EST)
150HELO localhost
151250 test.sendmail.com Hello testy@localhost, pleased to meet you
152MAIL From:<testy>
153250 2.1.0 <testy>... Sender ok
154RCPT To:<root>
155250 2.1.5 <root>... Recipient ok
156DATA
157354 Enter mail, end with "." on a line by itself
158From: testy@test.sendmail.com
159To: root@test.sendmail.com
160Subject: testing sample filter
161
162Sample body
163.
164250 2.0.0 dB73Zxi25236 Message accepted for delivery
165QUIT
166221 2.0.0 test.sendmail.com closing connection
167
168In the above example, the lines beginning with numbers are output by the
169mail server, and those without are your input.  If everything is working
170properly, you will find a file in /tmp by the name of msg.XXXXXXXX (where
171the Xs represent any combination of letters and numbers).  This file should
172contain the message body and headers from the test email entered above.
173
174If the sample filter did not log your test email, there are a number of
175methods to narrow down the source of the problem.  Check your system
176logs written by syslogd and see if there are any pertinent lines.  You
177may need to reconfigure syslogd to capture all relevant data.  Additionally,
178the logging level of sendmail can be raised with the LogLevel option.
179See the sendmail(8) manual page for more information.
180
181
182+--------------------------+
183| SOURCE FOR SAMPLE FILTER |
184+--------------------------+
185
186Note that the filter below may not be thread safe on some operating
187systems.  You should check your system man pages for the functions used
188below to verify the functions are thread safe.
189
190/* A trivial filter that logs all email to a file. */
191
192#include <sys/types.h>
193#include <stdio.h>
194#include <stdlib.h>
195#include <string.h>
196#include <sysexits.h>
197#include <unistd.h>
198
199#include "libmilter/mfapi.h"
200
201typedef int bool;
202
203#ifndef FALSE
204# define FALSE	0
205#endif /* ! FALSE*/
206#ifndef TRUE
207# define TRUE	1
208#endif /* ! TRUE*/
209
210struct mlfiPriv
211{
212	char	*mlfi_fname;
213	FILE	*mlfi_fp;
214};
215
216#define MLFIPRIV	((struct mlfiPriv *) smfi_getpriv(ctx))
217
218extern sfsistat	 mlfi_cleanup(SMFICTX *, bool);
219
220sfsistat
221mlfi_envfrom(ctx, envfrom)
222	SMFICTX *ctx;
223	char **envfrom;
224{
225	struct mlfiPriv *priv;
226	int fd;
227
228	/* allocate some private memory */
229	priv = malloc(sizeof *priv);
230	if (priv == NULL)
231	{
232		/* can't accept this message right now */
233		return SMFIS_TEMPFAIL;
234	}
235	memset(priv, '\0', sizeof *priv);
236
237	/* open a file to store this message */
238	priv->mlfi_fname = strdup("/tmp/msg.XXXXXXXX");
239	if (priv->mlfi_fname == NULL)
240	{
241		free(priv);
242		return SMFIS_TEMPFAIL;
243	}
244	if ((fd = mkstemp(priv->mlfi_fname)) < 0 ||
245	    (priv->mlfi_fp = fdopen(fd, "w+")) == NULL)
246	{
247		free(priv->mlfi_fname);
248		free(priv);
249		return SMFIS_TEMPFAIL;
250	}
251
252	/* save the private data */
253	smfi_setpriv(ctx, priv);
254
255	/* continue processing */
256	return SMFIS_CONTINUE;
257}
258
259sfsistat
260mlfi_header(ctx, headerf, headerv)
261	SMFICTX *ctx;
262	char *headerf;
263	char *headerv;
264{
265	/* write the header to the log file */
266	fprintf(MLFIPRIV->mlfi_fp, "%s: %s\r\n", headerf, headerv);
267
268	/* continue processing */
269	return SMFIS_CONTINUE;
270}
271
272sfsistat
273mlfi_eoh(ctx)
274	SMFICTX *ctx;
275{
276	/* output the blank line between the header and the body */
277	fprintf(MLFIPRIV->mlfi_fp, "\r\n");
278
279	/* continue processing */
280	return SMFIS_CONTINUE;
281}
282
283sfsistat
284mlfi_body(ctx, bodyp, bodylen)
285	SMFICTX *ctx;
286	u_char *bodyp;
287	size_t bodylen;
288{
289	/* output body block to log file */
290	if (fwrite(bodyp, bodylen, 1, MLFIPRIV->mlfi_fp) <= 0)
291	{
292		/* write failed */
293		(void) mlfi_cleanup(ctx, FALSE);
294		return SMFIS_TEMPFAIL;
295	}
296
297	/* continue processing */
298	return SMFIS_CONTINUE;
299}
300
301sfsistat
302mlfi_eom(ctx)
303	SMFICTX *ctx;
304{
305	return mlfi_cleanup(ctx, TRUE);
306}
307
308sfsistat
309mlfi_close(ctx)
310	SMFICTX *ctx;
311{
312	return SMFIS_ACCEPT;
313}
314
315sfsistat
316mlfi_abort(ctx)
317	SMFICTX *ctx;
318{
319	return mlfi_cleanup(ctx, FALSE);
320}
321
322sfsistat
323mlfi_cleanup(ctx, ok)
324	SMFICTX *ctx;
325	bool ok;
326{
327	sfsistat rstat = SMFIS_CONTINUE;
328	struct mlfiPriv *priv = MLFIPRIV;
329	char *p;
330	char host[512];
331	char hbuf[1024];
332
333	if (priv == NULL)
334		return rstat;
335
336	/* close the archive file */
337	if (priv->mlfi_fp != NULL && fclose(priv->mlfi_fp) == EOF)
338	{
339		/* failed; we have to wait until later */
340		rstat = SMFIS_TEMPFAIL;
341		(void) unlink(priv->mlfi_fname);
342	}
343	else if (ok)
344	{
345		/* add a header to the message announcing our presence */
346		if (gethostname(host, sizeof host) < 0)
347			strlcpy(host, "localhost", sizeof host);
348		p = strrchr(priv->mlfi_fname, '/');
349		if (p == NULL)
350			p = priv->mlfi_fname;
351		else
352			p++;
353		snprintf(hbuf, sizeof hbuf, "%s@%s", p, host);
354		smfi_addheader(ctx, "X-Archived", hbuf);
355	}
356	else
357	{
358		/* message was aborted -- delete the archive file */
359		(void) unlink(priv->mlfi_fname);
360	}
361
362	/* release private memory */
363	free(priv->mlfi_fname);
364	free(priv);
365	smfi_setpriv(ctx, NULL);
366
367	/* return status */
368	return rstat;
369}
370
371struct smfiDesc smfilter =
372{
373	"SampleFilter",	/* filter name */
374	SMFI_VERSION,	/* version code -- do not change */
375	SMFIF_ADDHDRS,	/* flags */
376	NULL,		/* connection info filter */
377	NULL,		/* SMTP HELO command filter */
378	mlfi_envfrom,	/* envelope sender filter */
379	NULL,		/* envelope recipient filter */
380	mlfi_header,	/* header filter */
381	mlfi_eoh,	/* end of header */
382	mlfi_body,	/* body block filter */
383	mlfi_eom,	/* end of message */
384	mlfi_abort,	/* message aborted */
385	mlfi_close	/* connection cleanup */
386};
387
388
389int
390main(argc, argv)
391	int argc;
392	char *argv[];
393{
394	int c;
395	const char *args = "p:";
396
397	/* Process command line options */
398	while ((c = getopt(argc, argv, args)) != -1)
399	{
400		switch (c)
401		{
402		  case 'p':
403			if (optarg == NULL || *optarg == '\0')
404			{
405				(void) fprintf(stderr, "Illegal conn: %s\n",
406					       optarg);
407				exit(EX_USAGE);
408			}
409			(void) smfi_setconn(optarg);
410			break;
411
412		}
413	}
414	if (smfi_register(smfilter) == MI_FAILURE)
415	{
416		fprintf(stderr, "smfi_register failed\n");
417		exit(EX_UNAVAILABLE);
418	}
419	return smfi_main();
420}
421
422/* eof */
423
424$Revision: 8.9.2.1.2.14 $, Last updated $Date: 2001/02/21 23:08:19 $
425