summaryrefslogtreecommitdiffstats
path: root/contrib/sendmail/libmilter/README
blob: 1aadc4f4e2c95393472b3e32c61722f7d70a3583 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
This directory contains the source files for libmilter.

The sendmail Mail Filter API (Milter) is designed to allow third-party
programs access to mail messages as they are being processed in order to
filter meta-information and content.

This README file describes the steps needed to compile and run a filter,
through reference to a sample filter which is attached at the end of this
file.  It is necessary to first build libmilter.a, which can be done by
issuing the './Build' command in SRCDIR/libmilter .

Starting with 8.13 sendmail is compiled by default with support for
the milter API.

Note: if you want to write a milter in Java, then see
http://sendmail-jilter.sourceforge.net/

+----------------+
| SECURITY HINTS |
+----------------+

Note: we strongly recommend not to run any milter as root.  Libmilter
does not need root access to communicate with sendmail.  It is a
good security practice to run a program only with root privileges
if really necessary.  A milter should probably check first whether
it runs as root and refuse to start in that case.  libmilter will
not unlink a socket when running as root.

+----------------------+
| CONFIGURATION MACROS |
+----------------------+

Libmilter uses a set of C preprocessor macros to specify platform specific
features of the C compiler and standard C libraries.

SM_CONF_POLL
	Set to 1 if poll(2) should be used instead of select(2).

+-------------------+
| BUILDING A FILTER |
+-------------------+

The following command presumes that the sample code from the end of this
README is saved to a file named 'sample.c' and built in the local platform-
specific build subdirectory (SRCDIR/obj.*/libmilter).

	cc -I../../include -o sample sample.c libmilter.a ../libsm/libsm.a -pthread

It is recommended that you build your filters in a location outside of
the sendmail source tree.  Modify the compiler include references (-I)
and the library locations accordingly.  Also, some operating systems may
require additional libraries.  For example, SunOS 5.X requires '-lresolv
-lsocket -lnsl'.  Depending on your operating system you may need a library
instead of the option -pthread, e.g., -lpthread.

Filters must be thread-safe!  Many operating systems now provide support for
POSIX threads in the standard C libraries.  The compiler flag to link with
threading support differs according to the compiler and linker used.  Check
the Makefile in your appropriate obj.*/libmilter build subdirectory if you
are unsure of the local flag used.

Note that since filters use threads, it may be necessary to alter per
process limits in your filter.  For example, you might look at using
setrlimit() to increase the number of open file descriptors if your filter
is going to be busy.


+----------------------------------------+
| SPECIFYING FILTERS IN SENDMAIL CONFIGS |
+----------------------------------------+

Filters are specified with a key letter ``X'' (for ``eXternal'').

For example:

	Xfilter1, S=local:/var/run/f1.sock, F=R
	Xfilter2, S=inet6:999@localhost, F=T, T=C:10m;S:1s;R:1s;E:5m
	Xfilter3, S=inet:3333@localhost

specifies three filters.  Filters can be specified in your .mc file using
the following:

	INPUT_MAIL_FILTER(`filter1', `S=local:/var/run/f1.sock, F=R')
	INPUT_MAIL_FILTER(`filter2', `S=inet6:999@localhost, F=T, T=C:10m;S:1s;R:1s;E:5m')
	INPUT_MAIL_FILTER(`filter3', `S=inet:3333@localhost')

The first attaches to a Unix-domain socket in the /var/run directory; the
second uses an IPv6 socket on port 999 of localhost, and the third uses an
IPv4 socket on port 3333 of localhost.  The current flags (F=) are:

	R		Reject connection if filter unavailable
	T		Temporary fail connection if filter unavailable

If neither F=R nor F=T is specified, the message is passed through sendmail
in case of filter errors as if the failing filters were not present.

Finally, you can override the default timeouts used by sendmail when
talking to the filters using the T= equate.  There are four fields inside
of the T= equate:

Letter		Meaning
  C		Timeout for connecting to a filter (if 0, use system timeout)
  S		Timeout for sending information from the MTA to a filter
  R		Timeout for reading reply from the filter
  E		Overall timeout between sending end-of-message to filter
		and waiting for the final acknowledgment

Note the separator between each is a ';' as a ',' already separates equates
and therefore can't separate timeouts.  The default values (if not set in
the config) are:

T=C:5m;S:10s;R:10s;E:5m

where 's' is seconds and 'm' is minutes.

Which filters are invoked and their sequencing is handled by the 
InputMailFilters option. Note: if InputMailFilters is not defined no filters
will be used.

	O InputMailFilters=filter1, filter2, filter3

This is is set automatically according to the order of the
INPUT_MAIL_FILTER commands in your .mc file.  Alternatively, you can
reset its value by setting confINPUT_MAIL_FILTERS in your .mc file.
This options causes the three filters to be called in the same order
they were specified.  It allows for possible future filtering on output
(although this is not intended for this release).

Also note that a filter can be defined without adding it to the input
filter list by using MAIL_FILTER() instead of INPUT_MAIL_FILTER() in your
.mc file.

To test sendmail with the sample filter, the following might be added (in
the appropriate locations) to your .mc file:

	INPUT_MAIL_FILTER(`sample', `S=local:/var/run/f1.sock')


+------------------+
| TESTING A FILTER |
+------------------+

Once you have compiled a filter, modified your .mc file and restarted
the sendmail process, you will want to test that the filter performs as
intended.

The sample filter takes one argument -p, which indicates the local port
on which to create a listening socket for the filter.  Maintaining
consistency with the suggested options for sendmail.cf, this would be the
UNIX domain socket located in /var/run/f1.sock.

	% ./sample -p local:/var/run/f1.sock

If the sample filter returns immediately to a command line, there was either
an error with your command or a problem creating the specified socket.
Further logging can be captured through the syslogd daemon.  Using the
'netstat -a' command can ensure that your filter process is listening on
the appropriate local socket.

Email messages must be injected via SMTP to be filtered.  There are two
simple means of doing this; either using the 'sendmail -bs' command, or
by telnetting to port 25 of the machine configured for milter.  Once
connected via one of these options, the session can be continued through
the use of standard SMTP commands.

% sendmail -bs
220 test.sendmail.com ESMTP Sendmail 8.11.0/8.11.0; Tue, 10 Nov 1970 13:05:23 -0500 (EST)
HELO localhost
250 test.sendmail.com Hello testy@localhost, pleased to meet you
MAIL From:<testy>
250 2.1.0 <testy>... Sender ok
RCPT To:<root>
250 2.1.5 <root>... Recipient ok
DATA
354 Enter mail, end with "." on a line by itself
From: testy@test.sendmail.com
To: root@test.sendmail.com
Subject: testing sample filter

Sample body
.
250 2.0.0 dB73Zxi25236 Message accepted for delivery
QUIT
221 2.0.0 test.sendmail.com closing connection

In the above example, the lines beginning with numbers are output by the
mail server, and those without are your input.  If everything is working
properly, you will find a file in /tmp by the name of msg.XXXXXXXX (where
the Xs represent any combination of letters and numbers).  This file should
contain the message body and headers from the test email entered above.

If the sample filter did not log your test email, there are a number of
methods to narrow down the source of the problem.  Check your system
logs written by syslogd and see if there are any pertinent lines.  You
may need to reconfigure syslogd to capture all relevant data.  Additionally,
the logging level of sendmail can be raised with the LogLevel option.
See the sendmail(8) manual page for more information.


+--------------+
| REQUIREMENTS |
+--------------+

libmilter requires pthread support in the operating system.  Moreover, it
requires that the library functions it uses are thread safe; which is true
for the operating systems libmilter has been developed and tested on.  On
some operating systems this requires special compile time options (e.g.,
not just -pthread).  libmilter is currently known to work on (modulo problems
in the pthread support of some specific versions):

FreeBSD 3.x, 4.x
SunOS 5.x (x >= 5)
AIX 4.3.x
HP UX 11.x
Linux (recent versions/distributions)

libmilter is currently not supported on:

IRIX 6.x
Ultrix

Feedback about problems (and possible fixes) is welcome.

+--------------------------+
| SOURCE FOR SAMPLE FILTER |
+--------------------------+

Note that the filter below may not be thread safe on some operating
systems.  You should check your system man pages for the functions used
below to verify the functions are thread safe.

/* A trivial filter that logs all email to a file. */

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>

#include "libmilter/mfapi.h"

#ifndef true
typedef int bool;
# define false	0
# define true	1
#endif /* ! true */

struct mlfiPriv
{
	char	*mlfi_fname;
	FILE	*mlfi_fp;
};

#define MLFIPRIV	((struct mlfiPriv *) smfi_getpriv(ctx))

extern sfsistat	 mlfi_cleanup(SMFICTX *, bool);

sfsistat
mlfi_envfrom(ctx, envfrom)
	SMFICTX *ctx;
	char **envfrom;
{
	struct mlfiPriv *priv;
	int fd = -1;

	/* allocate some private memory */
	priv = malloc(sizeof *priv);
	if (priv == NULL)
	{
		/* can't accept this message right now */
		return SMFIS_TEMPFAIL;
	}
	memset(priv, '\0', sizeof *priv);

	/* open a file to store this message */
	priv->mlfi_fname = strdup("/tmp/msg.XXXXXXXX");
	if (priv->mlfi_fname == NULL)
	{
		free(priv);
		return SMFIS_TEMPFAIL;
	}
	if ((fd = mkstemp(priv->mlfi_fname)) < 0 ||
	    (priv->mlfi_fp = fdopen(fd, "w+")) == NULL)
	{
		if (fd >= 0)
			(void) close(fd);
		free(priv->mlfi_fname);
		free(priv);
		return SMFIS_TEMPFAIL;
	}

	/* save the private data */
	smfi_setpriv(ctx, priv);

	/* continue processing */
	return SMFIS_CONTINUE;
}

sfsistat
mlfi_header(ctx, headerf, headerv)
	SMFICTX *ctx;
	char *headerf;
	char *headerv;
{
	/* write the header to the log file */
	fprintf(MLFIPRIV->mlfi_fp, "%s: %s\r\n", headerf, headerv);

	/* continue processing */
	return SMFIS_CONTINUE;
}

sfsistat
mlfi_eoh(ctx)
	SMFICTX *ctx;
{
	/* output the blank line between the header and the body */
	fprintf(MLFIPRIV->mlfi_fp, "\r\n");

	/* continue processing */
	return SMFIS_CONTINUE;
}

sfsistat
mlfi_body(ctx, bodyp, bodylen)
	SMFICTX *ctx;
	u_char *bodyp;
	size_t bodylen;
{
	/* output body block to log file */
	if (fwrite(bodyp, bodylen, 1, MLFIPRIV->mlfi_fp) <= 0)
	{
		/* write failed */
		(void) mlfi_cleanup(ctx, false);
		return SMFIS_TEMPFAIL;
	}

	/* continue processing */
	return SMFIS_CONTINUE;
}

sfsistat
mlfi_eom(ctx)
	SMFICTX *ctx;
{
	return mlfi_cleanup(ctx, true);
}

sfsistat
mlfi_close(ctx)
	SMFICTX *ctx;
{
	return SMFIS_ACCEPT;
}

sfsistat
mlfi_abort(ctx)
	SMFICTX *ctx;
{
	return mlfi_cleanup(ctx, false);
}

sfsistat
mlfi_cleanup(ctx, ok)
	SMFICTX *ctx;
	bool ok;
{
	sfsistat rstat = SMFIS_CONTINUE;
	struct mlfiPriv *priv = MLFIPRIV;
	char *p;
	char host[512];
	char hbuf[1024];

	if (priv == NULL)
		return rstat;

	/* close the archive file */
	if (priv->mlfi_fp != NULL && fclose(priv->mlfi_fp) == EOF)
	{
		/* failed; we have to wait until later */
		rstat = SMFIS_TEMPFAIL;
		(void) unlink(priv->mlfi_fname);
	}
	else if (ok)
	{
		/* add a header to the message announcing our presence */
		if (gethostname(host, sizeof host) < 0)
			snprintf(host, sizeof host, "localhost");
		p = strrchr(priv->mlfi_fname, '/');
		if (p == NULL)
			p = priv->mlfi_fname;
		else
			p++;
		snprintf(hbuf, sizeof hbuf, "%s@%s", p, host);
		smfi_addheader(ctx, "X-Archived", hbuf);
	}
	else
	{
		/* message was aborted -- delete the archive file */
		(void) unlink(priv->mlfi_fname);
	}

	/* release private memory */
	free(priv->mlfi_fname);
	free(priv);
	smfi_setpriv(ctx, NULL);

	/* return status */
	return rstat;
}

struct smfiDesc smfilter =
{
	"SampleFilter",	/* filter name */
	SMFI_VERSION,	/* version code -- do not change */
	SMFIF_ADDHDRS,	/* flags */
	NULL,		/* connection info filter */
	NULL,		/* SMTP HELO command filter */
	mlfi_envfrom,	/* envelope sender filter */
	NULL,		/* envelope recipient filter */
	mlfi_header,	/* header filter */
	mlfi_eoh,	/* end of header */
	mlfi_body,	/* body block filter */
	mlfi_eom,	/* end of message */
	mlfi_abort,	/* message aborted */
	mlfi_close	/* connection cleanup */
};


int
main(argc, argv)
	int argc;
	char *argv[];
{
	bool setconn = false;
	int c;
	const char *args = "p:";

	/* Process command line options */
	while ((c = getopt(argc, argv, args)) != -1)
	{
		switch (c)
		{
		  case 'p':
			if (optarg == NULL || *optarg == '\0')
			{
				(void) fprintf(stderr, "Illegal conn: %s\n",
					       optarg);
				exit(EX_USAGE);
			}
			(void) smfi_setconn(optarg);
			setconn = true;
			break;

		}
	}
	if (!setconn)
	{
		fprintf(stderr, "%s: Missing required -p argument\n", argv[0]);
		exit(EX_USAGE);
	}
	if (smfi_register(smfilter) == MI_FAILURE)
	{
		fprintf(stderr, "smfi_register failed\n");
		exit(EX_UNAVAILABLE);
	}
	return smfi_main();
}

/* eof */

$Revision: 8.41 $, Last updated $Date: 2005/04/27 22:47:42 $
OpenPOWER on IntegriCloud