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