README revision 49218d4f8e4d84d1c08aeb267bcf6e451f2056dc
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews#
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews# Copyright 2004 Sun Microsystems, Inc. All rights reserved.
a908d41cb4b0658c958383375c5a8f0bcaaf843dFrancis Dupont# Use is subject to license terms.
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews#
0c27b3fe77ac1d5094ba3521e8142d9e7973133fMark Andrews# ident "%Z%%M% %I% %E% SMI"
0c27b3fe77ac1d5094ba3521e8142d9e7973133fMark Andrews#
0c27b3fe77ac1d5094ba3521e8142d9e7973133fMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsThe sendmail Mail Filter API (Milter) is designed to allow third-party
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsprograms access to mail messages as they are being processed in order to
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsfilter meta-information and content.
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsThis README file describes the steps needed to compile and run a filter,
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsthrough reference to a sample filter which is attached at the end of this
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsfile.
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsNote: if you want to write a milter in Java, then see
dbb012765c735ee0d82dedb116cdc7cf18957814Evan Hunthttp://sendmail-jilter.sourceforge.net/
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews+----------------+
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews| SECURITY HINTS |
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews+----------------+
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsNote: we strongly recommend not to run any milter as root. Libmilter
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsdoes not need root access to communicate with sendmail. It is a
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsgood security practice to run a program only with root privileges
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsif really necessary. A milter should probably check first whether
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsit runs as root and refuse to start in that case. libmilter will
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsnot unlink a socket when running as root.
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews+-------------------+
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews| BUILDING A FILTER |
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews+-------------------+
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsThe following command presumes that the sample code from the end of this
2ae159b376dac23870d8005563c585acf85a4b5aEvan HuntREADME is saved to a file named 'sample.c'.
2ae159b376dac23870d8005563c585acf85a4b5aEvan Hunt
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews cc -D_REENTRANT -o sample sample.c -lmilter
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsFilters must be thread-safe!
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsNote that since filters use threads, it may be necessary to alter per
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsprocess limits in your filter. For example, you might look at using
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewssetrlimit() to increase the number of open file descriptors if your filter
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsis going to be busy.
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews+----------------------------------------+
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews| SPECIFYING FILTERS IN SENDMAIL CONFIGS |
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews+----------------------------------------+
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsFilters are specified with a key letter ``X'' (for ``eXternal'').
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsFor example:
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews Xfilter1, S=local:/var/run/f1.sock, F=R
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews Xfilter2, S=inet6:999@localhost, F=T, T=C:10m;S:1s;R:1s;E:5m
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews Xfilter3, S=inet:3333@localhost
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsspecifies three filters. Filters can be specified in your .mc file using
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsthe following:
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews INPUT_MAIL_FILTER(`filter1', `S=local:/var/run/f1.sock, F=R')
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews INPUT_MAIL_FILTER(`filter2', `S=inet6:999@localhost, F=T, T=C:10m;S:1s;R:1s;E:5m')
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews INPUT_MAIL_FILTER(`filter3', `S=inet:3333@localhost')
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsThe first attaches to a Unix-domain socket in the /var/run directory; the
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewssecond uses an IPv6 socket on port 999 of localhost, and the third uses an
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsIPv4 socket on port 3333 of localhost. The current flags (F=) are:
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews R Reject connection if filter unavailable
ba751492fcc4f161a18b983d4f018a1a52938cb9Evan Hunt T Temporary fail connection if filter unavailable
ba751492fcc4f161a18b983d4f018a1a52938cb9Evan Hunt
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsIf neither F=R nor F=T is specified, the message is passed through sendmail
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsin case of filter errors as if the failing filters were not present.
9f5443280fcfd625a06f63a1b457ed2335840278Mark Andrews
9f5443280fcfd625a06f63a1b457ed2335840278Mark AndrewsFinally, you can override the default timeouts used by sendmail when
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewstalking to the filters using the T= equate. There are four fields inside
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsof the T= equate:
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsLetter Meaning
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews C Timeout for connecting to a filter (if 0, use system timeout)
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews S Timeout for sending information from the MTA to a filter
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews R Timeout for reading reply from the filter
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews E Overall timeout between sending end-of-message to filter
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews and waiting for the final acknowledgment
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsNote the separator between each is a ';' as a ',' already separates equates
1059bc2e42e8214f8b73d3b4cd181d8394a94a6aFrancis Dupontand therefore can't separate timeouts. The default values (if not set in
1059bc2e42e8214f8b73d3b4cd181d8394a94a6aFrancis Dupontthe config) are:
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsT=C:5m;S:10s;R:10s;E:5m
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewswhere 's' is seconds and 'm' is minutes.
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsWhich filters are invoked and their sequencing is handled by the
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsInputMailFilters option. Note: if InputMailFilters is not defined no filters
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewswill be used.
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews O InputMailFilters=filter1, filter2, filter3
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsThis is is set automatically according to the order of the
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsINPUT_MAIL_FILTER commands in your .mc file. Alternatively, you can
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsreset its value by setting confINPUT_MAIL_FILTERS in your .mc file.
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsThis options causes the three filters to be called in the same order
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsthey were specified. It allows for possible future filtering on output
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews(although this is not intended for this release).
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsAlso note that a filter can be defined without adding it to the input
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsfilter list by using MAIL_FILTER() instead of INPUT_MAIL_FILTER() in your
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews.mc file.
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsTo test sendmail with the sample filter, the following might be added (in
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsthe appropriate locations) to your .mc file:
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews INPUT_MAIL_FILTER(`sample', `S=local:/var/run/f1.sock')
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews+------------------+
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews| TESTING A FILTER |
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews+------------------+
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsOnce you have compiled a filter, modified your .mc file and restarted
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsthe sendmail process, you will want to test that the filter performs as
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsintended.
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsThe sample filter takes one argument -p, which indicates the local port
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewson which to create a listening socket for the filter. Maintaining
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsconsistency with the suggested options for sendmail.cf, this would be the
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsUNIX domain socket located in /var/run/f1.sock.
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews % ./sample -p local:/var/run/f1.sock
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsIf the sample filter returns immediately to a command line, there was either
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsan error with your command or a problem creating the specified socket.
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsFurther logging can be captured through the syslogd daemon. Using the
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews'netstat -a' command can ensure that your filter process is listening on
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsthe appropriate local socket.
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsEmail messages must be injected via SMTP to be filtered. There are two
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewssimple means of doing this; either using the 'sendmail -bs' command, or
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsby telnetting to port 25 of the machine configured for milter. Once
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsconnected via one of these options, the session can be continued through
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsthe use of standard SMTP commands.
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews% sendmail -bs
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews220 test.sendmail.com ESMTP Sendmail 8.11.0/8.11.0; Tue, 10 Nov 1970 13:05:23 -0500 (EST)
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsHELO localhost
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews250 test.sendmail.com Hello testy@localhost, pleased to meet you
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsMAIL From:<testy>
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews250 2.1.0 <testy>... Sender ok
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsRCPT To:<root>
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews250 2.1.5 <root>... Recipient ok
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan HuntDATA
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Hunt354 Enter mail, end with "." on a line by itself
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsFrom: testy@test.sendmail.com
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsTo: root@test.sendmail.com
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsSubject: testing sample filter
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsSample body
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews.
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews250 2.0.0 dB73Zxi25236 Message accepted for delivery
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsQUIT
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews221 2.0.0 test.sendmail.com closing connection
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Hunt
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan HuntIn the above example, the lines beginning with numbers are output by the
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Huntmail server, and those without are your input. If everything is working
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Huntproperly, you will find a file in /tmp by the name of msg.XXXXXXXX (where
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Huntthe Xs represent any combination of letters and numbers). This file should
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Huntcontain the message body and headers from the test email entered above.
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Hunt
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan HuntIf the sample filter did not log your test email, there are a number of
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Huntmethods to narrow down the source of the problem. Check your system
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Huntlogs written by syslogd and see if there are any pertinent lines. You
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Huntmay need to reconfigure syslogd to capture all relevant data. Additionally,
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Huntthe logging level of sendmail can be raised with the LogLevel option.
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark AndrewsSee the sendmail(8) manual page for more information.
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews+--------------------------+
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews| SOURCE FOR SAMPLE FILTER |
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews+--------------------------+
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews/* A trivial filter that logs all email to a file. */
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews#include <sys/types.h>
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews#include <stdio.h>
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews#include <stdlib.h>
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews#include <string.h>
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews#include <sysexits.h>
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews#include <unistd.h>
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews#include "libmilter/mfapi.h"
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews#ifndef true
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewstypedef int bool;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews# define false 0
2ae159b376dac23870d8005563c585acf85a4b5aEvan Hunt# define true 1
2ae159b376dac23870d8005563c585acf85a4b5aEvan Hunt#endif /* ! true */
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsstruct mlfiPriv
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews{
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews char *mlfi_fname;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews FILE *mlfi_fp;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews};
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews#define MLFIPRIV ((struct mlfiPriv *) smfi_getpriv(ctx))
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsextern sfsistat mlfi_cleanup(SMFICTX *, bool);
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewssfsistat
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsmlfi_envfrom(ctx, envfrom)
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews SMFICTX *ctx;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews char **envfrom;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews{
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews struct mlfiPriv *priv;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews int fd = -1;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews /* allocate some private memory */
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews priv = malloc(sizeof *priv);
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews if (priv == NULL)
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews {
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews /* can't accept this message right now */
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews return SMFIS_TEMPFAIL;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews }
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews memset(priv, '\0', sizeof *priv);
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews /* open a file to store this message */
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews priv->mlfi_fname = strdup("/tmp/msg.XXXXXXXX");
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews if (priv->mlfi_fname == NULL)
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews {
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews free(priv);
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews return SMFIS_TEMPFAIL;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews }
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews if ((fd = mkstemp(priv->mlfi_fname)) < 0 ||
ba751492fcc4f161a18b983d4f018a1a52938cb9Evan Hunt (priv->mlfi_fp = fdopen(fd, "w+")) == NULL)
ba751492fcc4f161a18b983d4f018a1a52938cb9Evan Hunt {
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews if (fd >= 0)
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews (void) close(fd);
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews free(priv->mlfi_fname);
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews free(priv);
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews return SMFIS_TEMPFAIL;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews }
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews /* save the private data */
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews smfi_setpriv(ctx, priv);
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews /* continue processing */
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews return SMFIS_CONTINUE;
1059bc2e42e8214f8b73d3b4cd181d8394a94a6aFrancis Dupont}
1059bc2e42e8214f8b73d3b4cd181d8394a94a6aFrancis Dupont
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewssfsistat
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsmlfi_header(ctx, headerf, headerv)
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews SMFICTX *ctx;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews char *headerf;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews char *headerv;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews{
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews /* write the header to the log file */
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews fprintf(MLFIPRIV->mlfi_fp, "%s: %s\r\n", headerf, headerv);
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews /* continue processing */
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews return SMFIS_CONTINUE;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews}
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewssfsistat
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsmlfi_eoh(ctx)
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews SMFICTX *ctx;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews{
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews /* output the blank line between the header and the body */
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews fprintf(MLFIPRIV->mlfi_fp, "\r\n");
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews /* continue processing */
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews return SMFIS_CONTINUE;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews}
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewssfsistat
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsmlfi_body(ctx, bodyp, bodylen)
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews SMFICTX *ctx;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews u_char *bodyp;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews size_t bodylen;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews{
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews /* output body block to log file */
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews if (fwrite(bodyp, bodylen, 1, MLFIPRIV->mlfi_fp) == 0)
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews {
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews /* write failed */
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews (void) mlfi_cleanup(ctx, false);
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews return SMFIS_TEMPFAIL;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews }
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews /* continue processing */
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews return SMFIS_CONTINUE;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews}
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewssfsistat
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsmlfi_eom(ctx)
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews SMFICTX *ctx;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews{
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews return mlfi_cleanup(ctx, true);
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews}
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewssfsistat
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsmlfi_close(ctx)
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews SMFICTX *ctx;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews{
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews return SMFIS_ACCEPT;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews}
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewssfsistat
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsmlfi_abort(ctx)
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews SMFICTX *ctx;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews{
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews return mlfi_cleanup(ctx, false);
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews}
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Hunt
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Huntsfsistat
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrewsmlfi_cleanup(ctx, ok)
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews SMFICTX *ctx;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews bool ok;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews{
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews sfsistat rstat = SMFIS_CONTINUE;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews struct mlfiPriv *priv = MLFIPRIV;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews char *p;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews char host[512];
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Hunt char hbuf[1024];
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Hunt
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Hunt if (priv == NULL)
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Hunt return rstat;
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Hunt
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Hunt /* close the archive file */
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Hunt if (priv->mlfi_fp != NULL && fclose(priv->mlfi_fp) == EOF)
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Hunt {
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Hunt /* failed; we have to wait until later */
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Hunt rstat = SMFIS_TEMPFAIL;
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Hunt (void) unlink(priv->mlfi_fname);
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Hunt }
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews else if (ok)
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews {
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews /* add a header to the message announcing our presence */
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews if (gethostname(host, sizeof host) < 0)
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews snprintf(host, sizeof host, "localhost");
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews p = strrchr(priv->mlfi_fname, '/');
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews if (p == NULL)
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews p = priv->mlfi_fname;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews else
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews p++;
343aeac7176d28c4a1b9d246b1f7311b4cd5da7dFrancis Dupont snprintf(hbuf, sizeof hbuf, "%s@%s", p, host);
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Hunt smfi_addheader(ctx, "X-Archived", hbuf);
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews }
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews else
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews {
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews /* message was aborted -- delete the archive file */
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews (void) unlink(priv->mlfi_fname);
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews }
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews /* release private memory */
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews free(priv->mlfi_fname);
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Hunt free(priv);
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews smfi_setpriv(ctx, NULL);
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Hunt
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews /* return status */
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews return rstat;
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Hunt}
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
ba751492fcc4f161a18b983d4f018a1a52938cb9Evan Huntstruct smfiDesc smfilter =
ba751492fcc4f161a18b983d4f018a1a52938cb9Evan Hunt{
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews "SampleFilter", /* filter name */
343aeac7176d28c4a1b9d246b1f7311b4cd5da7dFrancis Dupont SMFI_VERSION, /* version code -- do not change */
fc63119c8b7aa8827fad9e3e45e50c69bc2630e8Francis Dupont SMFIF_ADDHDRS, /* flags */
343aeac7176d28c4a1b9d246b1f7311b4cd5da7dFrancis Dupont NULL, /* connection info filter */
343aeac7176d28c4a1b9d246b1f7311b4cd5da7dFrancis Dupont NULL, /* SMTP HELO command filter */
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews mlfi_envfrom, /* envelope sender filter */
12bf5d4796505b4c20680531da96a31e6c2c1144Evan Hunt NULL, /* envelope recipient filter */
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Hunt mlfi_header, /* header filter */
84f95ddb2572641022619950a211aff49e331c98Mukund Sivaraman mlfi_eoh, /* end of header */
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Hunt mlfi_body, /* body block filter */
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews mlfi_eom, /* end of message */
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews mlfi_abort, /* message aborted */
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews mlfi_close /* connection cleanup */
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews};
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
cf24cbd837617c5cb89f04ba97b262be21a925bfEvan Hunt
cf24cbd837617c5cb89f04ba97b262be21a925bfEvan Huntint
821ff5e8fa1df2e09fea157ee3e298eef6cf4ec9Evan Huntmain(argc, argv)
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews int argc;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews char *argv[];
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews{
1bb2f53b9f74a8ca9812cbe9243ef41190b4da14Evan Hunt bool setconn = false;
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Hunt int c;
98922b2b2b024dcca25be7c220cf3b16b1e6c4b5Evan Hunt const char *args = "p:";
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews /* Process command line options */
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews while ((c = getopt(argc, argv, args)) != -1)
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews {
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews switch (c)
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews {
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews case 'p':
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews if (optarg == NULL || *optarg == '\0')
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews {
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews (void) fprintf(stderr, "Illegal conn: %s\n",
343aeac7176d28c4a1b9d246b1f7311b4cd5da7dFrancis Dupont optarg);
343aeac7176d28c4a1b9d246b1f7311b4cd5da7dFrancis Dupont exit(EX_USAGE);
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews }
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews (void) smfi_setconn(optarg);
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews setconn = true;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews break;
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews }
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews }
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews if (!setconn)
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews {
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews fprintf(stderr, "%s: Missing required -p argument\n", argv[0]);
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews exit(EX_USAGE);
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews }
343aeac7176d28c4a1b9d246b1f7311b4cd5da7dFrancis Dupont if (smfi_register(smfilter) == MI_FAILURE)
343aeac7176d28c4a1b9d246b1f7311b4cd5da7dFrancis Dupont {
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews fprintf(stderr, "smfi_register failed\n");
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews exit(EX_UNAVAILABLE);
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews }
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews return smfi_main();
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews}
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews/* eof */
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews$Revision: 8.41 $, Last updated $Date: 2005/04/27 22:47:42 $
c3c8823fed039b3a2b8e5ca8bc2f3301d1dd840eMark Andrews