README revision 64562
111394SswallaceThis directory contains the source files for libmilter.
211394Sswallace
311394SswallaceThe sendmail Mail Filter API (Milter) is designed to allow third-party
411394Sswallaceprograms access to mail messages as they are being processed in order to
511394Sswallacefilter meta-information and content.
611394Sswallace
711394SswallaceThis README file describes the steps needed to compile and run a filter,
811394Sswallacethrough reference to a sample filter which is attached at the end of this
911394Sswallacefile.  It is necessary to first build libmilter.a, which can be done by
1011394Sswallaceissuing the './Build' command in SRCDIR/libmilter .
1111394Sswallace
1211394SswallaceNOTE: Both libmilter and the callouts in sendmail are marked as an FFR (For
1311394SswallaceFuture Release).  If you intend to use them in 8.10.X, you must compiled
1411394Sswallaceboth libmilter and sendmail with -D_FFR_MILTER defined.  You can do this by
1511394Sswallaceadding the following to your devtools/Site/site.config.m4 file:
1611394Sswallace
1711394Sswallace	dnl Milter
1811394Sswallace	APPENDDEF(`conf_sendmail_ENVDEF', `-D_FFR_MILTER=1')
1911394Sswallace	APPENDDEF(`conf_libmilter_ENVDEF', `-D_FFR_MILTER=1') 
2011394Sswallace
2111394SswallaceYou will also need to define _FFR_MILTER when building your .cf file using
2211394Sswallacem4.
2311394Sswallace
2411394Sswallace+-------------------+
2511394Sswallace| BUILDING A FILTER |
2611394Sswallace+-------------------+
2711394Sswallace
2811394SswallaceThe following command presumes that the sample code from the end of this
2911394SswallaceREADME is saved to a file named 'sample.c' and built in the local platform-
3011394Sswallacespecific build subdirectory (SRCDIR/obj.*/libmilter).
3111394Sswallace
3211394Sswallace	cc -I../../sendmail -I../../include -o sample sample.c libmilter.a ../libsmutil/libsmutil.a -pthread
3311394Sswallace
3411394SswallaceIt is recommended that you build your filters in a location outside of
3511394Sswallacethe sendmail source tree.  Modify the compiler include references (-I)
3611394Sswallaceand the library locations accordingly.  Also, some operating systems may
3711394Sswallacerequire additional libraries.  For example, SunOS 5.X requires '-lresolv
3811394Sswallace-lsocket -lnsl'.  Depending on your OS you may need a library instead
3911394Sswallaceof the option -pthread, e.g., -lpthread.
4011394Sswallace
4111394SswallaceFilters must be thread-safe!  Many operating systems now provide support for
4228750SbdePOSIX threads in the standard C libraries.  The compiler flag to link with
4328750Sbdethreading support differs according to the compiler and linker used.  Check
4428750Sbdethe Makefile in your appropriate obj.*/libmilter build subdirectory if you
4528750Sbdeare unsure of the local flag used.
4611397Sswallace
4712662Sdg
4811394Sswallace+----------------------------------------+
4911394Sswallace| SPECIFYING FILTERS IN SENDMAIL CONFIGS |
5014331Speter+----------------------------------------+
5114331Speter
5211394SswallaceFilters are specified with a key letter ``X'' (for ``eXternal'').
5311397Sswallace
5411397SswallaceFor example:
5511397Sswallace
5611397Sswallace	Xfilter1, S=local:/var/run/f1.sock, F=R
5711397Sswallace	Xfilter2, S=inet6:999@localhost, F=T, T=S:1s;R:1s;E:5m
5811397Sswallace	Xfilter3, S=inet:3333@localhost
5911397Sswallace
6011394Sswallacespecifies three filters.  Filters can be specified in your .mc file using
6111394Sswallacethe following:
6211394Sswallace
6314464Speter	INPUT_MAIL_FILTER(`filter1', `S=local:/var/run/f1.sock, F=R')
6441871Sbde	INPUT_MAIL_FILTER(`filter2', `S=inet6:999@localhost, F=T, T=S:1s;R:1s;E:5m')
6511394Sswallace	INPUT_MAIL_FILTER(`filter3', `S=inet:3333@localhost')
6611394Sswallace
6711394SswallaceThe first attaches to a Unix-domain socket in the /var/run directory; the
6811394Sswallacesecond uses an IPv6 socket on port 999 of localhost, and the third uses an
6911394SswallaceIPv4 socket on port 3333 of localhost.  The current flags (F=) are:
7011394Sswallace
7111394Sswallace	R		Reject connection if filter unavailable
7211394Sswallace	T		Temporary fail connection if filter unavailable
7311394Sswallace
7411394SswallaceFinally, you can override the default timeouts used by sendmail when
7511394Sswallacetalking to the filters using the T= equate.  There are three fields inside
7611394Sswallaceof the T= equate:
7711394Sswallace
7811394SswallaceLetter          Meaning
7911394Sswallace  S             Timeout for sending information from the MTA to a filter
8011394Sswallace  R             Timeout for reading reply from the filter
8111394Sswallace  E             Overall timeout between sending end-of-message to filter
8211394Sswallace                and waiting for the final acknowledgment
8311394Sswallace
8411394SswallaceNote the separator between each is a ';' as a ',' already separates equates
8511394Sswallaceand therefore can't separate timeouts.  The default values (if not set in the config) are:
8611394Sswallace
8711394SswallaceT=S:10s;R:10s;E:5m
8811394Sswallace
8911394Sswallacewhere 's' is seconds and 'm' is minutes.
9011394Sswallace
9111394SswallaceActual sequencing is handled by the InputMailFilters option which is set
9211394Sswallaceautomatically according to the order of the INPUT_MAIL_FILTER commands
9311394Sswallacein your .mc file.  Alternatively, you can reset its value by setting
9420652SbdeconfINPUT_MAIL_FILTERS in your .mc file.  This options causes the three
9530994Sphkfilters to be called in the same order they were specified.  It allows
9620652Sbdefor possible future filtering on output (although this is not intended
9720652Sbdefor this release).
9811394Sswallace
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