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.11.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. Note: if InputMailFilters is not defined no filters 101will be used. 102 103 O InputMailFilters=filter1, filter2, filter3 104 105This is is set automatically according to the order of the 106INPUT_MAIL_FILTER commands in your .mc file. Alternatively, you can 107reset its value by setting confINPUT_MAIL_FILTERS in your .mc file. 108This options causes the three filters to be called in the same order 109they were specified. It allows for possible future filtering on output 110(although this is not intended for this release). 111 112Also note that a filter can be defined without adding it to the input 113filter list by using MAIL_FILTER() instead of INPUT_MAIL_FILTER() in your 114.mc file. 115 116To test sendmail with the sample filter, the following might be added (in 117the appropriate locations) to your .mc file: 118 119 INPUT_MAIL_FILTER(`sample', `S=local:/var/run/f1.sock') 120 121 122+------------------+ 123| TESTING A FILTER | 124+------------------+ 125 126Once you have compiled a filter, modified your .mc file and restarted 127the sendmail process, you will want to test that the filter performs as 128intended. 129 130The sample filter takes one argument -p, which indicates the local port 131on which to create a listening socket for the filter. Maintaining 132consistency with the suggested options for sendmail.cf, this would be the 133UNIX domain socket located in /var/run/f1.sock. 134 135 % ./sample -p local:/var/run/f1.sock 136 137If the sample filter returns immediately to a command line, there was either 138an error with your command or a problem creating the specified socket. 139Further logging can be captured through the syslogd daemon. Using the 140'netstat -a' command can ensure that your filter process is listening on 141the appropriate local socket. 142 143Email messages must be injected via SMTP to be filtered. There are two 144simple means of doing this; either using the 'sendmail -bs' command, or 145by telnetting to port 25 of the machine configured for milter. Once 146connected via one of these options, the session can be continued through 147the use of standard SMTP commands. 148 149% sendmail -bs 150220 test.sendmail.com ESMTP Sendmail 8.11.0/8.11.0; Tue, 10 Nov 1970 13:05:23 -0500 (EST) 151HELO localhost 152250 test.sendmail.com Hello testy@localhost, pleased to meet you 153MAIL From:<testy> 154250 2.1.0 <testy>... Sender ok 155RCPT To:<root> 156250 2.1.5 <root>... Recipient ok 157DATA 158354 Enter mail, end with "." on a line by itself 159From: testy@test.sendmail.com 160To: root@test.sendmail.com 161Subject: testing sample filter 162 163Sample body 164. 165250 2.0.0 dB73Zxi25236 Message accepted for delivery 166QUIT 167221 2.0.0 test.sendmail.com closing connection 168 169In the above example, the lines beginning with numbers are output by the 170mail server, and those without are your input. If everything is working 171properly, you will find a file in /tmp by the name of msg.XXXXXXXX (where 172the Xs represent any combination of letters and numbers). This file should 173contain the message body and headers from the test email entered above. 174 175If the sample filter did not log your test email, there are a number of 176methods to narrow down the source of the problem. Check your system 177logs written by syslogd and see if there are any pertinent lines. You 178may need to reconfigure syslogd to capture all relevant data. Additionally, 179the logging level of sendmail can be raised with the LogLevel option. 180See the sendmail(8) manual page for more information. 181 182 183+--------------------------+ 184| SOURCE FOR SAMPLE FILTER | 185+--------------------------+ 186 187Note that the filter below may not be thread safe on some operating 188systems. You should check your system man pages for the functions used 189below to verify the functions are thread safe. 190 191/* A trivial filter that logs all email to a file. */ 192 193#include <sys/types.h> 194#include <stdio.h> 195#include <stdlib.h> 196#include <string.h> 197#include <sysexits.h> 198#include <unistd.h> 199 200#include "libmilter/mfapi.h" 201 202typedef int bool; 203 204#ifndef FALSE 205# define FALSE 0 206#endif /* ! FALSE*/ 207#ifndef TRUE 208# define TRUE 1 209#endif /* ! TRUE*/ 210 211struct mlfiPriv 212{ 213 char *mlfi_fname; 214 FILE *mlfi_fp; 215}; 216 217#define MLFIPRIV ((struct mlfiPriv *) smfi_getpriv(ctx)) 218 219extern sfsistat mlfi_cleanup(SMFICTX *, bool); 220 221sfsistat 222mlfi_envfrom(ctx, envfrom) 223 SMFICTX *ctx; 224 char **envfrom; 225{ 226 struct mlfiPriv *priv; 227 int fd = -1; 228 229 /* allocate some private memory */ 230 priv = malloc(sizeof *priv); 231 if (priv == NULL) 232 { 233 /* can't accept this message right now */ 234 return SMFIS_TEMPFAIL; 235 } 236 memset(priv, '\0', sizeof *priv); 237 238 /* open a file to store this message */ 239 priv->mlfi_fname = strdup("/tmp/msg.XXXXXXXX"); 240 if (priv->mlfi_fname == NULL) 241 { 242 free(priv); 243 return SMFIS_TEMPFAIL; 244 } 245 if ((fd = mkstemp(priv->mlfi_fname)) < 0 || 246 (priv->mlfi_fp = fdopen(fd, "w+")) == NULL) 247 { 248 if (fd >= 0) 249 (void) close(fd); 250 free(priv->mlfi_fname); 251 free(priv); 252 return SMFIS_TEMPFAIL; 253 } 254 255 /* save the private data */ 256 smfi_setpriv(ctx, priv); 257 258 /* continue processing */ 259 return SMFIS_CONTINUE; 260} 261 262sfsistat 263mlfi_header(ctx, headerf, headerv) 264 SMFICTX *ctx; 265 char *headerf; 266 char *headerv; 267{ 268 /* write the header to the log file */ 269 fprintf(MLFIPRIV->mlfi_fp, "%s: %s\r\n", headerf, headerv); 270 271 /* continue processing */ 272 return SMFIS_CONTINUE; 273} 274 275sfsistat 276mlfi_eoh(ctx) 277 SMFICTX *ctx; 278{ 279 /* output the blank line between the header and the body */ 280 fprintf(MLFIPRIV->mlfi_fp, "\r\n"); 281 282 /* continue processing */ 283 return SMFIS_CONTINUE; 284} 285 286sfsistat 287mlfi_body(ctx, bodyp, bodylen) 288 SMFICTX *ctx; 289 u_char *bodyp; 290 size_t bodylen; 291{ 292 /* output body block to log file */ 293 if (fwrite(bodyp, bodylen, 1, MLFIPRIV->mlfi_fp) <= 0) 294 { 295 /* write failed */ 296 (void) mlfi_cleanup(ctx, FALSE); 297 return SMFIS_TEMPFAIL; 298 } 299 300 /* continue processing */ 301 return SMFIS_CONTINUE; 302} 303 304sfsistat 305mlfi_eom(ctx) 306 SMFICTX *ctx; 307{ 308 return mlfi_cleanup(ctx, TRUE); 309} 310 311sfsistat 312mlfi_close(ctx) 313 SMFICTX *ctx; 314{ 315 return SMFIS_ACCEPT; 316} 317 318sfsistat 319mlfi_abort(ctx) 320 SMFICTX *ctx; 321{ 322 return mlfi_cleanup(ctx, FALSE); 323} 324 325sfsistat 326mlfi_cleanup(ctx, ok) 327 SMFICTX *ctx; 328 bool ok; 329{ 330 sfsistat rstat = SMFIS_CONTINUE; 331 struct mlfiPriv *priv = MLFIPRIV; 332 char *p; 333 char host[512]; 334 char hbuf[1024]; 335 336 if (priv == NULL) 337 return rstat; 338 339 /* close the archive file */ 340 if (priv->mlfi_fp != NULL && fclose(priv->mlfi_fp) == EOF) 341 { 342 /* failed; we have to wait until later */ 343 rstat = SMFIS_TEMPFAIL; 344 (void) unlink(priv->mlfi_fname); 345 } 346 else if (ok) 347 { 348 /* add a header to the message announcing our presence */ 349 if (gethostname(host, sizeof host) < 0) 350 strlcpy(host, "localhost", sizeof host); 351 p = strrchr(priv->mlfi_fname, '/'); 352 if (p == NULL) 353 p = priv->mlfi_fname; 354 else 355 p++; 356 snprintf(hbuf, sizeof hbuf, "%s@%s", p, host); 357 smfi_addheader(ctx, "X-Archived", hbuf); 358 } 359 else 360 { 361 /* message was aborted -- delete the archive file */ 362 (void) unlink(priv->mlfi_fname); 363 } 364 365 /* release private memory */ 366 free(priv->mlfi_fname); 367 free(priv); 368 smfi_setpriv(ctx, NULL); 369 370 /* return status */ 371 return rstat; 372} 373 374struct smfiDesc smfilter = 375{ 376 "SampleFilter", /* filter name */ 377 SMFI_VERSION, /* version code -- do not change */ 378 SMFIF_ADDHDRS, /* flags */ 379 NULL, /* connection info filter */ 380 NULL, /* SMTP HELO command filter */ 381 mlfi_envfrom, /* envelope sender filter */ 382 NULL, /* envelope recipient filter */ 383 mlfi_header, /* header filter */ 384 mlfi_eoh, /* end of header */ 385 mlfi_body, /* body block filter */ 386 mlfi_eom, /* end of message */ 387 mlfi_abort, /* message aborted */ 388 mlfi_close /* connection cleanup */ 389}; 390 391 392int 393main(argc, argv) 394 int argc; 395 char *argv[]; 396{ 397 int c; 398 const char *args = "p:"; 399 400 /* Process command line options */ 401 while ((c = getopt(argc, argv, args)) != -1) 402 { 403 switch (c) 404 { 405 case 'p': 406 if (optarg == NULL || *optarg == '\0') 407 { 408 (void) fprintf(stderr, "Illegal conn: %s\n", 409 optarg); 410 exit(EX_USAGE); 411 } 412 (void) smfi_setconn(optarg); 413 break; 414 415 } 416 } 417 if (smfi_register(smfilter) == MI_FAILURE) 418 { 419 fprintf(stderr, "smfi_register failed\n"); 420 exit(EX_UNAVAILABLE); 421 } 422 return smfi_main(); 423} 424 425/* eof */ 426 427$Revision: 8.9.2.1.2.17 $, Last updated $Date: 2001/04/11 18:32:58 $ 428