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 12Starting with 8.13 sendmail is compiled by default with support for 13the milter API. 14 15+----------------+ 16| SECURITY HINTS | 17+----------------+ 18 19Note: we strongly recommend not to run any milter as root. Libmilter 20does not need root access to communicate with sendmail. It is a 21good security practice to run a program only with root privileges 22if really necessary. A milter should probably check first whether 23it runs as root and refuse to start in that case. libmilter will 24not unlink a socket when running as root. 25 26+----------------------+ 27| CONFIGURATION MACROS | 28+----------------------+ 29 30Libmilter uses a set of C preprocessor macros to specify platform specific 31features of the C compiler and standard C libraries. 32 33SM_CONF_POLL 34 Set to 1 if poll(2) should be used instead of select(2). 35 36+-------------------+ 37| BUILDING A FILTER | 38+-------------------+ 39 40The following command presumes that the sample code from the end of this 41README is saved to a file named 'sample.c' and built in the local platform- 42specific build subdirectory (SRCDIR/obj.*/libmilter). 43 44 cc -I../../include -o sample sample.c libmilter.a ../libsm/libsm.a -pthread 45 46It is recommended that you build your filters in a location outside of 47the sendmail source tree. Modify the compiler include references (-I) 48and the library locations accordingly. Also, some operating systems may 49require additional libraries. For example, SunOS 5.X requires '-lresolv 50-lsocket -lnsl'. Depending on your operating system you may need a library 51instead of the option -pthread, e.g., -lpthread. 52 53Filters must be thread-safe! Many operating systems now provide support for 54POSIX threads in the standard C libraries. The compiler flag to link with 55threading support differs according to the compiler and linker used. Check 56the Makefile in your appropriate obj.*/libmilter build subdirectory if you 57are unsure of the local flag used. 58 59Note that since filters use threads, it may be necessary to alter per 60process limits in your filter. For example, you might look at using 61setrlimit() to increase the number of open file descriptors if your filter 62is going to be busy. 63 64 65+----------------------------------------+ 66| SPECIFYING FILTERS IN SENDMAIL CONFIGS | 67+----------------------------------------+ 68 69Filters are specified with a key letter ``X'' (for ``eXternal''). 70 71For 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 77specifies three filters. Filters can be specified in your .mc file using 78the 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 84The first attaches to a Unix-domain socket in the /var/run directory; the 85second uses an IPv6 socket on port 999 of localhost, and the third uses an 86IPv4 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 91If neither F=R nor F=T is specified, the message is passed through sendmail 92in case of filter errors as if the failing filters were not present. 93 94Finally, you can override the default timeouts used by sendmail when 95talking to the filters using the T= equate. There are four fields inside 96of the T= equate: 97 98Letter 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 105Note the separator between each is a ';' as a ',' already separates equates 106and therefore can't separate timeouts. The default values (if not set in 107the config) are: 108 109T=C:5m;S:10s;R:10s;E:5m 110 111where 's' is seconds and 'm' is minutes. 112 113Which filters are invoked and their sequencing is handled by the 114InputMailFilters option. Note: if InputMailFilters is not defined no filters 115will be used. 116 117 O InputMailFilters=filter1, filter2, filter3 118 119This is is set automatically according to the order of the 120INPUT_MAIL_FILTER commands in your .mc file. Alternatively, you can 121reset its value by setting confINPUT_MAIL_FILTERS in your .mc file. 122This options causes the three filters to be called in the same order 123they were specified. It allows for possible future filtering on output 124(although this is not intended for this release). 125 126Also note that a filter can be defined without adding it to the input 127filter list by using MAIL_FILTER() instead of INPUT_MAIL_FILTER() in your 128.mc file. 129 130To test sendmail with the sample filter, the following might be added (in 131the 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 140Once you have compiled a filter, modified your .mc file and restarted 141the sendmail process, you will want to test that the filter performs as 142intended. 143 144The sample filter takes one argument -p, which indicates the local port 145on which to create a listening socket for the filter. Maintaining 146consistency with the suggested options for sendmail.cf, this would be the 147UNIX domain socket located in /var/run/f1.sock. 148 149 % ./sample -p local:/var/run/f1.sock 150 151If the sample filter returns immediately to a command line, there was either 152an error with your command or a problem creating the specified socket. 153Further logging can be captured through the syslogd daemon. Using the 154'netstat -a' command can ensure that your filter process is listening on 155the appropriate local socket. 156 157Email messages must be injected via SMTP to be filtered. There are two 158simple means of doing this; either using the 'sendmail -bs' command, or 159by telnetting to port 25 of the machine configured for milter. Once 160connected via one of these options, the session can be continued through 161the use of standard SMTP commands. 162 163% sendmail -bs 164220 test.sendmail.com ESMTP Sendmail 8.11.0/8.11.0; Tue, 10 Nov 1970 13:05:23 -0500 (EST) 165HELO localhost 166250 test.sendmail.com Hello testy@localhost, pleased to meet you 167MAIL From:<testy> 168250 2.1.0 <testy>... Sender ok 169RCPT To:<root> 170250 2.1.5 <root>... Recipient ok 171DATA 172354 Enter mail, end with "." on a line by itself 173From: testy@test.sendmail.com 174To: root@test.sendmail.com 175Subject: testing sample filter 176 177Sample body 178. 179250 2.0.0 dB73Zxi25236 Message accepted for delivery 180QUIT 181221 2.0.0 test.sendmail.com closing connection 182 183In the above example, the lines beginning with numbers are output by the 184mail server, and those without are your input. If everything is working 185properly, you will find a file in /tmp by the name of msg.XXXXXXXX (where 186the Xs represent any combination of letters and numbers). This file should 187contain the message body and headers from the test email entered above. 188 189If the sample filter did not log your test email, there are a number of 190methods to narrow down the source of the problem. Check your system 191logs written by syslogd and see if there are any pertinent lines. You 192may need to reconfigure syslogd to capture all relevant data. Additionally, 193the logging level of sendmail can be raised with the LogLevel option. 194See the sendmail(8) manual page for more information. 195 196 197+--------------+ 198| REQUIREMENTS | 199+--------------+ 200 201libmilter requires pthread support in the operating system. Moreover, it 202requires that the library functions it uses are thread safe; which is true 203for the operating systems libmilter has been developed and tested on. On 204some operating systems this requires special compile time options (e.g., 205not just -pthread). libmilter is currently known to work on (modulo problems 206in the pthread support of some specific versions): 207 208FreeBSD 3.x, 4.x 209SunOS 5.x (x >= 5) 210AIX 4.3.x 211HP UX 11.x 212Linux (recent versions/distributions) 213 214libmilter is currently not supported on: 215 216IRIX 6.x 217Ultrix 218 219Feedback about problems (and possible fixes) is welcome. 220 221+--------------------------+ 222| SOURCE FOR SAMPLE FILTER | 223+--------------------------+ 224 225Note that the filter below may not be thread safe on some operating 226systems. You should check your system man pages for the functions used 227below 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 241typedef int bool; 242# define false 0 243# define true 1 244#endif /* ! true */ 245 246struct mlfiPriv 247{ 248 char *mlfi_fname; 249 FILE *mlfi_fp; 250}; 251 252#define MLFIPRIV ((struct mlfiPriv *) smfi_getpriv(ctx)) 253 254extern sfsistat mlfi_cleanup(SMFICTX *, bool); 255 256sfsistat 257mlfi_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 297sfsistat 298mlfi_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 310sfsistat 311mlfi_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 321sfsistat 322mlfi_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 339sfsistat 340mlfi_eom(ctx) 341 SMFICTX *ctx; 342{ 343 return mlfi_cleanup(ctx, true); 344} 345 346sfsistat 347mlfi_close(ctx) 348 SMFICTX *ctx; 349{ 350 return SMFIS_ACCEPT; 351} 352 353sfsistat 354mlfi_abort(ctx) 355 SMFICTX *ctx; 356{ 357 return mlfi_cleanup(ctx, false); 358} 359 360sfsistat 361mlfi_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 409struct 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 427int 428main(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 $ 470