/************************************************************ ** $Id: milter.c,v 0.5 2003/11/03 03:08:10 bcx Exp $ ** ** Copyright Bryan Costales and Perforce Software, Inc. 2003 ** ** This code is "open source" as defined by version 1.9 of ** the Open Source Definition from: ** ** http://www.opensource.org/docs/definition.php. ** ** Entry points defined in this file: ** ** All the milter entry points and: ** main(): ** Process command line an launch the milter. *************************************************************/ # define EXTERN extern # include "slow.h" /********************************************************** The tests to be made: Good deeds (green flags): mail delivered from sender mail delivered to sender Questionable deeds (yellow flags): mail received for multiple recipients mail received at secondary MX mail received for bad address (bounce) Bad deeds (red flags): attempt to relay attempt to pipeline SMTP commands illegally mail received for honeypot address mail received from bogus/mismatched sender domain There are also some events that can be logged from outside the MTA, so people can rat out a bad sender: sender imported from a blacklist (yellow flag) sender imported from a whitelist (green) sender failed open relay check (yellow) report of spam content from a user (yellow) ***********************************************************/ void free_priv(SMFICTX *ctx) { PRIV *priv = (PRIV *)smfi_getpriv(ctx); if (priv == NULL) return; if (priv->iptxt != NULL) (void) free(priv->iptxt); if (priv->hdrrcpts != NULL) (void) free_list(priv->hdrrcpts); (void) free((char *)priv); return; } /* ** Process Connect: ** ** This is after sendmail has accepted the connection, ** but before sendmail has sent its initial greeting. ** ** For the {client_resolv} test to work, you need to add the following ** to your sendmail mc configuration file: ** ** define(`confMILTER_MACROS_CONNECT', confMILTER_MACROS_CONNECT``,{client_resolve}'') ** */ sfsistat xxfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *ha) { PRIV *priv; struct in_addr in; char *sym; bool badfamily; /* * Set up the private (thread specific) data that will persist * throughout the connection. A connection may * contain multiple envelopes. */ priv = (PRIV *)try_alloc(__FILE__, __LINE__, sizeof(PRIV)); (void) memset((char *)priv, 0, sizeof(PRIV)); if (smfi_setpriv(ctx, (void *)priv) == MI_FAILURE) { milterr(Prog, __FILE__, __LINE__, TRUE, errno, "ABORTED", "Register private data"); } /* * Gather the IP number of the connecting host and * record it in the private data. */ badfamily = FALSE; if (ha == NULL || ha->sa_family != AF_INET) { /* * This is not an IPv4 address (sendmail may have * been run with the -bs command-line switch. * Since we cannot record it, we accept * the whole connection. */ badfamily = TRUE; setbit(BIT_NOTINET, priv->bits); /* XXX log what happened? */ } else { (void) memcpy(&(priv->ip), ha->sa_data, sizeof(PRIMARY_DATUM)); (void) memcpy(&in.s_addr, ha->sa_data, sizeof(in.s_addr)); priv->iptxt = try_strdup(__FILE__, __LINE__, inet_ntoa(in)); } /* * If this connection is from one of our MX hosts * save that information for future use, and * clear the saved ip data. */ if (is_in_list(priv->iptxt, MxHosts) == TRUE || is_in_list(hostname, MxHosts) == TRUE) { /* * Yes, from our MX server, so clear the IP * information. It will be later snarfed * from a Received: header. * Note this will only happen if the config * allows it. */ if (LookBackOne == TRUE) { priv->ip = (PRIMARY_DATUM)0; if (priv->iptxt != NULL) (void) free(priv->iptxt); priv->iptxt = NULL; setbit(BIT_FROMMX, priv->bits); } } /* * sendmail looks up the real host name, to get its * IP number. It then reverse looks up the IP number * to get the host name. If the two don't match, sendmail * consider the hostname forged. * * If the milter configuration file allows this * test, perform it. */ if (CheckForged == TRUE) { sym = smfi_getsymval(ctx, "{client_resolve}"); if (sym != NULL && strcasecmp(sym, "forged") == 0) setbit(BIT_FORGED, priv->bits); } /* * Hook for regression testing. If you change this * output, change test.c too. */ if (isdebug(BUG_CONNECT)) { (void)printf("%s [%s]: flags=%s%s%s\n", hostname==NULL?"<nil>":hostname, priv->iptxt==NULL?"<nil>":priv->iptxt, bitset(BIT_FROMMX, priv->bits)?"[MMX]":"", bitset(BIT_NOTINET, priv->bits)?"[NET]":"", bitset(BIT_FORGED, priv->bits)?"[FOR]":""); } /* * If this is a connection on standard input or some * other non-IPv4 network. Accept it, because we cannot * associate an IP number with it. */ if (badfamily == TRUE) return SMFIS_ACCEPT; return SMFIS_CONTINUE; } /* ** Process the HELO/EHLO command ** ** The argument contains the host name give in the ** HELO/EHLO command. This is the literal argument ** and cannot be trusted. ** ** Note to get the "advanced write" test to work, you need to patch ** V8.13 sendmail with the "patchs/sendmail.patch" file. Then paste ** the following define into your sendmail mc configuration file. ** define(`confMILTER_MACROS_HELO', confMILTER_MACROS_HELO``, {illegalpipe}'') ** */ sfsistat xxfi_helo(SMFICTX *ctx, char *host) { PRIV *priv; char *sym; /* we ignore the passed argument "host" */ /* * If the milter configuration file allow it, check for * an illegal advance write. */ if(AdvanceWrite == TRUE) { priv = (PRIV *)smfi_getpriv(ctx); if (priv == NULL) { milterr(Prog, __FILE__, __LINE__, TRUE, errno, "ABORTED", "Private data is NULL"); } sym = smfi_getsymval(ctx, "{illegalpipe}"); if (sym != NULL && strcasecmp(sym, "true") == 0) setbit(BIT_ADVANCEWRITE, priv->bits); } /* * Hook for regression testing. If you change this * output, change test.c too. */ if (isdebug(BUG_HELO)) { (void)printf("%s: flags=%s\n", host==NULL?"<nil>":host, bitset(BIT_ADVANCEWRITE, priv->bits)?"[PIP]":""); } return SMFIS_CONTINUE; } /* ** Process MAIL FROM: ** ** argv contains a single recipient address in argv[0] ** the rest of argv is the MAIL FROM: arguments. These are ** also captured in the macros passed by sendmail. ** ** This function is usually called only once. */ sfsistat xxfi_envfrom(SMFICTX *ctx, char **argv) { char host[MAXHOSTNAMELEN]; int herr; struct hostent res; char *cp, *ep; PRIV *priv; if (argv == NULL) return SMFIS_CONTINUE; /* * If the milter configuration file prevents this * test, skip it. */ if (CheckFromHeader == FALSE) return SMFIS_CONTINUE; priv = (PRIV *)smfi_getpriv(ctx); if (priv == NULL) { milterr(Prog, __FILE__, __LINE__, TRUE, errno, "ABORTED", "Private data is NULL"); } /* * Check the envelope sender's host. */ cp = strchr(argv[0], '@'); /* * Missing @ means either a local address (user) * or an empty angle brace pair (<>),which * is legal for bounced email. */ if (cp == NULL) { if (isdebug(BUG_MAILFROM)) { if (strcmp("<>", argv[0]) == 0) (void) printf("%s: okay bounce sender\n", argv[0]); else (void) printf("%s: okay local\n", argv[0]); } return SMFIS_CONTINUE; } ++cp; ep = strchr(cp, '>'); if (ep != NULL) { *ep = '\0'; --ep; } else ep = cp+strlen(cp); /* * A missing host part after the @ (i.e. "user@") is bad. */ if (cp >= ep) { setbit(BIT_BAD_FROM_HEAD, priv->bits); } else { /* * We don't count it an error if the lookup returns * a temporary error. */ if (gethostbyname_r(cp, &res, host, MAXHOSTNAMELEN, &herr) == NULL) { if (herr != TRY_AGAIN) setbit(BIT_BAD_FROM_HEAD, priv->bits); } } if (isdebug(BUG_MAILFROM)) (void) printf("%s: flags=%s\n", argv[0], bitset(BIT_BAD_FROM_HEAD, priv->bits)?"[FRM]":"" ); return SMFIS_CONTINUE; } /* ** Process the RCPT TO: recipient address. This may be called ** multiple times, once for each RCPT TO: command. ** Each envelope recipient is in argv[0]. */ sfsistat xxfi_envto(SMFICTX *ctx, char **argv) { PRIV *priv; char *cp, *ep; priv = (PRIV *)smfi_getpriv(ctx); if (priv == NULL) { milterr(Prog, __FILE__, __LINE__, TRUE, errno, "ABORTED", "Private data is NULL"); } cp = argv[0]; /* * Strip surrounding angle braces if any. */ if (*cp == '<') { ++cp; if ((ep = strchr(cp, '>')) != NULL) *ep ='0'; } /* * We reject any honeypot address, but count it. * Note that since sendmail will count it as a bad recipient, * we decrement the bad recipient count ahead of time. */ if (is_in_list(cp, HoneyHosts) == TRUE) { char buf[BUFSIZ]; setbit(BIT_GOT_HONEYPOT, priv->bits); if (isdebug(BUG_RCPTTO)) (void)printf("%s: flags=%s\n", argv[0], bitset(BIT_GOT_HONEYPOT,priv->bits)?"[HON]":""); ++(priv->honeyrcpts); --(priv->badrcpts); (void) snprintf(buf, BUFSIZ, "%s... User unknown", argv[0]); smfi_setreply(ctx, "550", "5.1.1", buf); return SMFIS_REJECT; } /* * If it is not a honey pot address, we count it as * good, with the caveat that a later milter or sendmail * may yet reject it. */ ++(priv->envrcpts); if (isdebug(BUG_RCPTTO)) (void)printf("%s: count=%d\n", argv[0], priv->envrcpts); return SMFIS_CONTINUE; } /* ** End of the envlope recipient list is marked by ** the start of the DATA phase with the collection ** of headers. Note that if there are no good recipients ** left in the recipeint list, sendmail will jump right ** to the xxfi_abort() routine instead. */ /* ** Process a header ** ** This routine is called once per header. ** This routine is usually called many times. ** In this routine we screen: ** XTrackHeader: to see if we already saw this message. ** To:, Cc:, and Bcc: to count header recipients ** Received (if BIT_FROMMX is set, see xxfi_connect) ** Message-Id: to detect bad Message-Ids */ sfsistat xxfi_header(SMFICTX *ctx, char *field, char *value) { PRIV *priv; char *cp; priv = (PRIV *)smfi_getpriv(ctx); if (priv == NULL) { milterr(Prog, __FILE__, __LINE__, TRUE, errno, "ABORTED", "Private data is NULL"); } /* * If the XTrackHeader appears, accept the message, * because this means it was already screened. */ if (XTrackHeader != NULL && strcasecmp(field, XTrackHeader) == 0) { if (isdebug(BUG_HEADER)) printf("%s: %s accepted\n", field, value); /* XXX log what happened? */ return SMFIS_ACCEPT; } /* * If this is a Received: header, and if we got this * message from one of our MX servers, try to snarf * the sending IP number. */ if (priv->ip == 0 && LookBackOne == TRUE && strcasecmp(field, "Received") == 0) { struct in_addr in; priv->ip = header_received(value); if (priv->ip == 0) { if (isdebug(BUG_HEADER)) printf("Received: not from our MX\n"); return SMFIS_CONTINUE; } /* * Got the sending IP number, so save it to * the text form too. */ (void) memcpy(&in.s_addr, &priv->ip, sizeof(in.s_addr)); priv->iptxt = try_strdup(__FILE__, __LINE__, inet_ntoa(in)); if (isdebug(BUG_HEADER)) printf("Received: our MX said %s\n", priv->iptxt); return SMFIS_CONTINUE; } /* * Is the header one that contains recipients? */ if (strcasecmp(field, "To") == 0 || strcasecmp(field, "Cc") == 0 || strcasecmp(field, "Bcc") == 0) { /* * Yes, so add to the list of header recipients. */ priv->hdrrcpts = header_recipient(priv->hdrrcpts, value); if (isdebug(BUG_HEADER)) { if (strcasecmp(field, "To") == 0) (void)print_list("To", priv->hdrrcpts, stdout); else (void) printf("%s: %s\n", field, (priv->hdrrcpts)[0]); } return SMFIS_CONTINUE; } if (CheckMessageId == TRUE && strcasecmp(field, "Message-Id") == 0) { priv->gotmid = TRUE; if (header_messageid(value) == FALSE) setbit(BIT_BAD_MSG_ID, priv->bits); if (isdebug(BUG_HEADER)) (void) printf("%s: %s flags=%s\n", field, value, bitset(BIT_BAD_MSG_ID, priv->bits)?"[MID]":""); } if (CheckFromHeader == TRUE && strcasecmp(field, "From") == 0) { char host[MAXHOSTNAMELEN]; int herr; struct hostent res; /* * Strip away the RFC 822 cruft from the address * and move the pointer to the first char following * the @ so that we can look up the host name. */ cp = deRFC822(value); if (cp != NULL && (cp = strchr(cp, '@')) != NULL && *(++cp) != '\0') { /* * We don't count it an error if the lookup returns * a temporary error. * Note that we cannot check if it is addressed to * the local host because we may be relaying to * other hosts or domains (perhaps as MX servers). * So we only check to see if it exists. */ if (gethostbyname_r(cp, &res, host, MAXHOSTNAMELEN, &herr) == NULL) { if (herr != TRY_AGAIN) setbit(BIT_BAD_FROM_HEAD, priv->bits); } } if (isdebug(BUG_HEADER)) (void) printf("%s: %s flags=%s\n", field, value, bitset(BIT_BAD_FROM_HEAD, priv->bits)?"[FRM]":""); } return SMFIS_CONTINUE; } /* ** After all headers have been processed, we can check ** to see if any were missing. ** Also we can count the header recipients here. */ sfsistat xxfi_eoh(SMFICTX *ctx) { PRIV *priv; char **rp; priv = (PRIV *)smfi_getpriv(ctx); if (priv == NULL) { milterr(Prog, __FILE__, __LINE__, TRUE, errno, "ABORTED", "Private data is NULL"); } /* * If there was no Message-ID header, we count this as bad too. */ if (CheckMessageId == TRUE && priv->gotmid == FALSE) setbit(BIT_BAD_MSG_ID, priv->bits); { if (isdebug(BUG_EOH)) (void) printf("eoh: flags=%s\n", bitset(BIT_BAD_MSG_ID, priv->bits)?"[MID]":""); } /* * Here we count up the number of header recipients. */ priv->nhdrrcpts = 0; for (rp = priv->hdrrcpts; *rp != NULL; ++rp) ++(priv->nhdrrcpts); priv->hdrrcpts = free_list(priv->hdrrcpts); if (isdebug(BUG_EOH)) (void) printf("eoh: nhdrrcpts=%d\n", priv->nhdrrcpts); return SMFIS_CONTINUE; } /* ** Gather up the information needed to write to the database. ** Do the database writes, and return what to do next. ** This is called from xxfi_eom and xxfi_abort. */ sfsistat push_to_db(SMFICTX *ctx, PRIV *priv) { SECONDARY_KEY k; SECONDARY_DATUM event; PRIMARY_DATUM t; (void)time(&t); event.eventmap = priv->bits; event.envrcpts = priv->envrcpts; event.headrcpts = priv->nhdrrcpts; event.honeyrcpts = priv->honeyrcpts; event.badrcpts = priv->badrcpts; if (event.headrcpts > MaxHeaderRcpts) setbit(BIT_TOO_MANY_HDR_RCPTS, event.eventmap); if (event.envrcpts > MaxRcptsPerEnvelope) setbit(BIT_TOO_MANY_ENV_RCPTS, event.eventmap); if (adddate_db(Dp1, Prog, priv->ip, t) != 0) milterr(Prog, __FILE__, __LINE__, TRUE, errno, "Primary database write", PrimaryDatabaseLoc); k.ip = priv->ip; k.date = t; if (ipputdateinfo_db(Dp2, Prog, &k, &event) != 0) milterr(Prog, __FILE__, __LINE__, TRUE, errno, "Secondary database write", SecondaryDatabaseLoc); (void) free_priv(ctx); (void) smfi_setpriv(ctx, NULL); return SMFIS_ACCEPT; } /* ** Process the end-of-envelope. Here we summarize everything and ** update the database. Note that we update the database here, ** because an earlier Milter may have rejected the message based ** on the body contents. ** ** Note to get the ${nbadrcpts} value, you need be running V8.13 sendmail ** and you need to paste the following define into your sendmail ** mc configuration file. Also note that this only gives an approximation ** of the number of bad envelope recipients. ** define(`confMILTER_MACROS_ENVRCPT', confMILTER_MACROS_ENVRCPT``, {nbadrcpts}'') ** */ sfsistat xxfi_eom(SMFICTX *ctx) { PRIV *priv; char *sym; priv = (PRIV *)smfi_getpriv(ctx); if (priv == NULL) { milterr(Prog, __FILE__, __LINE__, TRUE, errno, "ABORTED", "Private data is NULL"); } priv->badrcpts = 0; sym = smfi_getsymval(ctx, "{nbadrcpts}"); if (sym != NULL) priv->badrcpts = atoi(sym); if (isdebug(BUG_EOM)) { return SMFIS_ACCEPT; } return push_to_db(ctx, priv); } sfsistat xxfi_close(SMFICTX *ctx) { PRIV *priv; priv = (PRIV *)smfi_getpriv(ctx); if (priv != NULL) { (void) free_priv(ctx); (void) smfi_setpriv(ctx, NULL); } return SMFIS_ACCEPT; } sfsistat xxfi_abort(SMFICTX *ctx) { PRIV *priv; priv = (PRIV *)smfi_getpriv(ctx); if (priv != NULL) { setbit(BIT_MILTER_ABORTED, priv->bits); if (! isdebug(BUG_ABORT)) return push_to_db(ctx, priv); } return SMFIS_ACCEPT; } struct smfiDesc SmfiDesc = { NULL, /* filter name */ SMFI_VERSION, /* version code -- do not change */ 0, /* flags */ xxfi_connect, /* connection info filter */ xxfi_helo, /* SMTP HELO command filter */ xxfi_envfrom, /* envelope sender filter */ xxfi_envto, /* envelope recipient filter */ xxfi_header, /* header filter */ xxfi_eoh, /* end of header */ NULL, /* This milter ignores the body */ xxfi_eom, /* end of message */ xxfi_abort, /* message aborted */ xxfi_close /* connection cleanup */ }; int main(argc, argv) int argc; char *argv[]; { int c; char *cf = NULL; const char *version = "$Revision: 0.5 $"; bool force; int ret; int mode; char *xtest; char buf[BUFSIZ]; extern int errno; Prog = try_strdup(__FILE__, __LINE__, basename(argv[0])); SmfiDesc.xxfi_name = Prog; mode = MODE_RUN_AS_MILTER; if (strcmp(Prog, "slowadd")) /* synonym for -a */ mode = MODE_ADD_BY_HAND; else if (strcmp(Prog, "slowweed")) /* synonym for -w */ mode = MODE_GARBAGE_COLLECT; else if (strcmp(Prog, "slowdump")) /* synonym for -d */ mode = MODE_GARBAGE_COLLECT; /* Process command line options */ xtest = NULL; force = FALSE; Debugbits = 0L; while ((c = getopt(argc, argv, "dX:D:fC:V")) != -1) { switch (c) { /* Specify configuration file */ case 'C': cf = optarg; break; /* Dump the database to stdout */ case 'd': mode = MODE_DUMP_DATABASE; break; /* Print the program's version and exit */ case 'V': printf("%s: %s\n", Prog, version); exit(0); break; /* * To bootstrap the system, use -f to * force the databases to be created if * they do not already exists. */ case 'f': mode = MODE_BOOTSTRAP; force = TRUE; break; /* * Debugging is by catagory. e.g. -Dmx prints * the mx records found for the LocalHostName. */ case 'D': (void) set_debug(optarg); break; case 'X': xtest = optarg; break; } } /* Read the configuration file */ if (cf == NULL) cf = CFFILE; (void) setdefaults(); (void) readconf(Prog, cf); if (isdebug(BUG_DB)) test_db(xtest); /* * Open both databases. The db flags used are in * database.c. Here we only supply the file name. */ Dp1 = open_database(PrimaryDatabaseLoc, force); if (Dp1 == NULL) { milterr(Prog, __FILE__, __LINE__, FALSE, errno, "Primary database open", PrimaryDatabaseLoc); } Dp2 = open_database(SecondaryDatabaseLoc, force); if (Dp2 == NULL) { milterr(Prog, __FILE__, __LINE__, FALSE, errno, "Secondary database open", SecondaryDatabaseLoc); } if (mode == MODE_BOOTSTRAP) { printf("%s: successfully created.\n", PrimaryDatabaseLoc); printf("%s: successfully created.\n", SecondaryDatabaseLoc); return EX_OK; } if (mode == MODE_DUMP_DATABASE) { ret = dump_database(Dp1, Dp2, PrimaryDatabaseLoc, SecondaryDatabaseLoc, stdout); exit(ret); } /* * Read into memory, a list of the honeypot addresses. * Use -Dhoney to print what was read. */ (void) read_honey(); if (isdebug(BUG_HONEY)) { (void) print_list("Honey Pot Addresses", HoneyHosts, stdout); return EX_OK; } /* * ??? Should the mx list be empty on our MX servers? */ if (LocalHostName == NULL) { if (gethostname(buf, BUFSIZ) <0) milterr(Prog, __FILE__, __LINE__, FALSE, errno, "Define LocalHostName", "Get Host Name"); if (strchr(buf, '.') == NULL) milterr(Prog, __FILE__, __LINE__, FALSE, EPROTO, buf, "Not fully qualified"); MxHosts = getmx(buf, MxHosts); } else MxHosts = getmx(LocalHostName, MxHosts); /* * This cannot be used for regression testing. But it * is a handy way to see how mx lookups are being done. */ if (isdebug(BUG_MX)) { if (xtest == NULL) { (void) print_list(LocalHostName, MxHosts, stdout); return EX_OK; } MxHosts = NULL; MxHosts = getmx(xtest, MxHosts); (void) print_list(xtest, MxHosts, stdout); return EX_OK; } /* * Regression testing. */ if (isdebug(BUG_CONNECT)) test_connect(xtest); if (isdebug(BUG_HELO)) test_helo(xtest); if (isdebug(BUG_MAILFROM)) test_mailfrom(xtest); if (isdebug(BUG_MAILFROM)) test_mailfrom(xtest); if (isdebug(BUG_RCPTTO)) test_rcptto(xtest); if (isdebug(BUG_HEADER)) test_header(xtest); if (isdebug(BUG_EOH)) test_eoh(xtest); if (isdebug(BUG_EOM)) test_eom(xtest); if (isdebug(BUG_CLOSE)) test_close(xtest); if (isdebug(BUG_ABORT)) test_abort(xtest); if (mode == MODE_RUN_AS_MILTER) { if (smfi_register(SmfiDesc) == MI_FAILURE) { milterr(Prog, __FILE__, __LINE__, FALSE, errno, "ABORT", "Milter Listen failed"); } if (smfi_setconn(ListenPort) == MI_FAILURE) { milterr(Prog, __FILE__, __LINE__, FALSE, errno, "Start to listen", ListenPort); } if (smfi_opensocket() == MI_FAILURE) { milterr(Prog, __FILE__, __LINE__, FALSE, errno, "Start to listen", ListenPort); } (void) smfi_main(); } return EX_OK; }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#7 | 4222 | bryan_costales |
Massive rewrite to speed up the database writes. Using a single database now with duplicate keys where the keys are the IP numbers. Added a purge command and removed the garbage command. Fixed some leaking memory bugs and properly closed the database in a few places were it was not properly closed. Updated the docs to reflect this and bumped both the database version and release number. Running on a FreeBSD 3.x machine and a Solaris 9 machine. |
||
#6 | 4052 | bryan_costales |
Implimented: whitelisting AddMXHost for MX servers that lie Converted to thread safe DNS routines garbage collection RunAsUser and RunAsGroup for root startups rebuild the database summarize by IP number Finished all documentation. Moved release from alpha to beta |
||
#5 | 4030 | bryan_costales |
Finished documenting the configuration file Fixed a race condition and a core dump bug. Added hooks for whitelisting and IP aliasing Added support for Berkeley DB 4.2 Converted to htonl() and ntohl() Known Bugs: ip.db cannot be shared over NFS IP tracking from MX hosts can fail A RunAsUser config option is needed. |
||
#4 | 3998 | bryan_costales |
Brought the whole distribution up to V0.9 Added a huge abount of documentation. Added slowedit find Created startup scripts to launch for testing Fixed numerous bugs. Fixed a few portablity issues. Installed hooks for whitelisting and IP aliases. |
||
#3 | 3957 | bryan_costales |
Added rbl lookup support and testing for same. Folded in support for smfi_stop(). Added lots of slowedit commands Fixed a serious bug in MX lookups. Added to documentation. |
||
#2 | 3890 | bryan_costales |
This is the 0.6 release. The following have been added with the uses indicated: Source files: edit.c -- the slowedit functions compat.c -- missing system files Autoconf: configure.ac, makefile.am config.h aclocal.m4 acinclude.m4 build/ Documents: doc/ -- html and man(1) documents Testing: tests/ -- regressive testing TODO: -- revised to show actual progress |
||
#1 | 3838 | bryan_costales |
This is pre-release 0.5 (rcs numbering) which includes: The milter source files and Makefile A regressive testing subdirectory with Makefile and /bin/sh scripts. A patches subdirectory with a patch file for V8.13 sendmail All have been compiled and tested on a 64bit Sun Solaris 9 machine. |