README revision 64562
1127342SmlaierThis directory contains the source files for libmilter. 2127342Smlaier 3127342SmlaierThe sendmail Mail Filter API (Milter) is designed to allow third-party 4127342Smlaierprograms access to mail messages as they are being processed in order to 5127342Smlaierfilter meta-information and content. 6127342Smlaier 7195026SdougbThis README file describes the steps needed to compile and run a filter, 8150836Syarthrough reference to a sample filter which is attached at the end of this 9136224Smtmfile. It is necessary to first build libmilter.a, which can be done by 10127342Smlaierissuing the './Build' command in SRCDIR/libmilter . 11127342Smlaier 12127342SmlaierNOTE: Both libmilter and the callouts in sendmail are marked as an FFR (For 13127342SmlaierFuture Release). If you intend to use them in 8.10.X, you must compiled 14230099Sdougbboth libmilter and sendmail with -D_FFR_MILTER defined. You can do this by 15127342Smlaieradding the following to your devtools/Site/site.config.m4 file: 16127342Smlaier 17127342Smlaier dnl Milter 18136942Spjd APPENDDEF(`conf_sendmail_ENVDEF', `-D_FFR_MILTER=1') 19127342Smlaier APPENDDEF(`conf_libmilter_ENVDEF', `-D_FFR_MILTER=1') 20127342Smlaier 21127342SmlaierYou will also need to define _FFR_MILTER when building your .cf file using 22222007Shrsm4. 23150839Syar 24165683Syar+-------------------+ 25127342Smlaier| BUILDING A FILTER | 26127342Smlaier+-------------------+ 27127342Smlaier 28197947SdougbThe following command presumes that the sample code from the end of this 29159243SobrienREADME is saved to a file named 'sample.c' and built in the local platform- 30150839Syarspecific build subdirectory (SRCDIR/obj.*/libmilter). 31150839Syar 32216499Skevlo cc -I../../sendmail -I../../include -o sample sample.c libmilter.a ../libsmutil/libsmutil.a -pthread 33130954Smlaier 34197947SdougbIt is recommended that you build your filters in a location outside of 35127342Smlaierthe sendmail source tree. Modify the compiler include references (-I) 36127342Smlaierand the library locations accordingly. Also, some operating systems may 37127342Smlaierrequire additional libraries. For example, SunOS 5.X requires '-lresolv 38127342Smlaier-lsocket -lnsl'. Depending on your OS you may need a library instead 39150839Syarof the option -pthread, e.g., -lpthread. 40197947Sdougb 41216499SkevloFilters must be thread-safe! Many operating systems now provide support for 42197947SdougbPOSIX threads in the standard C libraries. The compiler flag to link with 43127342Smlaierthreading support differs according to the compiler and linker used. Check 44127342Smlaierthe Makefile in your appropriate obj.*/libmilter build subdirectory if you 45127342Smlaierare unsure of the local flag used. 46136942Spjd 47136942Spjd 48136942Spjd+----------------------------------------+ 49150839Syar| SPECIFYING FILTERS IN SENDMAIL CONFIGS | 50136942Spjd+----------------------------------------+ 51136942Spjd 52127342SmlaierFilters are specified with a key letter ``X'' (for ``eXternal''). 53127342Smlaier 54127342SmlaierFor example: 55150839Syar 56144638Sseanc Xfilter1, S=local:/var/run/f1.sock, F=R 57144638Sseanc Xfilter2, S=inet6:999@localhost, F=T, T=S:1s;R:1s;E:5m 58150839Syar Xfilter3, S=inet:3333@localhost 59150839Syar 60127342Smlaierspecifies three filters. Filters can be specified in your .mc file using 61127342Smlaierthe following: 62127342Smlaier 63127342Smlaier INPUT_MAIL_FILTER(`filter1', `S=local:/var/run/f1.sock, F=R') 64150839Syar INPUT_MAIL_FILTER(`filter2', `S=inet6:999@localhost, F=T, T=S:1s;R:1s;E:5m') 65127342Smlaier INPUT_MAIL_FILTER(`filter3', `S=inet:3333@localhost') 66127342Smlaier 67127342SmlaierThe first attaches to a Unix-domain socket in the /var/run directory; the 68127342Smlaiersecond uses an IPv6 socket on port 999 of localhost, and the third uses an 69150839SyarIPv4 socket on port 3333 of localhost. The current flags (F=) are: 70127342Smlaier 71127342Smlaier R Reject connection if filter unavailable 72127342Smlaier T Temporary fail connection if filter unavailable 73 74Finally, you can override the default timeouts used by sendmail when 75talking to the filters using the T= equate. There are three fields inside 76of the T= equate: 77 78Letter Meaning 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 the config) are: 86 87T=S:10s;R:10s;E:5m 88 89where 's' is seconds and 'm' is minutes. 90 91Actual sequencing is handled by the InputMailFilters option which is set 92automatically according to the order of the INPUT_MAIL_FILTER commands 93in your .mc file. Alternatively, you can reset its value by setting 94confINPUT_MAIL_FILTERS in your .mc file. This options causes the three 95filters to be called in the same order they were specified. It allows 96for possible future filtering on output (although this is not intended 97for this release). 98 99Also note that a filter can be defined without adding it to the input 100filter list by using MAIL_FILTER() instead of INPUT_MAIL_FILTER() in your 101.mc file. 102 103To test sendmail with the sample filter, the following might be added (in 104the appropriate locations) to your .mc file: 105 106 INPUT_MAIL_FILTER(`sample', `S=local:/var/run/f1.sock') 107 108 109+------------------+ 110| TESTING A FILTER | 111+------------------+ 112 113Once you have compiled a filter, modified your .mc file and restarted 114the sendmail process, you will want to test that the filter performs as 115intended. 116 117The sample filter takes one argument -p, which indicates the local port 118on which to create a listening socket for the filter. Maintaining 119consistency with the suggested options for sendmail.cf, this would be the 120UNIX domain socket located in /var/run/f1.sock. 121 122 % ./sample -p local:/var/run/f1.sock 123 124If the sample filter returns immediately to a command line, there was either 125an error with your command or a problem creating the specified socket. 126Further logging can be captured through the syslogd daemon. Using the 127'netstat -a' command can ensure that your filter process is listening on 128the appropriate local socket. 129 130Email messages must be injected via SMTP to be filtered. There are two 131simple means of doing this; either using the 'sendmail -bs' command, or 132by telnetting to port 25 of the machine configured for milter. Once 133connected via one of these options, the session can be continued through 134the use of standard SMTP commands. 135 136% sendmail -bs 137220 test.sendmail.com ESMTP Sendmail 8.10.0.Beta8/8.10.0.Beta8; Mon, 6 Dec 1999 19:34:23 -0800 (PST) 138HELO localhost 139250 test.sendmail.com Hello testy@localhost, pleased to meet you 140MAIL From:<testy> 141250 2.1.0 <testy>... Sender ok 142RCPT To:<root> 143250 2.1.5 <root>... Recipient ok 144DATA 145354 Enter mail, end with "." on a line by itself 146From: testy@test.sendmail.com 147To: root@test.sendmail.com 148Subject: testing sample filter 149 150Sample body 151. 152250 2.0.0 dB73Zxi25236 Message accepted for delivery 153QUIT 154221 2.0.0 test.sendmail.com closing connection 155 156In the above example, the lines beginning with numbers are output by the 157mail server, and those without are your input. If everything is working 158properly, you will find a file in /tmp by the name of msg.XXXXXXXX (where 159the Xs represent any combination of letters and numbers). This file should 160contain the message body and headers from the test email entered above. 161 162If the sample filter did not log your test email, there are a number of 163methods to narrow down the source of the problem. Check your system 164logs written by syslogd and see if there are any pertinent lines. You 165may need to reconfigure syslogd to capture all relevant data. Additionally, 166the logging level of sendmail can be raised with the LogLevel option. 167See the sendmail(8) manual page for more information. 168 169 170+--------------------------+ 171| SOURCE FOR SAMPLE FILTER | 172+--------------------------+ 173 174/* A trivial filter that logs all email to a file. */ 175 176#include <sys/types.h> 177#include <stdio.h> 178#include <stdlib.h> 179#include <string.h> 180#include <sysexits.h> 181#include <unistd.h> 182 183#include "libmilter/mfapi.h" 184 185typedef int bool; 186 187#ifndef FALSE 188# define FALSE 0 189#endif /* ! FALSE*/ 190#ifndef TRUE 191# define TRUE 1 192#endif /* ! TRUE*/ 193 194struct mlfiPriv 195{ 196 char *mlfi_fname; 197 FILE *mlfi_fp; 198}; 199 200#define MLFIPRIV ((struct mlfiPriv *) smfi_getpriv(ctx)) 201 202extern sfsistat mlfi_cleanup(SMFICTX *, bool); 203 204sfsistat 205mlfi_envfrom(ctx, envfrom) 206 SMFICTX *ctx; 207 char **envfrom; 208{ 209 struct mlfiPriv *priv; 210 int fd; 211 212 /* allocate some private memory */ 213 priv = malloc(sizeof *priv); 214 if (priv == NULL) 215 { 216 /* can't accept this message right now */ 217 return SMFIS_TEMPFAIL; 218 } 219 memset(priv, '\0', sizeof *priv); 220 221 /* open a file to store this message */ 222 priv->mlfi_fname = strdup("/tmp/msg.XXXXXXXX"); 223 if (priv->mlfi_fname == NULL) 224 { 225 free(priv); 226 return SMFIS_TEMPFAIL; 227 } 228 if ((fd = mkstemp(priv->mlfi_fname)) < 0 || 229 (priv->mlfi_fp = fdopen(fd, "w+")) == NULL) 230 { 231 free(priv->mlfi_fname); 232 free(priv); 233 return SMFIS_TEMPFAIL; 234 } 235 236 /* save the private data */ 237 smfi_setpriv(ctx, priv); 238 239 /* continue processing */ 240 return SMFIS_CONTINUE; 241} 242 243sfsistat 244mlfi_header(ctx, headerf, headerv) 245 SMFICTX *ctx; 246 char *headerf; 247 char *headerv; 248{ 249 /* write the header to the log file */ 250 fprintf(MLFIPRIV->mlfi_fp, "%s: %s\r\n", headerf, headerv); 251 252 /* continue processing */ 253 return SMFIS_CONTINUE; 254} 255 256sfsistat 257mlfi_eoh(ctx) 258 SMFICTX *ctx; 259{ 260 /* output the blank line between the header and the body */ 261 fprintf(MLFIPRIV->mlfi_fp, "\r\n"); 262 263 /* continue processing */ 264 return SMFIS_CONTINUE; 265} 266 267sfsistat 268mlfi_body(ctx, bodyp, bodylen) 269 SMFICTX *ctx; 270 u_char *bodyp; 271 size_t bodylen; 272{ 273 /* output body block to log file */ 274 if (fwrite(bodyp, bodylen, 1, MLFIPRIV->mlfi_fp) <= 0) 275 { 276 /* write failed */ 277 (void) mlfi_cleanup(ctx, FALSE); 278 return SMFIS_TEMPFAIL; 279 } 280 281 /* continue processing */ 282 return SMFIS_CONTINUE; 283} 284 285sfsistat 286mlfi_eom(ctx) 287 SMFICTX *ctx; 288{ 289 return mlfi_cleanup(ctx, TRUE); 290} 291 292sfsistat 293mlfi_close(ctx) 294 SMFICTX *ctx; 295{ 296 return SMFIS_ACCEPT; 297} 298 299sfsistat 300mlfi_abort(ctx) 301 SMFICTX *ctx; 302{ 303 return mlfi_cleanup(ctx, FALSE); 304} 305 306sfsistat 307mlfi_cleanup(ctx, ok) 308 SMFICTX *ctx; 309 bool ok; 310{ 311 sfsistat rstat = SMFIS_CONTINUE; 312 struct mlfiPriv *priv = MLFIPRIV; 313 char *p; 314 char host[512]; 315 char hbuf[1024]; 316 317 if (priv == NULL) 318 return rstat; 319 320 /* close the archive file */ 321 if (priv->mlfi_fp != NULL && fclose(priv->mlfi_fp) == EOF) 322 { 323 /* failed; we have to wait until later */ 324 rstat = SMFIS_TEMPFAIL; 325 (void) unlink(priv->mlfi_fname); 326 } 327 else if (ok) 328 { 329 /* add a header to the message announcing our presence */ 330 if (gethostname(host, sizeof host) < 0) 331 strlcpy(host, "localhost", sizeof host); 332 p = strrchr(priv->mlfi_fname, '/'); 333 if (p == NULL) 334 p = priv->mlfi_fname; 335 else 336 p++; 337 snprintf(hbuf, sizeof hbuf, "%s@%s", p, host); 338 smfi_addheader(ctx, "X-Archived", hbuf); 339 } 340 else 341 { 342 /* message was aborted -- delete the archive file */ 343 (void) unlink(priv->mlfi_fname); 344 } 345 346 /* release private memory */ 347 free(priv->mlfi_fname); 348 free(priv); 349 smfi_setpriv(ctx, NULL); 350 351 /* return status */ 352 return rstat; 353} 354 355struct smfiDesc smfilter = 356{ 357 "SampleFilter", /* filter name */ 358 SMFI_VERSION, /* version code -- do not change */ 359 SMFIF_ADDHDRS, /* flags */ 360 NULL, /* connection info filter */ 361 NULL, /* SMTP HELO command filter */ 362 mlfi_envfrom, /* envelope sender filter */ 363 NULL, /* envelope recipient filter */ 364 mlfi_header, /* header filter */ 365 mlfi_eoh, /* end of header */ 366 mlfi_body, /* body block filter */ 367 mlfi_eom, /* end of message */ 368 mlfi_abort, /* message aborted */ 369 mlfi_close /* connection cleanup */ 370}; 371 372 373int 374main(argc, argv) 375 int argc; 376 char *argv[]; 377{ 378 int c; 379 const char *args = "p:"; 380 381 /* Process command line options */ 382 while ((c = getopt(argc, argv, args)) != -1) 383 { 384 switch (c) 385 { 386 case 'p': 387 if (optarg == NULL || *optarg == '\0') 388 { 389 (void) fprintf(stderr, "Illegal conn: %s\n", 390 optarg); 391 exit(EX_USAGE); 392 } 393 (void) smfi_setconn(optarg); 394 break; 395 396 } 397 } 398 if (smfi_register(smfilter) == MI_FAILURE) 399 { 400 fprintf(stderr, "smfi_register failed\n"); 401 exit(EX_UNAVAILABLE); 402 } 403 return smfi_main(); 404} 405 406/* eof */ 407 408$Revision: 8.9.2.1.2.8 $, Last updated $Date: 2000/07/18 15:43:26 $ 409