README revision 132943
111394SswallaceThis directory contains the source files for libmilter. 211394Sswallace 311394SswallaceThe sendmail Mail Filter API (Milter) is designed to allow third-party 411394Sswallaceprograms access to mail messages as they are being processed in order to 511394Sswallacefilter meta-information and content. 611394Sswallace 711394SswallaceThis README file describes the steps needed to compile and run a filter, 811394Sswallacethrough reference to a sample filter which is attached at the end of this 911394Sswallacefile. It is necessary to first build libmilter.a, which can be done by 1011394Sswallaceissuing the './Build' command in SRCDIR/libmilter . 1111394Sswallace 1211394SswallaceStarting with 8.13 sendmail is compiled by default with support for 1311394Sswallacethe milter API. 1411394Sswallace 1511394Sswallace+----------------+ 1611394Sswallace| SECURITY HINTS | 1711394Sswallace+----------------+ 1811394Sswallace 1911394SswallaceNote: we strongly recommend not to run any milter as root. Libmilter 2011394Sswallacedoes not need root access to communicate with sendmail. It is a 2111394Sswallacegood security practice to run a program only with root privileges 2211394Sswallaceif really necessary. A milter should probably check first whether 2311394Sswallaceit runs as root and refuse to start in that case. libmilter will 2411394Sswallacenot unlink a socket when running as root. 2511394Sswallace 2611394Sswallace+----------------------+ 2711394Sswallace| CONFIGURATION MACROS | 2811394Sswallace+----------------------+ 2911394Sswallace 3011394SswallaceLibmilter uses a set of C preprocessor macros to specify platform specific 3111394Sswallacefeatures of the C compiler and standard C libraries. 3211394Sswallace 3311394SswallaceSM_CONF_POLL 3411394Sswallace Set to 1 if poll(2) should be used instead of select(2). 3511394Sswallace 3611394Sswallace+-------------------+ 3711394Sswallace| BUILDING A FILTER | 3811394Sswallace+-------------------+ 3911394Sswallace 4011394SswallaceThe following command presumes that the sample code from the end of this 4111394SswallaceREADME is saved to a file named 'sample.c' and built in the local platform- 4211397Sswallacespecific build subdirectory (SRCDIR/obj.*/libmilter). 4311397Sswallace 4411394Sswallace cc -I../../include -o sample sample.c libmilter.a ../libsm/libsm.a -pthread 4511394Sswallace 4611394SswallaceIt is recommended that you build your filters in a location outside of 4711394Sswallacethe sendmail source tree. Modify the compiler include references (-I) 4811397Sswallaceand the library locations accordingly. Also, some operating systems may 4911397Sswallacerequire additional libraries. For example, SunOS 5.X requires '-lresolv 5011397Sswallace-lsocket -lnsl'. Depending on your operating system you may need a library 5111397Sswallaceinstead of the option -pthread, e.g., -lpthread. 5211397Sswallace 5311397SswallaceFilters must be thread-safe! Many operating systems now provide support for 5411397SswallacePOSIX threads in the standard C libraries. The compiler flag to link with 5511394Sswallacethreading support differs according to the compiler and linker used. Check 5611394Sswallacethe Makefile in your appropriate obj.*/libmilter build subdirectory if you 5711394Sswallaceare unsure of the local flag used. 5811394Sswallace 5911397SswallaceNote that since filters use threads, it may be necessary to alter per 6011397Sswallaceprocess limits in your filter. For example, you might look at using 6111394Sswallacesetrlimit() to increase the number of open file descriptors if your filter 6211394Sswallaceis going to be busy. 6311394Sswallace 6411394Sswallace 6511394Sswallace+----------------------------------------+ 6611394Sswallace| SPECIFYING FILTERS IN SENDMAIL CONFIGS | 6711394Sswallace+----------------------------------------+ 6811394Sswallace 6911394SswallaceFilters are specified with a key letter ``X'' (for ``eXternal''). 7011394Sswallace 7111394SswallaceFor example: 7211394Sswallace 7311394Sswallace Xfilter1, S=local:/var/run/f1.sock, F=R 7411394Sswallace Xfilter2, S=inet6:999@localhost, F=T, T=C:10m;S:1s;R:1s;E:5m 7511394Sswallace Xfilter3, S=inet:3333@localhost 7611394Sswallace 7711394Sswallacespecifies three filters. Filters can be specified in your .mc file using 7811394Sswallacethe following: 7911394Sswallace 8011394Sswallace INPUT_MAIL_FILTER(`filter1', `S=local:/var/run/f1.sock, F=R') 8111394Sswallace INPUT_MAIL_FILTER(`filter2', `S=inet6:999@localhost, F=T, T=C:10m;S:1s;R:1s;E:5m') 8211394Sswallace INPUT_MAIL_FILTER(`filter3', `S=inet:3333@localhost') 8311394Sswallace 8411394SswallaceThe first attaches to a Unix-domain socket in the /var/run directory; the 8511394Sswallacesecond uses an IPv6 socket on port 999 of localhost, and the third uses an 8611394SswallaceIPv4 socket on port 3333 of localhost. The current flags (F=) are: 8711394Sswallace 8811394Sswallace R Reject connection if filter unavailable 8911394Sswallace T Temporary fail connection if filter unavailable 9011394Sswallace 9111394SswallaceIf 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