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