/************************************************************ ** $Id: milter.c,v 0.6 2003/11/09 17:25:39 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" 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)); priv->hdrrcpts = NULL; 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; set_bit(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)); # ifndef WORDS_BIGENDIAN in.s_addr = revbytes(in.s_addr); # endif 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; set_bit(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) set_bit(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, isbitset(BIT_FROMMX, priv->bits)?"[MMX]":"", isbitset(BIT_NOTINET, priv->bits)?"[NET]":"", isbitset(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" */ priv = (PRIV *)smfi_getpriv(ctx); if (priv == NULL) { milterr(Prog, __FILE__, __LINE__, TRUE, errno, "ABORTED", "Private data is NULL"); } /* * If the milter configuration file allow it, check for * an illegal advance write. */ if(AdvanceWrite == TRUE) { sym = smfi_getsymval(ctx, "{illegalpipe}"); if (sym != NULL && strcasecmp(sym, "true") == 0) set_bit(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, isbitset(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 *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) { set_bit(BIT_BAD_FROM_HEAD, priv->bits); } else { /* * We don't count it an error if the lookup returns * a temporary error. */ if (gethostbyname(cp) == NULL) { if (h_errno != TRY_AGAIN) set_bit(BIT_BAD_FROM_HEAD, priv->bits); } } if (isdebug(BUG_MAILFROM)) (void) printf("%s: flags=%s\n", argv[0], isbitset(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]; set_bit(BIT_GOT_HONEYPOT, priv->bits); if (isdebug(BUG_RCPTTO)) (void)printf("%s: flags=%s\n", argv[0], isbitset(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)); # ifndef WORDS_BIGENDIAN in.s_addr = revbytes(in.s_addr); # endif 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) set_bit(BIT_BAD_MSG_ID, priv->bits); if (isdebug(BUG_HEADER)) (void) printf("%s: %s flags=%s\n", field, value, isbitset(BIT_BAD_MSG_ID, priv->bits)?"[MID]":""); } if (CheckFromHeader == TRUE && strcasecmp(field, "From") == 0) { /* * 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(cp) == NULL) { if (h_errno != TRY_AGAIN) set_bit(BIT_BAD_FROM_HEAD, priv->bits); } } if (isdebug(BUG_HEADER)) (void) printf("%s: %s flags=%s\n", field, value, isbitset(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) set_bit(BIT_BAD_MSG_ID, priv->bits); { if (isdebug(BUG_EOH)) (void) printf("eoh: flags=%s\n", isbitset(BIT_BAD_MSG_ID, priv->bits)?"[MID]":""); } /* * Here we count up the number of header recipients. */ priv->nhdrrcpts = 0; if (priv->hdrrcpts != NULL) { 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; if (isdebug(BUG_DB)) t = 1234000000; else (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) set_bit(BIT_TOO_MANY_HDR_RCPTS, event.eventmap); if (event.envrcpts > MaxRcptsPerEnvelope) set_bit(BIT_TOO_MANY_ENV_RCPTS, event.eventmap); if (put_ip_database(Dp1, priv->ip, t) != 0) milterr(Prog, __FILE__, __LINE__, TRUE, errno, "Primary database write", PrimaryDatabaseLoc); k.ip = priv->ip; k.date = t; if (put_event_database(Dp2, &k, &event) != 0) milterr(Prog, __FILE__, __LINE__, TRUE, errno, "Secondary database write", SecondaryDatabaseLoc); # if HAVE_SYSLOG if (DoLog == TRUE && LogEvents == TRUE) syslog(LOG_INFO, "%s%s", LogPrefix, event_to_str(priv->iptxt, t, &event)); # endif (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. ** 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) { set_bit(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 */ SMFIF_NONE, /* 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; 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; /* * The run-by-hand interface is by name only. */ mode = MODE_RUN_AS_MILTER; if (strcmp(Prog, "slowedit") == 0) mode = MODE_ADD_BY_HAND; # if HAVE_SYSLOG openlog(Prog, LOG_NDELAY, LOG_MAIL); # endif /* Process command line options */ xtest = NULL; force = FALSE; Debugbits = 0L; Verbose = FALSE; while ((c = getopt(argc, argv, "EdX:D:fC:Vv")) != -1) { switch (c) { /* Verbose let's you see what is happening */ /* Note that verbose is not debug */ case 'v': Verbose = TRUE; break; /* Edit mode, break and let slowedit() handle it. */ case 'E': if (Verbose == TRUE) (void) printf("Reset Mode to EDIT\n"); mode = MODE_ADD_BY_HAND; goto skip; /* Specify configuration file */ case 'C': cf = optarg; break; /* Dump the database to stdout */ case 'd': if (Verbose == TRUE) (void) printf("Reset Mode to DBDUMP\n"); 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': if (Verbose == TRUE) (void) printf("Reset Mode to FORCE\n"); 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; } } /* * If slowedit mode, leave the rest of argc, argv available. */ skip: /* Read the configuration file */ if (cf == NULL) cf = CFFILE; (void) setdefaults(); (void) readconf(Prog, cf); /* * 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 (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) { if (Verbose == TRUE) { printf("%s: successfully created.\n", PrimaryDatabaseLoc); printf("%s: successfully created.\n", SecondaryDatabaseLoc); } # if HAVE_SYSLOG if (DoLog == TRUE) syslog(LOG_INFO, "%s%s created, %s created", LogPrefix, PrimaryDatabaseLoc, SecondaryDatabaseLoc); # endif return EX_OK; } if (mode == MODE_DUMP_DATABASE) { if (Verbose == TRUE) { printf("Dumping %s and %s\n", PrimaryDatabaseLoc, SecondaryDatabaseLoc); } ret = dump_database(Dp1, Dp2, 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(mode != MODE_ADD_BY_HAND) { 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, EINVAL, 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; } 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); } /* * TODO -- add code to put into the background */ # if HAVE_SYSLOG if (DoLog == TRUE) syslog(LOG_INFO, "Version %s: starting up on socket %s", VERSION, ListenPort); # endif (void) smfi_main(); } else if(mode == MODE_ADD_BY_HAND) { slowedit(optind, argc, argv); } 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. |