README revision 71345
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
181Note that the filter below may not be thread safe on some operating
182systems.  You should check your system man pages for the functions used
183below to verify the functions are thread safe.
184
185/* A trivial filter that logs all email to a file. */
186
187#include <sys/types.h>
188#include <stdio.h>
189#include <stdlib.h>
190#include <string.h>
191#include <sysexits.h>
192#include <unistd.h>
193
194#include "libmilter/mfapi.h"
195
196typedef int bool;
197
198#ifndef FALSE
199# define FALSE	0
200#endif /* ! FALSE*/
201#ifndef TRUE
202# define TRUE	1
203#endif /* ! TRUE*/
204
205struct mlfiPriv
206{
207	char	*mlfi_fname;
208	FILE	*mlfi_fp;
209};
210
211#define MLFIPRIV	((struct mlfiPriv *) smfi_getpriv(ctx))
212
213extern sfsistat	 mlfi_cleanup(SMFICTX *, bool);
214
215sfsistat
216mlfi_envfrom(ctx, envfrom)
217	SMFICTX *ctx;
218	char **envfrom;
219{
220	struct mlfiPriv *priv;
221	int fd;
222
223	/* allocate some private memory */
224	priv = malloc(sizeof *priv);
225	if (priv == NULL)
226	{
227		/* can't accept this message right now */
228		return SMFIS_TEMPFAIL;
229	}
230	memset(priv, '\0', sizeof *priv);
231
232	/* open a file to store this message */
233	priv->mlfi_fname = strdup("/tmp/msg.XXXXXXXX");
234	if (priv->mlfi_fname == NULL)
235	{
236		free(priv);
237		return SMFIS_TEMPFAIL;
238	}
239	if ((fd = mkstemp(priv->mlfi_fname)) < 0 ||
240	    (priv->mlfi_fp = fdopen(fd, "w+")) == NULL)
241	{
242		free(priv->mlfi_fname);
243		free(priv);
244		return SMFIS_TEMPFAIL;
245	}
246
247	/* save the private data */
248	smfi_setpriv(ctx, priv);
249
250	/* continue processing */
251	return SMFIS_CONTINUE;
252}
253
254sfsistat
255mlfi_header(ctx, headerf, headerv)
256	SMFICTX *ctx;
257	char *headerf;
258	char *headerv;
259{
260	/* write the header to the log file */
261	fprintf(MLFIPRIV->mlfi_fp, "%s: %s\r\n", headerf, headerv);
262
263	/* continue processing */
264	return SMFIS_CONTINUE;
265}
266
267sfsistat
268mlfi_eoh(ctx)
269	SMFICTX *ctx;
270{
271	/* output the blank line between the header and the body */
272	fprintf(MLFIPRIV->mlfi_fp, "\r\n");
273
274	/* continue processing */
275	return SMFIS_CONTINUE;
276}
277
278sfsistat
279mlfi_body(ctx, bodyp, bodylen)
280	SMFICTX *ctx;
281	u_char *bodyp;
282	size_t bodylen;
283{
284	/* output body block to log file */
285	if (fwrite(bodyp, bodylen, 1, MLFIPRIV->mlfi_fp) <= 0)
286	{
287		/* write failed */
288		(void) mlfi_cleanup(ctx, FALSE);
289		return SMFIS_TEMPFAIL;
290	}
291
292	/* continue processing */
293	return SMFIS_CONTINUE;
294}
295
296sfsistat
297mlfi_eom(ctx)
298	SMFICTX *ctx;
299{
300	return mlfi_cleanup(ctx, TRUE);
301}
302
303sfsistat
304mlfi_close(ctx)
305	SMFICTX *ctx;
306{
307	return SMFIS_ACCEPT;
308}
309
310sfsistat
311mlfi_abort(ctx)
312	SMFICTX *ctx;
313{
314	return mlfi_cleanup(ctx, FALSE);
315}
316
317sfsistat
318mlfi_cleanup(ctx, ok)
319	SMFICTX *ctx;
320	bool ok;
321{
322	sfsistat rstat = SMFIS_CONTINUE;
323	struct mlfiPriv *priv = MLFIPRIV;
324	char *p;
325	char host[512];
326	char hbuf[1024];
327
328	if (priv == NULL)
329		return rstat;
330
331	/* close the archive file */
332	if (priv->mlfi_fp != NULL && fclose(priv->mlfi_fp) == EOF)
333	{
334		/* failed; we have to wait until later */
335		rstat = SMFIS_TEMPFAIL;
336		(void) unlink(priv->mlfi_fname);
337	}
338	else if (ok)
339	{
340		/* add a header to the message announcing our presence */
341		if (gethostname(host, sizeof host) < 0)
342			strlcpy(host, "localhost", sizeof host);
343		p = strrchr(priv->mlfi_fname, '/');
344		if (p == NULL)
345			p = priv->mlfi_fname;
346		else
347			p++;
348		snprintf(hbuf, sizeof hbuf, "%s@%s", p, host);
349		smfi_addheader(ctx, "X-Archived", hbuf);
350	}
351	else
352	{
353		/* message was aborted -- delete the archive file */
354		(void) unlink(priv->mlfi_fname);
355	}
356
357	/* release private memory */
358	free(priv->mlfi_fname);
359	free(priv);
360	smfi_setpriv(ctx, NULL);
361
362	/* return status */
363	return rstat;
364}
365
366struct smfiDesc smfilter =
367{
368	"SampleFilter",	/* filter name */
369	SMFI_VERSION,	/* version code -- do not change */
370	SMFIF_ADDHDRS,	/* flags */
371	NULL,		/* connection info filter */
372	NULL,		/* SMTP HELO command filter */
373	mlfi_envfrom,	/* envelope sender filter */
374	NULL,		/* envelope recipient filter */
375	mlfi_header,	/* header filter */
376	mlfi_eoh,	/* end of header */
377	mlfi_body,	/* body block filter */
378	mlfi_eom,	/* end of message */
379	mlfi_abort,	/* message aborted */
380	mlfi_close	/* connection cleanup */
381};
382
383
384int
385main(argc, argv)
386	int argc;
387	char *argv[];
388{
389	int c;
390	const char *args = "p:";
391
392	/* Process command line options */
393	while ((c = getopt(argc, argv, args)) != -1)
394	{
395		switch (c)
396		{
397		  case 'p':
398			if (optarg == NULL || *optarg == '\0')
399			{
400				(void) fprintf(stderr, "Illegal conn: %s\n",
401					       optarg);
402				exit(EX_USAGE);
403			}
404			(void) smfi_setconn(optarg);
405			break;
406
407		}
408	}
409	if (smfi_register(smfilter) == MI_FAILURE)
410	{
411		fprintf(stderr, "smfi_register failed\n");
412		exit(EX_UNAVAILABLE);
413	}
414	return smfi_main();
415}
416
417/* eof */
418
419$Revision: 8.9.2.1.2.13 $, Last updated $Date: 2000/12/29 18:55:23 $
420