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