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