README revision 95154
1117259SacheThis directory contains the source files for libmilter.
2117259Sache
3117259SacheThe sendmail Mail Filter API (Milter) is designed to allow third-party
4117259Sacheprograms access to mail messages as they are being processed in order to
5117259Sachefilter meta-information and content.
6117259Sache
7117259SacheThis README file describes the steps needed to compile and run a filter,
8117259Sachethrough reference to a sample filter which is attached at the end of this
9117259Sachefile.  It is necessary to first build libmilter.a, which can be done by
10117259Sacheissuing the './Build' command in SRCDIR/libmilter .
11117259Sache
12117259SacheNOTE: If you intend to use filters in sendmail, you must compile sendmail
13117259Sachewith -DMILTER defined.  You can do this by adding the following to
14117259Sacheyour devtools/Site/site.config.m4 file:
15117259Sache
16117259Sache	APPENDDEF(`conf_sendmail_ENVDEF', `-DMILTER')
17117259Sache
18117259Sache+----------------+
19117259Sache| SECURITY HINTS |
20117259Sache+----------------+
21117259Sache
22117259SacheNote: we strongly recommend not to run any milter as root.  Libmilter
23117259Sachedoes not need root access to communicate with sendmail.  It is a
24117259Sachegood security practice to run a program only with root privileges
25117259Sacheif really necessary.  A milter should probably check first whether
26117259Sacheit runs as root and refuse to start in that case.  There is a
27117259Sachecompile time option _FFR_MILTER_ROOT_UNSAFE which keeps libmilter
28117259Sachefrom unlinking a socket when running as root.  It is recommended
29117259Sacheto turn on this option:
30117259Sache
31117259Sache	APPENDDEF(`conf_libmilter_ENVDEF', `-D_FFR_MILTER_ROOT_UNSAFE ')
32117259Sache
33117259Sache
34117259Sache+-------------------+
35117259Sache| BUILDING A FILTER |
36117259Sache+-------------------+
37117259Sache
38117259SacheThe following command presumes that the sample code from the end of this
39117259SacheREADME is saved to a file named 'sample.c' and built in the local platform-
40117259Sachespecific build subdirectory (SRCDIR/obj.*/libmilter).
41117259Sache
42117259Sache	cc -I../../sendmail -I../../include -o sample sample.c libmilter.a ../libsm/libsm.a -pthread
43117259Sache
44117259SacheIt is recommended that you build your filters in a location outside of
45117259Sachethe sendmail source tree.  Modify the compiler include references (-I)
46117259Sacheand the library locations accordingly.  Also, some operating systems may
47117259Sacherequire additional libraries.  For example, SunOS 5.X requires '-lresolv
48117259Sache-lsocket -lnsl'.  Depending on your operating system you may need a library
49117259Sacheinstead of the option -pthread, e.g., -lpthread.
50117259Sache
51117259SacheFilters must be thread-safe!  Many operating systems now provide support for
52117259SachePOSIX threads in the standard C libraries.  The compiler flag to link with
53117259Sachethreading support differs according to the compiler and linker used.  Check
54117259Sachethe Makefile in your appropriate obj.*/libmilter build subdirectory if you
55117259Sacheare unsure of the local flag used.
56117259Sache
57117259SacheNote that since filters use threads, it may be necessary to alter per
58117259Sacheprocess limits in your filter.  For example, you might look at using
59117259Sachesetrlimit() to increase the number of open file descriptors if your filter
60117259Sacheis going to be busy.
61117259Sache
62117259Sache
63117259Sache+----------------------------------------+
64117259Sache| SPECIFYING FILTERS IN SENDMAIL CONFIGS |
65117259Sache+----------------------------------------+
66117259Sache
67117259SacheFilters are specified with a key letter ``X'' (for ``eXternal'').
68117259Sache
69117259SacheFor example:
70117259Sache
71117259Sache	Xfilter1, S=local:/var/run/f1.sock, F=R
72117259Sache	Xfilter2, S=inet6:999@localhost, F=T, T=C:10m;S:1s;R:1s;E:5m
73117259Sache	Xfilter3, S=inet:3333@localhost
74117259Sache
75117259Sachespecifies three filters.  Filters can be specified in your .mc file using
76117259Sachethe following:
77117259Sache
78117259Sache	INPUT_MAIL_FILTER(`filter1', `S=local:/var/run/f1.sock, F=R')
79117259Sache	INPUT_MAIL_FILTER(`filter2', `S=inet6:999@localhost, F=T, T=C:10m;S:1s;R:1s;E:5m')
80117259Sache	INPUT_MAIL_FILTER(`filter3', `S=inet:3333@localhost')
81117259Sache
82117259SacheThe first attaches to a Unix-domain socket in the /var/run directory; the
83117259Sachesecond uses an IPv6 socket on port 999 of localhost, and the third uses an
84117259SacheIPv4 socket on port 3333 of localhost.  The current flags (F=) are:
85117259Sache
86117259Sache	R		Reject connection if filter unavailable
87117259Sache	T		Temporary fail connection if filter unavailable
88117259Sache
89117259SacheIf neither F=R nor F=T is specified, the message is passed through sendmail
90117259Sachein case of filter errors as if the failing filters were not present.
91117259Sache
92117259SacheFinally, you can override the default timeouts used by sendmail when
93117259Sachetalking to the filters using the T= equate.  There are four fields inside
94117259Sacheof the T= equate:
95117259Sache
96117259SacheLetter		Meaning
97117259Sache  C		Timeout for connecting to a filter (if 0, use system timeout)
98117259Sache  S		Timeout for sending information from the MTA to a filter
99117259Sache  R		Timeout for reading reply from the filter
100117259Sache  E		Overall timeout between sending end-of-message to filter
101117259Sache		and waiting for the final acknowledgment
102117259Sache
103117259SacheNote the separator between each is a ';' as a ',' already separates equates
104117259Sacheand therefore can't separate timeouts.  The default values (if not set in
105117259Sachethe config) are:
106117259Sache
107117259SacheT=C:5m;S:10s;R:10s;E:5m
108117259Sache
109117259Sachewhere 's' is seconds and 'm' is minutes.
110117259Sache
111117259SacheWhich filters are invoked and their sequencing is handled by the 
112117259SacheInputMailFilters option. Note: if InputMailFilters is not defined no filters
113117259Sachewill be used.
114117259Sache
115117259Sache	O InputMailFilters=filter1, filter2, filter3
116117259Sache
117117259SacheThis is is set automatically according to the order of the
118117259SacheINPUT_MAIL_FILTER commands in your .mc file.  Alternatively, you can
119117259Sachereset its value by setting confINPUT_MAIL_FILTERS in your .mc file.
120117259SacheThis options causes the three filters to be called in the same order
121117259Sachethey were specified.  It allows for possible future filtering on output
122117259Sache(although this is not intended for this release).
123117259Sache
124117259SacheAlso note that a filter can be defined without adding it to the input
125117259Sachefilter list by using MAIL_FILTER() instead of INPUT_MAIL_FILTER() in your
126117259Sache.mc file.
127117259Sache
128117259SacheTo test sendmail with the sample filter, the following might be added (in
129117259Sachethe appropriate locations) to your .mc file:
130117259Sache
131117259Sache	INPUT_MAIL_FILTER(`sample', `S=local:/var/run/f1.sock')
132117259Sache
133117259Sache
134117259Sache+------------------+
135117259Sache| TESTING A FILTER |
136117259Sache+------------------+
137117259Sache
138117259SacheOnce you have compiled a filter, modified your .mc file and restarted
139117259Sachethe sendmail process, you will want to test that the filter performs as
140117259Sacheintended.
141117259Sache
142117259SacheThe sample filter takes one argument -p, which indicates the local port
143117259Sacheon which to create a listening socket for the filter.  Maintaining
144117259Sacheconsistency with the suggested options for sendmail.cf, this would be the
145117259SacheUNIX domain socket located in /var/run/f1.sock.
146117259Sache
147117259Sache	% ./sample -p local:/var/run/f1.sock
148117259Sache
149117259SacheIf the sample filter returns immediately to a command line, there was either
150117259Sachean error with your command or a problem creating the specified socket.
151117259SacheFurther logging can be captured through the syslogd daemon.  Using the
152117259Sache'netstat -a' command can ensure that your filter process is listening on
153117259Sachethe appropriate local socket.
154117259Sache
155117259SacheEmail messages must be injected via SMTP to be filtered.  There are two
156117259Sachesimple means of doing this; either using the 'sendmail -bs' command, or
157117259Sacheby telnetting to port 25 of the machine configured for milter.  Once
158117259Sacheconnected via one of these options, the session can be continued through
159117259Sachethe use of standard SMTP commands.
160117259Sache
161117259Sache% sendmail -bs
162117259Sache220 test.sendmail.com ESMTP Sendmail 8.11.0/8.11.0; Tue, 10 Nov 1970 13:05:23 -0500 (EST)
163117259SacheHELO localhost
164117259Sache250 test.sendmail.com Hello testy@localhost, pleased to meet you
165117259SacheMAIL From:<testy>
166117259Sache250 2.1.0 <testy>... Sender ok
167117259SacheRCPT To:<root>
168117259Sache250 2.1.5 <root>... Recipient ok
169117259SacheDATA
170117259Sache354 Enter mail, end with "." on a line by itself
171117259SacheFrom: testy@test.sendmail.com
172117259SacheTo: root@test.sendmail.com
173117259SacheSubject: testing sample filter
174117259Sache
175117259SacheSample body
176117259Sache.
177117259Sache250 2.0.0 dB73Zxi25236 Message accepted for delivery
178117259SacheQUIT
179117259Sache221 2.0.0 test.sendmail.com closing connection
180117259Sache
181117259SacheIn the above example, the lines beginning with numbers are output by the
182117259Sachemail server, and those without are your input.  If everything is working
183117259Sacheproperly, you will find a file in /tmp by the name of msg.XXXXXXXX (where
184117259Sachethe Xs represent any combination of letters and numbers).  This file should
185117259Sachecontain the message body and headers from the test email entered above.
186117259Sache
187117259SacheIf the sample filter did not log your test email, there are a number of
188117259Sachemethods to narrow down the source of the problem.  Check your system
189117259Sachelogs written by syslogd and see if there are any pertinent lines.  You
190117259Sachemay need to reconfigure syslogd to capture all relevant data.  Additionally,
191117259Sachethe logging level of sendmail can be raised with the LogLevel option.
192117259SacheSee the sendmail(8) manual page for more information.
193117259Sache
194117259Sache
195117259Sache+--------------+
196117259Sache| REQUIREMENTS |
197117259Sache+--------------+
198117259Sache
199117259Sachelibmilter requires pthread support in the operating system.  Moreover, it
200117259Sacherequires that the library functions it uses are thread safe; which is true
201for the operating systems libmilter has been developed and tested on.  On
202some operating systems this requires special compile time options (e.g.,
203not just -pthread).  libmilter is currently known to work on (modulo problems
204in the pthread support of some specific versions):
205
206FreeBSD 3.x, 4.x
207SunOS 5.x (x >= 5)
208AIX 4.3.x
209HP UX 11.x
210Linux (recent versions/distributions)
211
212libmilter is currently not supported on:
213
214IRIX 6.x
215Ultrix
216
217Feedback about problems (and possible fixes) is welcome.
218
219+--------------------------+
220| SOURCE FOR SAMPLE FILTER |
221+--------------------------+
222
223Note that the filter below may not be thread safe on some operating
224systems.  You should check your system man pages for the functions used
225below to verify the functions are thread safe.
226
227/* A trivial filter that logs all email to a file. */
228
229#include <sys/types.h>
230#include <stdio.h>
231#include <stdlib.h>
232#include <string.h>
233#include <sysexits.h>
234#include <unistd.h>
235
236#include "libmilter/mfapi.h"
237
238#ifndef true
239typedef int bool;
240# define false	0
241# define true	1
242#endif /* ! true */
243
244struct mlfiPriv
245{
246	char	*mlfi_fname;
247	FILE	*mlfi_fp;
248};
249
250#define MLFIPRIV	((struct mlfiPriv *) smfi_getpriv(ctx))
251
252extern sfsistat	 mlfi_cleanup(SMFICTX *, bool);
253
254sfsistat
255mlfi_envfrom(ctx, envfrom)
256	SMFICTX *ctx;
257	char **envfrom;
258{
259	struct mlfiPriv *priv;
260	int fd = -1;
261
262	/* allocate some private memory */
263	priv = malloc(sizeof *priv);
264	if (priv == NULL)
265	{
266		/* can't accept this message right now */
267		return SMFIS_TEMPFAIL;
268	}
269	memset(priv, '\0', sizeof *priv);
270
271	/* open a file to store this message */
272	priv->mlfi_fname = strdup("/tmp/msg.XXXXXXXX");
273	if (priv->mlfi_fname == NULL)
274	{
275		free(priv);
276		return SMFIS_TEMPFAIL;
277	}
278	if ((fd = mkstemp(priv->mlfi_fname)) < 0 ||
279	    (priv->mlfi_fp = fdopen(fd, "w+")) == NULL)
280	{
281		if (fd >= 0)
282			(void) close(fd);
283		free(priv->mlfi_fname);
284		free(priv);
285		return SMFIS_TEMPFAIL;
286	}
287
288	/* save the private data */
289	smfi_setpriv(ctx, priv);
290
291	/* continue processing */
292	return SMFIS_CONTINUE;
293}
294
295sfsistat
296mlfi_header(ctx, headerf, headerv)
297	SMFICTX *ctx;
298	char *headerf;
299	char *headerv;
300{
301	/* write the header to the log file */
302	fprintf(MLFIPRIV->mlfi_fp, "%s: %s\r\n", headerf, headerv);
303
304	/* continue processing */
305	return SMFIS_CONTINUE;
306}
307
308sfsistat
309mlfi_eoh(ctx)
310	SMFICTX *ctx;
311{
312	/* output the blank line between the header and the body */
313	fprintf(MLFIPRIV->mlfi_fp, "\r\n");
314
315	/* continue processing */
316	return SMFIS_CONTINUE;
317}
318
319sfsistat
320mlfi_body(ctx, bodyp, bodylen)
321	SMFICTX *ctx;
322	u_char *bodyp;
323	size_t bodylen;
324{
325	/* output body block to log file */
326	if (fwrite(bodyp, bodylen, 1, MLFIPRIV->mlfi_fp) <= 0)
327	{
328		/* write failed */
329		(void) mlfi_cleanup(ctx, false);
330		return SMFIS_TEMPFAIL;
331	}
332
333	/* continue processing */
334	return SMFIS_CONTINUE;
335}
336
337sfsistat
338mlfi_eom(ctx)
339	SMFICTX *ctx;
340{
341	return mlfi_cleanup(ctx, true);
342}
343
344sfsistat
345mlfi_close(ctx)
346	SMFICTX *ctx;
347{
348	return SMFIS_ACCEPT;
349}
350
351sfsistat
352mlfi_abort(ctx)
353	SMFICTX *ctx;
354{
355	return mlfi_cleanup(ctx, false);
356}
357
358sfsistat
359mlfi_cleanup(ctx, ok)
360	SMFICTX *ctx;
361	bool ok;
362{
363	sfsistat rstat = SMFIS_CONTINUE;
364	struct mlfiPriv *priv = MLFIPRIV;
365	char *p;
366	char host[512];
367	char hbuf[1024];
368
369	if (priv == NULL)
370		return rstat;
371
372	/* close the archive file */
373	if (priv->mlfi_fp != NULL && fclose(priv->mlfi_fp) == EOF)
374	{
375		/* failed; we have to wait until later */
376		rstat = SMFIS_TEMPFAIL;
377		(void) unlink(priv->mlfi_fname);
378	}
379	else if (ok)
380	{
381		/* add a header to the message announcing our presence */
382		if (gethostname(host, sizeof host) < 0)
383			snprintf(host, sizeof host, "localhost");
384		p = strrchr(priv->mlfi_fname, '/');
385		if (p == NULL)
386			p = priv->mlfi_fname;
387		else
388			p++;
389		snprintf(hbuf, sizeof hbuf, "%s@%s", p, host);
390		smfi_addheader(ctx, "X-Archived", hbuf);
391	}
392	else
393	{
394		/* message was aborted -- delete the archive file */
395		(void) unlink(priv->mlfi_fname);
396	}
397
398	/* release private memory */
399	free(priv->mlfi_fname);
400	free(priv);
401	smfi_setpriv(ctx, NULL);
402
403	/* return status */
404	return rstat;
405}
406
407struct smfiDesc smfilter =
408{
409	"SampleFilter",	/* filter name */
410	SMFI_VERSION,	/* version code -- do not change */
411	SMFIF_ADDHDRS,	/* flags */
412	NULL,		/* connection info filter */
413	NULL,		/* SMTP HELO command filter */
414	mlfi_envfrom,	/* envelope sender filter */
415	NULL,		/* envelope recipient filter */
416	mlfi_header,	/* header filter */
417	mlfi_eoh,	/* end of header */
418	mlfi_body,	/* body block filter */
419	mlfi_eom,	/* end of message */
420	mlfi_abort,	/* message aborted */
421	mlfi_close	/* connection cleanup */
422};
423
424
425int
426main(argc, argv)
427	int argc;
428	char *argv[];
429{
430	int c;
431	const char *args = "p:";
432
433	/* Process command line options */
434	while ((c = getopt(argc, argv, args)) != -1)
435	{
436		switch (c)
437		{
438		  case 'p':
439			if (optarg == NULL || *optarg == '\0')
440			{
441				(void) fprintf(stderr, "Illegal conn: %s\n",
442					       optarg);
443				exit(EX_USAGE);
444			}
445			(void) smfi_setconn(optarg);
446			break;
447
448		}
449	}
450	if (smfi_register(smfilter) == MI_FAILURE)
451	{
452		fprintf(stderr, "smfi_register failed\n");
453		exit(EX_UNAVAILABLE);
454	}
455	return smfi_main();
456}
457
458/* eof */
459
460$Revision: 1.1.1.7 $, Last updated $Date: 2002/02/17 21:56:45 $
461