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