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 181/* A trivial filter that logs all email to a file. */ 182 183#include <sys/types.h> 184#include <stdio.h> 185#include <stdlib.h> 186#include <string.h> 187#include <sysexits.h> 188#include <unistd.h> 189 190#include "libmilter/mfapi.h" 191 192typedef int bool; 193 194#ifndef FALSE 195# define FALSE 0 196#endif /* ! FALSE*/ 197#ifndef TRUE 198# define TRUE 1 199#endif /* ! TRUE*/ 200 201struct mlfiPriv 202{ 203 char *mlfi_fname; 204 FILE *mlfi_fp; 205}; 206 207#define MLFIPRIV ((struct mlfiPriv *) smfi_getpriv(ctx)) 208 209extern sfsistat mlfi_cleanup(SMFICTX *, bool); 210 211sfsistat 212mlfi_envfrom(ctx, envfrom) 213 SMFICTX *ctx; 214 char **envfrom; 215{ 216 struct mlfiPriv *priv; 217 int fd; 218 219 /* allocate some private memory */ 220 priv = malloc(sizeof *priv); 221 if (priv == NULL) 222 { 223 /* can't accept this message right now */ 224 return SMFIS_TEMPFAIL; 225 } 226 memset(priv, '\0', sizeof *priv); 227 228 /* open a file to store this message */ 229 priv->mlfi_fname = strdup("/tmp/msg.XXXXXXXX"); 230 if (priv->mlfi_fname == NULL) 231 { 232 free(priv); 233 return SMFIS_TEMPFAIL; 234 } 235 if ((fd = mkstemp(priv->mlfi_fname)) < 0 || 236 (priv->mlfi_fp = fdopen(fd, "w+")) == NULL) 237 { 238 free(priv->mlfi_fname); 239 free(priv); 240 return SMFIS_TEMPFAIL; 241 } 242 243 /* save the private data */ 244 smfi_setpriv(ctx, priv); 245 246 /* continue processing */ 247 return SMFIS_CONTINUE; 248} 249 250sfsistat 251mlfi_header(ctx, headerf, headerv) 252 SMFICTX *ctx; 253 char *headerf; 254 char *headerv; 255{ 256 /* write the header to the log file */ 257 fprintf(MLFIPRIV->mlfi_fp, "%s: %s\r\n", headerf, headerv); 258 259 /* continue processing */ 260 return SMFIS_CONTINUE; 261} 262 263sfsistat 264mlfi_eoh(ctx) 265 SMFICTX *ctx; 266{ 267 /* output the blank line between the header and the body */ 268 fprintf(MLFIPRIV->mlfi_fp, "\r\n"); 269 270 /* continue processing */ 271 return SMFIS_CONTINUE; 272} 273 274sfsistat 275mlfi_body(ctx, bodyp, bodylen) 276 SMFICTX *ctx; 277 u_char *bodyp; 278 size_t bodylen; 279{ 280 /* output body block to log file */ 281 if (fwrite(bodyp, bodylen, 1, MLFIPRIV->mlfi_fp) <= 0) 282 { 283 /* write failed */ 284 (void) mlfi_cleanup(ctx, FALSE); 285 return SMFIS_TEMPFAIL; 286 } 287 288 /* continue processing */ 289 return SMFIS_CONTINUE; 290} 291 292sfsistat 293mlfi_eom(ctx) 294 SMFICTX *ctx; 295{ 296 return mlfi_cleanup(ctx, TRUE); 297} 298 299sfsistat 300mlfi_close(ctx) 301 SMFICTX *ctx; 302{ 303 return SMFIS_ACCEPT; 304} 305 306sfsistat 307mlfi_abort(ctx) 308 SMFICTX *ctx; 309{ 310 return mlfi_cleanup(ctx, FALSE); 311} 312 313sfsistat 314mlfi_cleanup(ctx, ok) 315 SMFICTX *ctx; 316 bool ok; 317{ 318 sfsistat rstat = SMFIS_CONTINUE; 319 struct mlfiPriv *priv = MLFIPRIV; 320 char *p; 321 char host[512]; 322 char hbuf[1024]; 323 324 if (priv == NULL) 325 return rstat; 326 327 /* close the archive file */ 328 if (priv->mlfi_fp != NULL && fclose(priv->mlfi_fp) == EOF) 329 { 330 /* failed; we have to wait until later */ 331 rstat = SMFIS_TEMPFAIL; 332 (void) unlink(priv->mlfi_fname); 333 } 334 else if (ok) 335 { 336 /* add a header to the message announcing our presence */ 337 if (gethostname(host, sizeof host) < 0) 338 strlcpy(host, "localhost", sizeof host); 339 p = strrchr(priv->mlfi_fname, '/'); 340 if (p == NULL) 341 p = priv->mlfi_fname; 342 else 343 p++; 344 snprintf(hbuf, sizeof hbuf, "%s@%s", p, host); 345 smfi_addheader(ctx, "X-Archived", hbuf); 346 } 347 else 348 { 349 /* message was aborted -- delete the archive file */ 350 (void) unlink(priv->mlfi_fname); 351 } 352 353 /* release private memory */ 354 free(priv->mlfi_fname); 355 free(priv); 356 smfi_setpriv(ctx, NULL); 357 358 /* return status */ 359 return rstat; 360} 361 362struct smfiDesc smfilter = 363{ 364 "SampleFilter", /* filter name */ 365 SMFI_VERSION, /* version code -- do not change */ 366 SMFIF_ADDHDRS, /* flags */ 367 NULL, /* connection info filter */ 368 NULL, /* SMTP HELO command filter */ 369 mlfi_envfrom, /* envelope sender filter */ 370 NULL, /* envelope recipient filter */ 371 mlfi_header, /* header filter */ 372 mlfi_eoh, /* end of header */ 373 mlfi_body, /* body block filter */ 374 mlfi_eom, /* end of message */ 375 mlfi_abort, /* message aborted */ 376 mlfi_close /* connection cleanup */ 377}; 378 379 380int 381main(argc, argv) 382 int argc; 383 char *argv[]; 384{ 385 int c; 386 const char *args = "p:"; 387 388 /* Process command line options */ 389 while ((c = getopt(argc, argv, args)) != -1) 390 { 391 switch (c) 392 { 393 case 'p': 394 if (optarg == NULL || *optarg == '\0') 395 { 396 (void) fprintf(stderr, "Illegal conn: %s\n", 397 optarg); 398 exit(EX_USAGE); 399 } 400 (void) smfi_setconn(optarg); 401 break; 402 403 } 404 } 405 if (smfi_register(smfilter) == MI_FAILURE) 406 { 407 fprintf(stderr, "smfi_register failed\n"); 408 exit(EX_UNAVAILABLE); 409 } 410 return smfi_main(); 411} 412 413/* eof */ 414 415$Revision: 8.9.2.1.2.12 $, Last updated $Date: 2000/09/19 19:40:13 $ 416