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