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