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