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