README revision 64562
1127342SmlaierThis directory contains the source files for libmilter.
2127342Smlaier
3127342SmlaierThe sendmail Mail Filter API (Milter) is designed to allow third-party
4127342Smlaierprograms access to mail messages as they are being processed in order to
5127342Smlaierfilter meta-information and content.
6127342Smlaier
7195026SdougbThis README file describes the steps needed to compile and run a filter,
8150836Syarthrough reference to a sample filter which is attached at the end of this
9136224Smtmfile.  It is necessary to first build libmilter.a, which can be done by
10127342Smlaierissuing the './Build' command in SRCDIR/libmilter .
11127342Smlaier
12127342SmlaierNOTE: Both libmilter and the callouts in sendmail are marked as an FFR (For
13127342SmlaierFuture Release).  If you intend to use them in 8.10.X, you must compiled
14230099Sdougbboth libmilter and sendmail with -D_FFR_MILTER defined.  You can do this by
15127342Smlaieradding the following to your devtools/Site/site.config.m4 file:
16127342Smlaier
17127342Smlaier	dnl Milter
18136942Spjd	APPENDDEF(`conf_sendmail_ENVDEF', `-D_FFR_MILTER=1')
19127342Smlaier	APPENDDEF(`conf_libmilter_ENVDEF', `-D_FFR_MILTER=1') 
20127342Smlaier
21127342SmlaierYou will also need to define _FFR_MILTER when building your .cf file using
22222007Shrsm4.
23150839Syar
24165683Syar+-------------------+
25127342Smlaier| BUILDING A FILTER |
26127342Smlaier+-------------------+
27127342Smlaier
28197947SdougbThe following command presumes that the sample code from the end of this
29159243SobrienREADME is saved to a file named 'sample.c' and built in the local platform-
30150839Syarspecific build subdirectory (SRCDIR/obj.*/libmilter).
31150839Syar
32216499Skevlo	cc -I../../sendmail -I../../include -o sample sample.c libmilter.a ../libsmutil/libsmutil.a -pthread
33130954Smlaier
34197947SdougbIt is recommended that you build your filters in a location outside of
35127342Smlaierthe sendmail source tree.  Modify the compiler include references (-I)
36127342Smlaierand the library locations accordingly.  Also, some operating systems may
37127342Smlaierrequire additional libraries.  For example, SunOS 5.X requires '-lresolv
38127342Smlaier-lsocket -lnsl'.  Depending on your OS you may need a library instead
39150839Syarof the option -pthread, e.g., -lpthread.
40197947Sdougb
41216499SkevloFilters must be thread-safe!  Many operating systems now provide support for
42197947SdougbPOSIX threads in the standard C libraries.  The compiler flag to link with
43127342Smlaierthreading support differs according to the compiler and linker used.  Check
44127342Smlaierthe Makefile in your appropriate obj.*/libmilter build subdirectory if you
45127342Smlaierare unsure of the local flag used.
46136942Spjd
47136942Spjd
48136942Spjd+----------------------------------------+
49150839Syar| SPECIFYING FILTERS IN SENDMAIL CONFIGS |
50136942Spjd+----------------------------------------+
51136942Spjd
52127342SmlaierFilters are specified with a key letter ``X'' (for ``eXternal'').
53127342Smlaier
54127342SmlaierFor example:
55150839Syar
56144638Sseanc	Xfilter1, S=local:/var/run/f1.sock, F=R
57144638Sseanc	Xfilter2, S=inet6:999@localhost, F=T, T=S:1s;R:1s;E:5m
58150839Syar	Xfilter3, S=inet:3333@localhost
59150839Syar
60127342Smlaierspecifies three filters.  Filters can be specified in your .mc file using
61127342Smlaierthe following:
62127342Smlaier
63127342Smlaier	INPUT_MAIL_FILTER(`filter1', `S=local:/var/run/f1.sock, F=R')
64150839Syar	INPUT_MAIL_FILTER(`filter2', `S=inet6:999@localhost, F=T, T=S:1s;R:1s;E:5m')
65127342Smlaier	INPUT_MAIL_FILTER(`filter3', `S=inet:3333@localhost')
66127342Smlaier
67127342SmlaierThe first attaches to a Unix-domain socket in the /var/run directory; the
68127342Smlaiersecond uses an IPv6 socket on port 999 of localhost, and the third uses an
69150839SyarIPv4 socket on port 3333 of localhost.  The current flags (F=) are:
70127342Smlaier
71127342Smlaier	R		Reject connection if filter unavailable
72127342Smlaier	T		Temporary fail connection if filter unavailable
73
74Finally, you can override the default timeouts used by sendmail when
75talking to the filters using the T= equate.  There are three fields inside
76of the T= equate:
77
78Letter          Meaning
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 the config) are:
86
87T=S:10s;R:10s;E:5m
88
89where 's' is seconds and 'm' is minutes.
90
91Actual sequencing is handled by the InputMailFilters option which is set
92automatically according to the order of the INPUT_MAIL_FILTER commands
93in your .mc file.  Alternatively, you can reset its value by setting
94confINPUT_MAIL_FILTERS in your .mc file.  This options causes the three
95filters to be called in the same order they were specified.  It allows
96for possible future filtering on output (although this is not intended
97for this release).
98
99Also note that a filter can be defined without adding it to the input
100filter list by using MAIL_FILTER() instead of INPUT_MAIL_FILTER() in your
101.mc file.
102
103To test sendmail with the sample filter, the following might be added (in
104the appropriate locations) to your .mc file:
105
106	INPUT_MAIL_FILTER(`sample', `S=local:/var/run/f1.sock')
107
108
109+------------------+
110| TESTING A FILTER |
111+------------------+
112
113Once you have compiled a filter, modified your .mc file and restarted
114the sendmail process, you will want to test that the filter performs as
115intended.
116
117The sample filter takes one argument -p, which indicates the local port
118on which to create a listening socket for the filter.  Maintaining
119consistency with the suggested options for sendmail.cf, this would be the
120UNIX domain socket located in /var/run/f1.sock.
121
122	% ./sample -p local:/var/run/f1.sock
123
124If the sample filter returns immediately to a command line, there was either
125an error with your command or a problem creating the specified socket.
126Further logging can be captured through the syslogd daemon.  Using the
127'netstat -a' command can ensure that your filter process is listening on
128the appropriate local socket.
129
130Email messages must be injected via SMTP to be filtered.  There are two
131simple means of doing this; either using the 'sendmail -bs' command, or
132by telnetting to port 25 of the machine configured for milter.  Once
133connected via one of these options, the session can be continued through
134the use of standard SMTP commands.
135
136% sendmail -bs
137220 test.sendmail.com ESMTP Sendmail 8.10.0.Beta8/8.10.0.Beta8; Mon, 6 Dec 1999 19:34:23 -0800 (PST)
138HELO localhost
139250 test.sendmail.com Hello testy@localhost, pleased to meet you
140MAIL From:<testy>
141250 2.1.0 <testy>... Sender ok
142RCPT To:<root>
143250 2.1.5 <root>... Recipient ok
144DATA
145354 Enter mail, end with "." on a line by itself
146From: testy@test.sendmail.com
147To: root@test.sendmail.com
148Subject: testing sample filter
149
150Sample body
151.
152250 2.0.0 dB73Zxi25236 Message accepted for delivery
153QUIT
154221 2.0.0 test.sendmail.com closing connection
155
156In the above example, the lines beginning with numbers are output by the
157mail server, and those without are your input.  If everything is working
158properly, you will find a file in /tmp by the name of msg.XXXXXXXX (where
159the Xs represent any combination of letters and numbers).  This file should
160contain the message body and headers from the test email entered above.
161
162If the sample filter did not log your test email, there are a number of
163methods to narrow down the source of the problem.  Check your system
164logs written by syslogd and see if there are any pertinent lines.  You
165may need to reconfigure syslogd to capture all relevant data.  Additionally,
166the logging level of sendmail can be raised with the LogLevel option.
167See the sendmail(8) manual page for more information.
168
169
170+--------------------------+
171| SOURCE FOR SAMPLE FILTER |
172+--------------------------+
173
174/* A trivial filter that logs all email to a file. */
175
176#include <sys/types.h>
177#include <stdio.h>
178#include <stdlib.h>
179#include <string.h>
180#include <sysexits.h>
181#include <unistd.h>
182
183#include "libmilter/mfapi.h"
184
185typedef int bool;
186
187#ifndef FALSE
188# define FALSE	0
189#endif /* ! FALSE*/
190#ifndef TRUE
191# define TRUE	1
192#endif /* ! TRUE*/
193
194struct mlfiPriv
195{
196	char	*mlfi_fname;
197	FILE	*mlfi_fp;
198};
199
200#define MLFIPRIV	((struct mlfiPriv *) smfi_getpriv(ctx))
201
202extern sfsistat	 mlfi_cleanup(SMFICTX *, bool);
203
204sfsistat
205mlfi_envfrom(ctx, envfrom)
206	SMFICTX *ctx;
207	char **envfrom;
208{
209	struct mlfiPriv *priv;
210	int fd;
211
212	/* allocate some private memory */
213	priv = malloc(sizeof *priv);
214	if (priv == NULL)
215	{
216		/* can't accept this message right now */
217		return SMFIS_TEMPFAIL;
218	}
219	memset(priv, '\0', sizeof *priv);
220
221	/* open a file to store this message */
222	priv->mlfi_fname = strdup("/tmp/msg.XXXXXXXX");
223	if (priv->mlfi_fname == NULL)
224	{
225		free(priv);
226		return SMFIS_TEMPFAIL;
227	}
228	if ((fd = mkstemp(priv->mlfi_fname)) < 0 ||
229	    (priv->mlfi_fp = fdopen(fd, "w+")) == NULL)
230	{
231		free(priv->mlfi_fname);
232		free(priv);
233		return SMFIS_TEMPFAIL;
234	}
235
236	/* save the private data */
237	smfi_setpriv(ctx, priv);
238
239	/* continue processing */
240	return SMFIS_CONTINUE;
241}
242
243sfsistat
244mlfi_header(ctx, headerf, headerv)
245	SMFICTX *ctx;
246	char *headerf;
247	char *headerv;
248{
249	/* write the header to the log file */
250	fprintf(MLFIPRIV->mlfi_fp, "%s: %s\r\n", headerf, headerv);
251
252	/* continue processing */
253	return SMFIS_CONTINUE;
254}
255
256sfsistat
257mlfi_eoh(ctx)
258	SMFICTX *ctx;
259{
260	/* output the blank line between the header and the body */
261	fprintf(MLFIPRIV->mlfi_fp, "\r\n");
262
263	/* continue processing */
264	return SMFIS_CONTINUE;
265}
266
267sfsistat
268mlfi_body(ctx, bodyp, bodylen)
269	SMFICTX *ctx;
270	u_char *bodyp;
271	size_t bodylen;
272{
273	/* output body block to log file */
274	if (fwrite(bodyp, bodylen, 1, MLFIPRIV->mlfi_fp) <= 0)
275	{
276		/* write failed */
277		(void) mlfi_cleanup(ctx, FALSE);
278		return SMFIS_TEMPFAIL;
279	}
280
281	/* continue processing */
282	return SMFIS_CONTINUE;
283}
284
285sfsistat
286mlfi_eom(ctx)
287	SMFICTX *ctx;
288{
289	return mlfi_cleanup(ctx, TRUE);
290}
291
292sfsistat
293mlfi_close(ctx)
294	SMFICTX *ctx;
295{
296	return SMFIS_ACCEPT;
297}
298
299sfsistat
300mlfi_abort(ctx)
301	SMFICTX *ctx;
302{
303	return mlfi_cleanup(ctx, FALSE);
304}
305
306sfsistat
307mlfi_cleanup(ctx, ok)
308	SMFICTX *ctx;
309	bool ok;
310{
311	sfsistat rstat = SMFIS_CONTINUE;
312	struct mlfiPriv *priv = MLFIPRIV;
313	char *p;
314	char host[512];
315	char hbuf[1024];
316
317	if (priv == NULL)
318		return rstat;
319
320	/* close the archive file */
321	if (priv->mlfi_fp != NULL && fclose(priv->mlfi_fp) == EOF)
322	{
323		/* failed; we have to wait until later */
324		rstat = SMFIS_TEMPFAIL;
325		(void) unlink(priv->mlfi_fname);
326	}
327	else if (ok)
328	{
329		/* add a header to the message announcing our presence */
330		if (gethostname(host, sizeof host) < 0)
331			strlcpy(host, "localhost", sizeof host);
332		p = strrchr(priv->mlfi_fname, '/');
333		if (p == NULL)
334			p = priv->mlfi_fname;
335		else
336			p++;
337		snprintf(hbuf, sizeof hbuf, "%s@%s", p, host);
338		smfi_addheader(ctx, "X-Archived", hbuf);
339	}
340	else
341	{
342		/* message was aborted -- delete the archive file */
343		(void) unlink(priv->mlfi_fname);
344	}
345
346	/* release private memory */
347	free(priv->mlfi_fname);
348	free(priv);
349	smfi_setpriv(ctx, NULL);
350
351	/* return status */
352	return rstat;
353}
354
355struct smfiDesc smfilter =
356{
357	"SampleFilter",	/* filter name */
358	SMFI_VERSION,	/* version code -- do not change */
359	SMFIF_ADDHDRS,	/* flags */
360	NULL,		/* connection info filter */
361	NULL,		/* SMTP HELO command filter */
362	mlfi_envfrom,	/* envelope sender filter */
363	NULL,		/* envelope recipient filter */
364	mlfi_header,	/* header filter */
365	mlfi_eoh,	/* end of header */
366	mlfi_body,	/* body block filter */
367	mlfi_eom,	/* end of message */
368	mlfi_abort,	/* message aborted */
369	mlfi_close	/* connection cleanup */
370};
371
372
373int
374main(argc, argv)
375	int argc;
376	char *argv[];
377{
378	int c;
379	const char *args = "p:";
380
381	/* Process command line options */
382	while ((c = getopt(argc, argv, args)) != -1)
383	{
384		switch (c)
385		{
386		  case 'p':
387			if (optarg == NULL || *optarg == '\0')
388			{
389				(void) fprintf(stderr, "Illegal conn: %s\n",
390					       optarg);
391				exit(EX_USAGE);
392			}
393			(void) smfi_setconn(optarg);
394			break;
395
396		}
397	}
398	if (smfi_register(smfilter) == MI_FAILURE)
399	{
400		fprintf(stderr, "smfi_register failed\n");
401		exit(EX_UNAVAILABLE);
402	}
403	return smfi_main();
404}
405
406/* eof */
407
408$Revision: 8.9.2.1.2.8 $, Last updated $Date: 2000/07/18 15:43:26 $
409