/************************************************************ ** $Id: milter.c,v 0.10 2004/01/24 15:01:19 bcx Exp bcx $ ** ** 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" /* ** Exiting early because of an abort or a problem requires ** that we log if we can. */ void egress_log(SMFICTX *ctx, char *file, int line, char *err, char *why) { # if HAVE_SYSLOG PRIV *priv = (PRIV *)smfi_getpriv(ctx); char *iptxt; if (priv == NULL) return; /* * If push_to_database failed to log something. And we are * tracing to debug something, we do a fallback log here. */ if (isdebug(BUG_TRACE)) { if (priv->iptxt == NULL) iptxt = "?.?.?.?"; else iptxt = priv->iptxt; (void)syslog(LOG_INFO, "%s: %s(%s): %s: %s", iptxt, file, line, err, why); # endif } } void clear_priv(SMFICTX *ctx, int is_end_of_connect) { PRIV *priv = (PRIV *)smfi_getpriv(ctx); if (priv == NULL) return; if (priv->midstr != NULL) { (void) free(priv->midstr); priv->midstr = NULL; } priv->bits = 0; priv->envrcpts = 0; priv->badrcpts = 0; priv->honeyrcpts = 0; priv->nhdrrcpts = 0; /* * Don't deallocate connection information, there may * be more envelopes. Only do this at end of connection. */ if (is_end_of_connect == TRUE) { if (priv->iptxt != NULL) { (void) free(priv->iptxt); priv->iptxt = NULL; } (void) free((char *)priv); (void) smfi_setpriv(ctx, NULL); } return; } sfsistat egress_envelope(SMFICTX *ctx, sfsistat ret, char *file, int line, char *err, char *why) { /* * We get here only if neither xxfi_eom() nor xxfi_abort() were * called. */ if (file != NULL && err != NULL && why != NULL) { if (push_to_db(ctx, 0, TRUE) == FALSE) egress_log(ctx, file, line, err, why); } clear_priv(ctx, FALSE); return ret; } sfsistat egress_connect(SMFICTX *ctx, sfsistat ret, char *file, int line, char *err, char *why) { egress_log(ctx, file, line, err, why); clear_priv(ctx, TRUE); return ret; } /* ** 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), TRUE); if (priv == NULL) { /* ** memory allocation failure here means ** only xxfi_connect() will be called. */ return SMFIS_ACCEPT; } (void) memset((char *)priv, '\0', sizeof(PRIV)); if (smfi_setpriv(ctx, (void *)priv) == MI_FAILURE) { /* ** milterr() always exits. */ milterr(Prog, __FILE__, __LINE__, TRUE, errno, "xxfi_connect() 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; priv->ip = htonl(0x080000); priv->iptxt = try_strdup(__FILE__, __LINE__, "127.0.0.0", TRUE); set_bit(BIT_NOTINET, priv->bits); } else { (void) memcpy(&(priv->ip), (char *)&(((struct sockaddr_in *)ha)->sin_addr), sizeof(PRIMARY_DATUM)); (void) memcpy(&in.s_addr, &(priv->ip), sizeof(PRIMARY_DATUM)); priv->iptxt = try_strdup(__FILE__, __LINE__, inet_ntoa(in), TRUE); if (priv->iptxt == NULL) { /* ** milterr() always exits. */ milterr(Prog, __FILE__, __LINE__, TRUE, errno, "xxfi_connect() ABORTED", strerror(errno)); } /* * If we are allowed to check RBL hosts, * we do it here. We can check multiple * RBL hosts, but beware: each takes time. */ if (RBLList != NULL) { char **p; for (p = RBLList; *p != NULL; ++p) { if (rbl_lookup(*p, priv->iptxt, TRUE) != 0) continue; set_bit(BIT_RBL_BAD, priv->bits); break; } } /* * 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 egress_connect(ctx, SMFIS_ACCEPT, __FILE__, __LINE__, "Not an IPv4 network connection", "Accept envelope from xxfi_connect()"); 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) { return egress_connect(ctx, SMFIS_ACCEPT, __FILE__, __LINE__, "Unexpected NULL private pointer", "Accept connection from xxfi_helo()"); } /* * If the milter configuration file allows it, check for * an illegal advance write. */ if(AdvanceWrite == TRUE) { sym = smfi_getsymval(ctx, "{illegalpipe}"); if (sym != NULL && *sym && 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. */ # ifndef HAVE_GETIPNODEBYNAME pthread_mutex_t Envfrom_mutex; # endif sfsistat xxfi_envfrom(SMFICTX *ctx, char **argv) { char *cp, *ep; PRIV *priv; if (argv == NULL) { return egress_envelope(ctx, SMFIS_ACCEPT, __FILE__, __LINE__, "NULL MAIL FROM sender", "Accept sender from xxfi_envfrom()"); } /* * If the milter configuration file prevents this * test, skip it. */ if (CheckSenderHost == FALSE) return SMFIS_CONTINUE; priv = (PRIV *)smfi_getpriv(ctx); if (priv == NULL) { return egress_envelope(ctx, SMFIS_ACCEPT, __FILE__, __LINE__, "Unexpected NULL private pointer", "Accept sender from xxfi_envfrom()"); } /* * 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) -1; } /* * A missing host part after the @ (i.e. "user@") is bad. */ if (cp >= ep) { set_bit(BIT_BAD_HOST, priv->bits); } else { /* * We don't count it an error if the lookup returns * a temporary error. */ int err; # if HAVE_GETIPNODEBYNAME struct hostent *hp; hp = getipnodebyname(cp, AF_INET, AI_DEFAULT , &err); if (hp == NULL) { if (err != TRY_AGAIN) set_bit(BIT_BAD_HOST, priv->bits); } freehostent(hp); # else /* * Warning, this is not thread-safe! */ pthread_mutex_lock(&Envfrom_mutex); if (gethostbyname(cp) == NULL) { err = h_errno; if (err != TRY_AGAIN) set_bit(BIT_BAD_HOST, priv->bits); } pthread_mutex_unlock(&Envfrom_mutex); # endif } if (isdebug(BUG_MAILFROM)) { (void) printf("%s: flags=%s\n", argv[0], isbitset(BIT_BAD_HOST, priv->bits)?"[BHO]":"" ); } 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) { return egress_envelope(ctx, SMFIS_ACCEPT, __FILE__, __LINE__, "Unexpected NULL private pointer", "Accept recipient from xxfi_envto()"); } 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. */ 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); (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 recipient 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) { return egress_envelope(ctx, SMFIS_ACCEPT, __FILE__, __LINE__, "Unexpected NULL private pointer", "Accept envelope from xxfi_header()"); } /* * If the XTrackHeader appears, accept the message, * because this means it was already screened (probably * by out MX host). */ if (XTrackHeader != NULL && strcasecmp(field, XTrackHeader) == 0) { if (isdebug(BUG_HEADER)) printf("%s: %s accepted\n", field, value); return egress_envelope(ctx, SMFIS_ACCEPT, __FILE__, __LINE__, "Found XTrackHeader, already screened", "Accept envelope from xxfi_header()"); } /* * 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)); in.s_addr = ntohl(in.s_addr); priv->iptxt = try_strdup(__FILE__, __LINE__, inet_ntoa(in), TRUE); if (priv->iptxt == NULL) { return egress_envelope(ctx, SMFIS_ACCEPT, __FILE__, __LINE__, strerror(errno), "Accept envelope from xxfi_header()"); } 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->nhdrrcpts += header_recipient(value); if (isdebug(BUG_HEADER)) { (void) printf("%s: %s\n", field, value); } return SMFIS_CONTINUE; } if (strcasecmp(field, "Message-Id") == 0) { priv->midstr = try_strdup(__FILE__, __LINE__, value, TRUE); if (CheckMessageId == 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 RFC2822 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. */ sfsistat xxfi_eoh(SMFICTX *ctx) { PRIV *priv; priv = (PRIV *)smfi_getpriv(ctx); if (priv == NULL) { return egress_envelope(ctx, SMFIS_ACCEPT, __FILE__, __LINE__, strerror(errno), "Accept envelope from xxfi_eoh()"); } /* * If there was no Message-ID header, we count this as bad too. */ if (CheckMessageId == TRUE && priv->midstr == NULL) 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]":""); } 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. */ int push_to_db(SMFICTX *ctx, time_t tt, int threading) { PRIMARY_KEY p; SECONDARY_DATUM event; PRIMARY_DATUM t; PRIMARY_KEY *ips; int cnt, i; PRIV *priv; int didlog; didlog = FALSE; priv = (PRIV *)smfi_getpriv(ctx); if (priv == NULL) return didlog; if (isdebug(BUG_DB) || isdebug(BUG_ADD) || isdebug(BUG_DELETE)) t = BUG_NOW; else if (tt != 0) t = tt; else (void)time(&t); event.date = t; event.eventmap = priv->bits; event.envrcpts = priv->envrcpts; event.headrcpts = priv->nhdrrcpts; event.honeyrcpts = priv->honeyrcpts; event.badrcpts = priv->badrcpts; (void) memset(event.msgid, '\0', MAX_MESSAGE_ID); if (priv->midstr != NULL) (void) memcpy(event.msgid, priv->midstr, MAX_MESSAGE_ID -1); 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); ips = NULL; cnt = 0; ips = aliasips(ips, &cnt, priv->ip); if (cnt == 0) return didlog; for (i = 0; i < cnt; ++i) { if (i == 1) set_bit(BIT_ADDEDIP, event.eventmap); p = ips[i]; if (put_event_database(Dp, p, &event) != 0) { /* ** milterr never returns. */ milterr(Prog, __FILE__, __LINE__, TRUE, errno, "Database write", DatabaseLoc); } # if HAVE_SYSLOG if (DoLog == TRUE && LogEvents == TRUE) { struct in_addr in; char *itext = NULL; if (priv->iptxt == NULL) { if (ips[i] == 0) itext = try_strdup(__FILE__, __LINE__, "0.0.0.0", TRUE); else { (void) memcpy(&in.s_addr, (char *)&ips[i], sizeof(PRIMARY_KEY)); in.s_addr = ntohl(in.s_addr); itext = try_strdup(__FILE__, __LINE__, inet_ntoa(in), TRUE ); } } else itext = try_strdup(__FILE__, __LINE__, priv->iptxt, TRUE); if (itext == NULL) itext = try_strdup(__FILE__, __LINE__, "unknown", TRUE); syslog(LOG_INFO, "%s%s", LogPrefix, event_to_str(itext, t, &event, TRUE)); didlog = TRUE; (void)free(itext); } # endif } if (ips != NULL) (void)free(ips); return didlog; } /* ** 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_EOM', confMILTER_MACROS_EOM``, {nbadrcpts}'') ** */ sfsistat xxfi_eom(SMFICTX *ctx) { PRIV *priv; char *sym; priv = (PRIV *)smfi_getpriv(ctx); if (priv == NULL) { return egress_envelope(ctx, SMFIS_ACCEPT, __FILE__, __LINE__, "Unexpected NULL private pointer", "Accept envelope from xxfi_eom()"); } /* * Record the number of bad recipients. */ priv->badrcpts = 0; sym = smfi_getsymval(ctx, "{nbadrcpts}"); if (sym != NULL) priv->badrcpts = atoi(sym); /* * Insert the tracking header if desired. */ if (XTrackHeader != NULL && priv->iptxt != NULL) (void)smfi_addheader(ctx, XTrackHeader, priv->iptxt); if (isdebug(BUG_EOM)) { return SMFIS_ACCEPT; } (void) push_to_db(ctx, 0, TRUE); (void) clear_priv(ctx, FALSE); return SMFIS_ACCEPT; } sfsistat xxfi_abort(SMFICTX *ctx) { PRIV *priv; priv = (PRIV *)smfi_getpriv(ctx); if (priv != NULL) { if (MilterAbort == TRUE) set_bit(BIT_MILTER_ABORTED, priv->bits); if (! isdebug(BUG_ABORT)) { (void) push_to_db(ctx, 0, TRUE); if (XTrackHeader != NULL && priv->iptxt != NULL) (void)smfi_addheader(ctx, XTrackHeader, priv->iptxt); } } if (! isdebug(BUG_ABORT)) (void) clear_priv(ctx, FALSE); return SMFIS_ACCEPT; } sfsistat xxfi_close(SMFICTX *ctx) { clear_priv(ctx, TRUE); return SMFIS_ACCEPT; } struct smfiDesc SmfiDesc = { NULL, /* filter name */ SMFI_VERSION, /* version code -- do not change */ SMFIF_ADDHDRS, /* flags: This milter adds headers */ 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 headers */ NULL, /* This milter ignores the body */ xxfi_eom, /* either end of message */ xxfi_abort, /* or 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; int dbver; char *xtest; char buf[BUFSIZ]; extern int errno; Prog = try_strdup(__FILE__, __LINE__, basename(argv[0]), FALSE); 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; /* global, set here, RO elsewhere */ Verbose = FALSE; /* global, set here, RO elsewhere */ 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; if (strcasecmp(cf, "/dev/null") == 0) break; if (getuid() == 0 || geteuid() == 0) break; printf("%s: -C requires root\n", Prog); exit(EINVAL); 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 BOOTSTRAP\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 * A real chicken and egg problem here. How can we be sure * the configuration file is safe, if we don't yet know * the setting of the RunAsUser configuration item? */ if (cf == NULL) cf = CFFILE; (void) setdefaults(); if (Verbose) printf("%s: using %s as config file.\n", Prog, cf); (void) readconf(Prog, cf); /* * If we were run by root, and if the config file defines * a different user to run as, switch now. */ if (geteuid() == 0 || getuid() == 0) { uid_t uid; gid_t gid; if (RunAsUser == NULL) { fprintf(stderr, "Run by root, but RunAsUser undefined. Abort.\n"); exit(EINVAL); } if (isdigit((int)*RunAsUser)) uid = atoi(RunAsUser); else { struct passwd *pw; pw = getpwnam(RunAsUser); if (pw == NULL) { fprintf(stderr, "RunAsUser=%s: no such login name\n", RunAsUser); exit(errno); } uid = pw->pw_uid; } if (RunAsGroup != NULL) { struct group *gp; if (isdigit((int)*RunAsGroup)) gid = atoi(RunAsGroup); else { gp = getgrnam(RunAsGroup); if (gp == NULL) { fprintf(stderr, "RunAsGroup=%s: no such group name\n", RunAsGroup); exit(errno); } gid = gp->gr_gid; } (void)setgid(gid); (void)setegid(gid); # if HAVE_SYSLOG if (DoLog == TRUE) syslog(LOG_INFO, "Reset group identity to %s", RunAsGroup); # endif } (void)setuid(uid); (void)seteuid(uid); # if HAVE_SYSLOG if (DoLog == TRUE) syslog(LOG_INFO, "Reset user identity to %s", RunAsUser); # endif } /* * Regression testing. * Each test exits when done. */ 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_SHOWNOW)) test_now(xtest); /* ** A hack to speed up DNS lookups. There will be ** no retries and no retransmissions. */ _res.retrans = 0; _res.retry = 0; /* * This cannot be used for regression testing. But it * is a handy hook to see how mx lookups are being done. */ if (isdebug(BUG_MX) && xtest != NULL) { MxHosts = NULL; MxHosts = getmx(xtest, MxHosts); (void) print_list(xtest, MxHosts, stdout); return EX_OK; } /* * Should the mx list be empty on our MX servers? */ if(mode != MODE_ADD_BY_HAND) { if (Verbose == TRUE) printf("%s: looking up MX Records\n", Prog); /* * If we did not specify our hostname in the config, * look it up with gethostname(). */ 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); if (Verbose == TRUE || isdebug(BUG_MX)) print_list(buf, MxHosts, stdout); if (isdebug(BUG_MX)) return EX_OK; } else { MxHosts = getmx(LocalHostName, MxHosts); if (Verbose == TRUE || isdebug(BUG_MX)) print_list(LocalHostName, MxHosts, stdout); if (isdebug(BUG_MX)) return EX_OK; } } /* * Read into memory, a list of the honeypot addresses. * Use -v to see what what was read. */ if (HoneyFile != NULL) { (void) read_honey(); if (Verbose == TRUE) { (void) print_list(HoneyFile, HoneyHosts, stdout); } } /* * The database stuff is always last. * Open both databases. The db flags set are in * database.c. Here we only supply the file name. */ if (mode == MODE_RUN_AS_MILTER || mode == MODE_BOOTSTRAP) { if (DatabaseLoc == NULL) { printf("%s: DatabaseLoc is NULL. Aborted\n", Prog); return EINVAL; } Dp = open_database(DatabaseLoc, force); if (mode == MODE_BOOTSTRAP) { PRIMARY_KEY p; SECONDARY_DATUM d; p = DATABASE_VERSION; memset((char *)&d, '\0', sizeof(SECONDARY_DATUM)); ret = put_event_database(Dp, p, &d); if (ret != 0) { printf("%s: bootstrap: could not install database version\n", Prog); printf("%s: database creation aborted.\n", Prog); exit(ret); } if (Verbose == TRUE) printf("%s: succesful insert of version\n", Prog); return EX_OK; } dbver = get_db_version(Dp, FALSE); if (dbver != DATABASE_VERSION) { printf("%s: database version mismatch. Expected %d got %d\n", Prog, DATABASE_VERSION, dbver); exit(EINVAL); } if (Verbose == TRUE) printf("%s: using %s version %d\n", Prog, DatabaseLoc, dbver); 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 if (Verbose == TRUE) printf("%s: starting multi-threaded run\n", Prog); # ifndef HAVE_GETIPNODEBYNAME pthread_mutex_init(&Envfrom_mutex, NULL); # endif (void) smfi_main(); (void)Dp->sync(Dp, 0); (void)close_database(Dp); if (Verbose == TRUE) printf("%s: all done.\n", Prog); # if HAVE_SYSLOG if (DoLog == TRUE) syslog(LOG_INFO, "Version %s: normal shutdown", VERSION); # endif /* * XXX Should we put auto-soft-restart code here? */ } 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. |