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