Merge from vendor branch LIBSTDC++:
[dragonfly.git] / contrib / sendmail / libmilter / README
1 This directory contains the source files for libmilter.
2
3 The sendmail Mail Filter API (Milter) is designed to allow third-party
4 programs access to mail messages as they are being processed in order to
5 filter meta-information and content.
6
7 This README file describes the steps needed to compile and run a filter,
8 through reference to a sample filter which is attached at the end of this
9 file.  It is necessary to first build libmilter.a, which can be done by
10 issuing the './Build' command in SRCDIR/libmilter .
11
12 NOTE: If you intend to use filters in sendmail, you must compile sendmail
13 with -DMILTER defined.  You can do this by adding the following to
14 your devtools/Site/site.config.m4 file:
15
16         APPENDDEF(`conf_sendmail_ENVDEF', `-DMILTER')
17
18 +----------------+
19 | SECURITY HINTS |
20 +----------------+
21
22 Note: we strongly recommend not to run any milter as root.  Libmilter
23 does not need root access to communicate with sendmail.  It is a
24 good security practice to run a program only with root privileges
25 if really necessary.  A milter should probably check first whether
26 it runs as root and refuse to start in that case.  There is a
27 compile time option _FFR_MILTER_ROOT_UNSAFE which keeps libmilter
28 from unlinking a socket when running as root.  It is recommended
29 to turn on this option:
30
31         APPENDDEF(`conf_libmilter_ENVDEF', `-D_FFR_MILTER_ROOT_UNSAFE ')
32
33
34 +-------------------+
35 | BUILDING A FILTER |
36 +-------------------+
37
38 The following command presumes that the sample code from the end of this
39 README is saved to a file named 'sample.c' and built in the local platform-
40 specific build subdirectory (SRCDIR/obj.*/libmilter).
41
42         cc -I../../include -o sample sample.c libmilter.a ../libsm/libsm.a -pthread
43
44 It is recommended that you build your filters in a location outside of
45 the sendmail source tree.  Modify the compiler include references (-I)
46 and the library locations accordingly.  Also, some operating systems may
47 require additional libraries.  For example, SunOS 5.X requires '-lresolv
48 -lsocket -lnsl'.  Depending on your operating system you may need a library
49 instead of the option -pthread, e.g., -lpthread.
50
51 Filters must be thread-safe!  Many operating systems now provide support for
52 POSIX threads in the standard C libraries.  The compiler flag to link with
53 threading support differs according to the compiler and linker used.  Check
54 the Makefile in your appropriate obj.*/libmilter build subdirectory if you
55 are unsure of the local flag used.
56
57 Note that since filters use threads, it may be necessary to alter per
58 process limits in your filter.  For example, you might look at using
59 setrlimit() to increase the number of open file descriptors if your filter
60 is going to be busy.
61
62
63 +----------------------------------------+
64 | SPECIFYING FILTERS IN SENDMAIL CONFIGS |
65 +----------------------------------------+
66
67 Filters are specified with a key letter ``X'' (for ``eXternal'').
68
69 For example:
70
71         Xfilter1, S=local:/var/run/f1.sock, F=R
72         Xfilter2, S=inet6:999@localhost, F=T, T=C:10m;S:1s;R:1s;E:5m
73         Xfilter3, S=inet:3333@localhost
74
75 specifies three filters.  Filters can be specified in your .mc file using
76 the following:
77
78         INPUT_MAIL_FILTER(`filter1', `S=local:/var/run/f1.sock, F=R')
79         INPUT_MAIL_FILTER(`filter2', `S=inet6:999@localhost, F=T, T=C:10m;S:1s;R:1s;E:5m')
80         INPUT_MAIL_FILTER(`filter3', `S=inet:3333@localhost')
81
82 The first attaches to a Unix-domain socket in the /var/run directory; the
83 second uses an IPv6 socket on port 999 of localhost, and the third uses an
84 IPv4 socket on port 3333 of localhost.  The current flags (F=) are:
85
86         R               Reject connection if filter unavailable
87         T               Temporary fail connection if filter unavailable
88
89 If neither F=R nor F=T is specified, the message is passed through sendmail
90 in case of filter errors as if the failing filters were not present.
91
92 Finally, you can override the default timeouts used by sendmail when
93 talking to the filters using the T= equate.  There are four fields inside
94 of the T= equate:
95
96 Letter          Meaning
97   C             Timeout for connecting to a filter (if 0, use system timeout)
98   S             Timeout for sending information from the MTA to a filter
99   R             Timeout for reading reply from the filter
100   E             Overall timeout between sending end-of-message to filter
101                 and waiting for the final acknowledgment
102
103 Note the separator between each is a ';' as a ',' already separates equates
104 and therefore can't separate timeouts.  The default values (if not set in
105 the config) are:
106
107 T=C:5m;S:10s;R:10s;E:5m
108
109 where 's' is seconds and 'm' is minutes.
110
111 Which filters are invoked and their sequencing is handled by the 
112 InputMailFilters option. Note: if InputMailFilters is not defined no filters
113 will be used.
114
115         O InputMailFilters=filter1, filter2, filter3
116
117 This is is set automatically according to the order of the
118 INPUT_MAIL_FILTER commands in your .mc file.  Alternatively, you can
119 reset its value by setting confINPUT_MAIL_FILTERS in your .mc file.
120 This options causes the three filters to be called in the same order
121 they were specified.  It allows for possible future filtering on output
122 (although this is not intended for this release).
123
124 Also note that a filter can be defined without adding it to the input
125 filter list by using MAIL_FILTER() instead of INPUT_MAIL_FILTER() in your
126 .mc file.
127
128 To test sendmail with the sample filter, the following might be added (in
129 the appropriate locations) to your .mc file:
130
131         INPUT_MAIL_FILTER(`sample', `S=local:/var/run/f1.sock')
132
133
134 +------------------+
135 | TESTING A FILTER |
136 +------------------+
137
138 Once you have compiled a filter, modified your .mc file and restarted
139 the sendmail process, you will want to test that the filter performs as
140 intended.
141
142 The sample filter takes one argument -p, which indicates the local port
143 on which to create a listening socket for the filter.  Maintaining
144 consistency with the suggested options for sendmail.cf, this would be the
145 UNIX domain socket located in /var/run/f1.sock.
146
147         % ./sample -p local:/var/run/f1.sock
148
149 If the sample filter returns immediately to a command line, there was either
150 an error with your command or a problem creating the specified socket.
151 Further logging can be captured through the syslogd daemon.  Using the
152 'netstat -a' command can ensure that your filter process is listening on
153 the appropriate local socket.
154
155 Email messages must be injected via SMTP to be filtered.  There are two
156 simple means of doing this; either using the 'sendmail -bs' command, or
157 by telnetting to port 25 of the machine configured for milter.  Once
158 connected via one of these options, the session can be continued through
159 the use of standard SMTP commands.
160
161 % sendmail -bs
162 220 test.sendmail.com ESMTP Sendmail 8.11.0/8.11.0; Tue, 10 Nov 1970 13:05:23 -0500 (EST)
163 HELO localhost
164 250 test.sendmail.com Hello testy@localhost, pleased to meet you
165 MAIL From:<testy>
166 250 2.1.0 <testy>... Sender ok
167 RCPT To:<root>
168 250 2.1.5 <root>... Recipient ok
169 DATA
170 354 Enter mail, end with "." on a line by itself
171 From: testy@test.sendmail.com
172 To: root@test.sendmail.com
173 Subject: testing sample filter
174
175 Sample body
176 .
177 250 2.0.0 dB73Zxi25236 Message accepted for delivery
178 QUIT
179 221 2.0.0 test.sendmail.com closing connection
180
181 In the above example, the lines beginning with numbers are output by the
182 mail server, and those without are your input.  If everything is working
183 properly, you will find a file in /tmp by the name of msg.XXXXXXXX (where
184 the Xs represent any combination of letters and numbers).  This file should
185 contain the message body and headers from the test email entered above.
186
187 If the sample filter did not log your test email, there are a number of
188 methods to narrow down the source of the problem.  Check your system
189 logs written by syslogd and see if there are any pertinent lines.  You
190 may need to reconfigure syslogd to capture all relevant data.  Additionally,
191 the logging level of sendmail can be raised with the LogLevel option.
192 See the sendmail(8) manual page for more information.
193
194
195 +--------------+
196 | REQUIREMENTS |
197 +--------------+
198
199 libmilter requires pthread support in the operating system.  Moreover, it
200 requires that the library functions it uses are thread safe; which is true
201 for the operating systems libmilter has been developed and tested on.  On
202 some operating systems this requires special compile time options (e.g.,
203 not just -pthread).  libmilter is currently known to work on (modulo problems
204 in the pthread support of some specific versions):
205
206 FreeBSD 3.x, 4.x
207 SunOS 5.x (x >= 5)
208 AIX 4.3.x
209 HP UX 11.x
210 Linux (recent versions/distributions)
211
212 libmilter is currently not supported on:
213
214 IRIX 6.x
215 Ultrix
216
217 Feedback about problems (and possible fixes) is welcome.
218
219 +--------------------------+
220 | SOURCE FOR SAMPLE FILTER |
221 +--------------------------+
222
223 Note that the filter below may not be thread safe on some operating
224 systems.  You should check your system man pages for the functions used
225 below to verify the functions are thread safe.
226
227 /* A trivial filter that logs all email to a file. */
228
229 #include <sys/types.h>
230 #include <stdio.h>
231 #include <stdlib.h>
232 #include <string.h>
233 #include <sysexits.h>
234 #include <unistd.h>
235
236 #include "libmilter/mfapi.h"
237
238 #ifndef true
239 typedef int bool;
240 # define false  0
241 # define true   1
242 #endif /* ! true */
243
244 struct mlfiPriv
245 {
246         char    *mlfi_fname;
247         FILE    *mlfi_fp;
248 };
249
250 #define MLFIPRIV        ((struct mlfiPriv *) smfi_getpriv(ctx))
251
252 extern sfsistat  mlfi_cleanup(SMFICTX *, bool);
253
254 sfsistat
255 mlfi_envfrom(ctx, envfrom)
256         SMFICTX *ctx;
257         char **envfrom;
258 {
259         struct mlfiPriv *priv;
260         int fd = -1;
261
262         /* allocate some private memory */
263         priv = malloc(sizeof *priv);
264         if (priv == NULL)
265         {
266                 /* can't accept this message right now */
267                 return SMFIS_TEMPFAIL;
268         }
269         memset(priv, '\0', sizeof *priv);
270
271         /* open a file to store this message */
272         priv->mlfi_fname = strdup("/tmp/msg.XXXXXXXX");
273         if (priv->mlfi_fname == NULL)
274         {
275                 free(priv);
276                 return SMFIS_TEMPFAIL;
277         }
278         if ((fd = mkstemp(priv->mlfi_fname)) < 0 ||
279             (priv->mlfi_fp = fdopen(fd, "w+")) == NULL)
280         {
281                 if (fd >= 0)
282                         (void) close(fd);
283                 free(priv->mlfi_fname);
284                 free(priv);
285                 return SMFIS_TEMPFAIL;
286         }
287
288         /* save the private data */
289         smfi_setpriv(ctx, priv);
290
291         /* continue processing */
292         return SMFIS_CONTINUE;
293 }
294
295 sfsistat
296 mlfi_header(ctx, headerf, headerv)
297         SMFICTX *ctx;
298         char *headerf;
299         char *headerv;
300 {
301         /* write the header to the log file */
302         fprintf(MLFIPRIV->mlfi_fp, "%s: %s\r\n", headerf, headerv);
303
304         /* continue processing */
305         return SMFIS_CONTINUE;
306 }
307
308 sfsistat
309 mlfi_eoh(ctx)
310         SMFICTX *ctx;
311 {
312         /* output the blank line between the header and the body */
313         fprintf(MLFIPRIV->mlfi_fp, "\r\n");
314
315         /* continue processing */
316         return SMFIS_CONTINUE;
317 }
318
319 sfsistat
320 mlfi_body(ctx, bodyp, bodylen)
321         SMFICTX *ctx;
322         u_char *bodyp;
323         size_t bodylen;
324 {
325         /* output body block to log file */
326         if (fwrite(bodyp, bodylen, 1, MLFIPRIV->mlfi_fp) <= 0)
327         {
328                 /* write failed */
329                 (void) mlfi_cleanup(ctx, false);
330                 return SMFIS_TEMPFAIL;
331         }
332
333         /* continue processing */
334         return SMFIS_CONTINUE;
335 }
336
337 sfsistat
338 mlfi_eom(ctx)
339         SMFICTX *ctx;
340 {
341         return mlfi_cleanup(ctx, true);
342 }
343
344 sfsistat
345 mlfi_close(ctx)
346         SMFICTX *ctx;
347 {
348         return SMFIS_ACCEPT;
349 }
350
351 sfsistat
352 mlfi_abort(ctx)
353         SMFICTX *ctx;
354 {
355         return mlfi_cleanup(ctx, false);
356 }
357
358 sfsistat
359 mlfi_cleanup(ctx, ok)
360         SMFICTX *ctx;
361         bool ok;
362 {
363         sfsistat rstat = SMFIS_CONTINUE;
364         struct mlfiPriv *priv = MLFIPRIV;
365         char *p;
366         char host[512];
367         char hbuf[1024];
368
369         if (priv == NULL)
370                 return rstat;
371
372         /* close the archive file */
373         if (priv->mlfi_fp != NULL && fclose(priv->mlfi_fp) == EOF)
374         {
375                 /* failed; we have to wait until later */
376                 rstat = SMFIS_TEMPFAIL;
377                 (void) unlink(priv->mlfi_fname);
378         }
379         else if (ok)
380         {
381                 /* add a header to the message announcing our presence */
382                 if (gethostname(host, sizeof host) < 0)
383                         snprintf(host, sizeof host, "localhost");
384                 p = strrchr(priv->mlfi_fname, '/');
385                 if (p == NULL)
386                         p = priv->mlfi_fname;
387                 else
388                         p++;
389                 snprintf(hbuf, sizeof hbuf, "%s@%s", p, host);
390                 smfi_addheader(ctx, "X-Archived", hbuf);
391         }
392         else
393         {
394                 /* message was aborted -- delete the archive file */
395                 (void) unlink(priv->mlfi_fname);
396         }
397
398         /* release private memory */
399         free(priv->mlfi_fname);
400         free(priv);
401         smfi_setpriv(ctx, NULL);
402
403         /* return status */
404         return rstat;
405 }
406
407 struct smfiDesc smfilter =
408 {
409         "SampleFilter", /* filter name */
410         SMFI_VERSION,   /* version code -- do not change */
411         SMFIF_ADDHDRS,  /* flags */
412         NULL,           /* connection info filter */
413         NULL,           /* SMTP HELO command filter */
414         mlfi_envfrom,   /* envelope sender filter */
415         NULL,           /* envelope recipient filter */
416         mlfi_header,    /* header filter */
417         mlfi_eoh,       /* end of header */
418         mlfi_body,      /* body block filter */
419         mlfi_eom,       /* end of message */
420         mlfi_abort,     /* message aborted */
421         mlfi_close      /* connection cleanup */
422 };
423
424
425 int
426 main(argc, argv)
427         int argc;
428         char *argv[];
429 {
430         bool setconn = false;
431         int c;
432         const char *args = "p:";
433
434         /* Process command line options */
435         while ((c = getopt(argc, argv, args)) != -1)
436         {
437                 switch (c)
438                 {
439                   case 'p':
440                         if (optarg == NULL || *optarg == '\0')
441                         {
442                                 (void) fprintf(stderr, "Illegal conn: %s\n",
443                                                optarg);
444                                 exit(EX_USAGE);
445                         }
446                         (void) smfi_setconn(optarg);
447                         setconn = true;
448                         break;
449
450                 }
451         }
452         if (!setconn)
453         {
454                 fprintf(stderr, "%s: Missing required -p argument\n", argv[0]);
455                 exit(EX_USAGE);
456         }
457         if (smfi_register(smfilter) == MI_FAILURE)
458         {
459                 fprintf(stderr, "smfi_register failed\n");
460                 exit(EX_UNAVAILABLE);
461         }
462         return smfi_main();
463 }
464
465 /* eof */
466
467 $Revision: 8.35.2.2 $, Last updated $Date: 2003/05/26 04:10:06 $