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