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