/* * Copyright (c) Joe Maimon, jmaimon@jmaimon.com New York,NY 2006 * * This milter is adapted from the sample milter included in the * open source sendmail distribution libmilter/README * * Portions may be copyright the Sendmail Consortium. * * No license was explicitly stated for that sample. In the event * that a License does apply, and this work is a significant deriviation * of the sample milter, the assumed license would be the license found * in the open source sendmail distribution LICENSE file. * * Additionaly, unless you received this file from all copyright holders under * any other arrangement, the GNU GPLv2 or later license may be assumed to apply, * as specified by the Free Software Foundation. */ /* * The following copyright applies to portions copied or derived from Sendmail.org sources */ /* * Copyright (c) 1998-2004, 2006 Sendmail, Inc. and its suppliers. * All rights reserved. * Copyright (c) 1986, 1995-1997 Eric P. Allman. All rights reserved. * Copyright (c) 1988, 1993 * The Regents of the University of California. All rights reserved. * * By using this file, you agree to the terms and conditions set * forth in the LICENSE file which can be found at the top level of * the sendmail distribution. * */ /*TODO * * verification caching * * connection caching * * header split * * delay_checks style from address verification - use with milter-rulesets, depends on verification caching * * autoproject/autoconfiscate * * SSL/TLS * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if DEBUG # include #endif /* DEBUG */ #if DBCACHE # include #endif /* DBCACHE */ # if NETINET || NETINET6 # include # include # endif /* NETINET || NETINET6 */ #ifndef _FFR_MILTER_REWRITE # define _FFR_MILTER_REWRITE 1 #endif /* _FFR_MILTER_REWRITE */ #ifndef _FFR_MILTER_SM_MAP # define _FFR_MILTER_SM_MAP 1 #endif /* _FFR_MILTER_SM_MAP */ #include "libmilter/mfapi.h" /* right hand side items */ #define CANONNET ((unsigned char)0226) /* canonical net, next token */ #define CANONHOST ((unsigned char)0227) /* canonical host, next token */ #define CANONUSER ((unsigned char)0230) /* canonical user, next N tokens */ #define CALLSUBR ((unsigned char)0231) /* call another rewriting set */ #ifndef true typedef int bool; # define false 0 # define true 1 #endif /* ! true */ #define VERSION "0.06" #define PROGNAME "callahead-milter" #define MAXREADLOOPS 30 /*anything else is probably ridiculous ((BUFSIZE *=2)*30) or (CALM_TO_READ * 30)*/ #define CALLAHEADMAGIC ((404332111 * 'c'+'a'+'l'+'l'+'a'+'h'+'e'+'a'+'d'+'m'+'a'+'g'+'i'+'c')) static char *progname = PROGNAME; static char *MyHname = NULL; static int MyHname_len; static int prevent_loop = 0; static int use_j_macro = 0; static int verify_sender = 0; static int callback_sender = 0; static FILE *logfile = NULL; static int verbosity = 0; static bool do_mx_lookups = 0; static char *cache_spec = 0; static char *cache_disk_file = NULL; static char *timeouts_spec = "D:10s;X:10s;L:20s;C:15s;O:25s;K:30s;S:10s;N:30s;R:10s;E:1m;I:1h;M:2m;U:1m"; static char *host_connection_caching = NULL; static char *pt_host_connection_caching = NULL; static bool cleanup_reply = false; static bool use_syslog = false; static char *syslog_label = PROGNAME; static char *calm_envrcpt = NULL; static char *calm_envfrom = NULL; static int calm_envrcpt_len = 0; static int calm_envfrom_len = 0; static int only_mx_supressed = 0; static bool retry_without_dot = false; static bool getmx_detect_ipa = false; static char *hostname_sm_lookup = NULL; static bool do_daemonize = false; static bool prefer_inet6 = false; #if DBCACHE DB_ENV *cach_env = NULL; DB *cach_dbp = NULL; u_int32_t cach_env_flags = 0; /* env open flags */ u_int32_t cach_db_flags = 0; /* database open flags */ int cach_full = 0; /* if 1, dont add records. Unlocked on purpose, it wont be exact */ DB_ENV *conc_env = NULL; /* Env structure handle */ DB *conc_dbp = NULL; u_int32_t conc_env_flags = 0; /* env open flags */ u_int32_t conc_db_flags = 0; /* database open flags */ int conc_full = 0; /* if 1, dont add records. Unlocked on purpose, it wont be exact */ struct cache_expire { DBC *cp; DBT *key; DBT *data; int num_recs; int total_recs; struct cache_breakdown *cb; int cursor_flag; }; #endif /* DBCACHE */ enum CONF_CONF { CONF_CONFBEG = 0, CONF_TABLESZ, CONF_ABSEXP, CONF_UPDEXP, CONF_MAXRST, CONF_SINIT, CONF_WINIT, CONF_SLEEP_MIN, CONF_SLEEP_MAX, CONF_WORK_MIN, CONF_WORK_MAX, CONF_CACHESZ, CONF_CLEANUP, CONF_GETCONWT, CONF_GETCONWTMX, CONF_MAXWAITERS, CONF_MAXENTRIES, CONF_THRESHOLD, CONF_CONFMX }; struct cache_breakdown { time_t entry_time; int occurences; }; int conc_conf[CONF_CONFMX]; pthread_mutex_t cach_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_t cach_thread; bool cach_stop = false; int cach_conf[CONF_CONFMX]; pthread_mutex_t conc_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_t conc_thread; bool conc_stop = false; struct conc_data { unsigned int magic; int len; int buflen; time_t con_start; time_t con_updated; int rsets; int num; int last_status; char buf[1]; }; struct cach_data { unsigned int magic; int len; int buflen; time_t cach_start; time_t cach_updated; int checks; int last_status; int hlen; char buf[1]; }; struct conc_entry { int consock; pthread_mutex_t conc_mutex; pthread_cond_t conc_cond; pthread_mutex_t conc_cond_mutex; int waiters; bool active; bool locking; }; struct conc_entry *conc_table = NULL; struct mlfiPriv { char * rewrite_val; char * sender_address; int sender_address_len; char * queue_id; char myhname[1024]; char *myhnamep; char *macro_j; int macro_j_len; int myhname_len; int consock; int consock_con; time_t milter_start; time_t dns_mx_start; /* recursive function */ time_t olookup_start; /* recursive function */ SMFICTX *ctx; int thread_id; #if DBCACHE /* PT connection cache */ DB_ENV *conc_env; DB *conc_dbp; /* DB structure handle */ u_int32_t conc_db_flags; /* database open flags */ u_int32_t conc_env_flags; /* env open flags */ int conc_full = 0; /* if 1, dont add records. Unlocked on purpose, it wont be exact */ #endif /* DBCACHE */ struct conc_entry *conc_table; pthread_t conc_thread; pthread_mutex_t conc_mutex; bool conc_stop; }; struct calm_hostent { struct hostent * host[2]; int num; char * buf[2]; }; static char * fallbackdsthost = NULL; static bool rewrite_anyways = 0; static bool rewrite_maybe = 0; static char * lfallbackdsthost = NULL; #if _FFR_MILTER_SM_MAP static char * bestmx_sm_map = "bestmx"; static bool bestmx_sm_map_lookup = false; #endif /* _FFR_MILTER_SM_MAP */ static char noqueue_id[] = "NOQUEUE:"; static sfsistat smfis_tempfail = SMFIS_TEMPFAIL; static sfsistat smfis_remote_tempfail = SMFIS_TEMPFAIL; static sfsistat never_stop_anything = 0; #define MLFIPRIV ((struct mlfiPriv *) smfi_getpriv(ctx)) /* * intro == -2 dont print itnroduction, unlock and return * intro == 0 dont print introduction * intro == 1 print introduction, lock and unlock before returning * intro == 2 print introduction, lock and return * */ pthread_mutex_t calm_verbose_mutex = PTHREAD_MUTEX_INITIALIZER; void calm_verbose(struct mlfiPriv *priv, int intro, char * fmt, ...) { va_list ap; int syslog_prio = LOG_INFO; if (!verbosity) return; va_start(ap, fmt); if (logfile) fflush(logfile); if (intro > 0) { if ((fmt && *fmt) || intro == 2) pthread_mutex_lock(&calm_verbose_mutex); #if DEBUGDEBUG fprintf((logfile) ? logfile : stderr, "priv %p queue %p thread %d fmt %p *fmt %d\n", priv, priv ? priv->queue_id : priv, priv ? priv->thread_id : 0, fmt, fmt ? *fmt : 0); #endif /* DEBUGDEBUG */ fprintf((logfile) ? logfile : stderr, "%s : %s: %d%s", progname, (priv && priv->queue_id) ? priv->queue_id : "NOQUEUE", (priv && priv->thread_id) ? priv->thread_id : (int) pthread_self(), (fmt && *fmt) ? " : " : "\n"); } if (use_syslog) { if (verbosity > 3) syslog_prio = LOG_DEBUG; else if (verbosity > 2) syslog_prio = LOG_ERR; if (intro) syslog(syslog_prio, "%s: %d%s", (priv && priv->queue_id) ? priv->queue_id : "NOQUEUE", (priv && priv->thread_id) ? priv->thread_id : (int) pthread_self(), (fmt && *fmt) ? " : " : "\n"); } if(fmt && *fmt) { vfprintf((logfile) ? logfile : stderr, fmt,ap); if (use_syslog) vsyslog(syslog_prio, fmt, ap); if (intro != 2) pthread_mutex_unlock(&calm_verbose_mutex); } else if (intro == -2) pthread_mutex_unlock(&calm_verbose_mutex); if (logfile) fflush(logfile); va_end(ap); return; } # define VERBOSE(a,b,...) do { \ if(verbosity >= a) \ calm_verbose(b, 1, __VA_ARGS__); \ } while (0) #if DEBUG # define VERBOSE_FUNCLINE(a,b) do { \ if(verbosity >= a) \ calm_verbose(b, 1, "%s(): line %d\n", \ __func__, __LINE__); \ } while (0) # define VERBOSE_FUNCLINE_MSG(a,b,...) do { \ if(verbosity >= a) \ { \ calm_verbose(b, 2, "%s(): line %d ", \ __func__, __LINE__); \ calm_verbose(b, -2, __VA_ARGS__); \ } \ } while (0) #else # define VERBOSE_FUNCLINE(...) while(0) { ; } # define VERBOSE_FUNCLINE_MSG(...) while(0) { ; } #endif /* DEBUG */ /* rw == 0 ; select read rw == 1 ; select write rw == 2 ; select write, check connect() rw == 3 ; select nothing (sleep) */ static int calm_select(struct mlfiPriv * priv, int sock, int rw, int secs, int usecs) { /* select */ fd_set rfds; fd_set wfds; struct timeval tv, *tvp; int retval; unsigned len = sizeof(retval); FD_ZERO(&rfds); FD_ZERO(&wfds); VERBOSE_FUNCLINE_MSG(4, priv, "priv %p sock %d rw %d secs %d usecs %d\n", priv, sock, rw, secs, usecs); if ((sock < 0 || rw < 0) && rw != 3) return 1; if (rw) FD_SET(sock, &wfds); else FD_SET(sock, &rfds); if (usecs) tv.tv_usec = usecs; else tv.tv_usec = 0; if (secs) tv.tv_sec = secs; else tv.tv_sec = 0; if (tv.tv_sec || tv.tv_usec) tvp = &tv; else tvp = NULL; /* sleep until action */ if (!secs && !usecs) { if (rw && rw != 3) tv.tv_sec = 20; else if (rw == 3) tv.tv_sec = 1; else tv.tv_sec = 2 * 60; } if (sock >= FD_SETSIZE) { return 1; } retval = select(sock + 1, (rw) ? NULL : &rfds, (rw && rw != 3) ? &wfds : NULL, NULL, tvp); VERBOSE_FUNCLINE_MSG(4, priv, "retval %d\n", retval); if (retval < 0) { return -1; } else if (retval) { if (rw == 2) { if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &retval, &len) < 0) { return -1; } else { if (retval) { return -1; } else { return 1; } /* cant happen */ return -1; } /* cant happen */ return -1; } return 1; } else { if (rw == 3) return 1; return -1; } /* cant happen */ return -1; } static int calm_select_read(struct mlfiPriv * priv, int sock, int secs, int usecs) { return (calm_select(priv, sock, 0, secs, usecs)); } static int calm_select_write(struct mlfiPriv * priv, int sock, int secs, int usecs) { return (calm_select(priv, sock, 1, secs, usecs)); } static int calm_select_connect(struct mlfiPriv *priv, int sock, int secs, int usecs) { return (calm_select(priv, sock, 2, secs, usecs)); } static int calm_select_sleep(struct mlfiPriv * priv, int secs, int usecs) { /* allow indefinite sleep? */ if (secs < 0) secs = 0; if (usecs < 0) usecs = 0; return (calm_select(priv, 0, 3, secs, usecs)); } /* lifted and modified from sendmail/convtime.c * props and copyrights to sendmail/org and consortium */ /* ** CONVTIME -- convert time ** ** Takes a time as an ascii string with a trailing character ** giving units: ** s -- seconds ** m -- minutes ** h -- hours ** d -- days (default) ** w -- weeks ** For example, "3d12h" is three and a half days. ** ** Parameters: ** p -- pointer to ascii time. ** units -- default units if none specified. ** ** Returns: ** time in seconds. ** ** Side Effects: ** none. */ time_t convtime(priv, p, units) struct mlfiPriv *priv; char *p; int units; { register time_t t, r; register char c; bool pos = true; r = 0; if (strcasecmp(p, "now") == 0) return -1; if (*p == '-') { pos = false; ++p; } while (*p != '\0') { t = 0; while ((c = *p++) != '\0' && isascii(c) && isdigit(c)) t = t * 10 + (c - '0'); if (c == '\0') { c = units; p--; } else if (strchr("wdhms", c) == NULL) { VERBOSE(1, priv, "Invalid time unit `%c'", c); c = units; } switch (c) { case 'w': /* weeks */ t *= 7; /* FALLTHROUGH */ case 'd': /* days */ /* FALLTHROUGH */ default: t *= 24; /* FALLTHROUGH */ case 'h': /* hours */ t *= 60; /* FALLTHROUGH */ case 'm': /* minutes */ t *= 60; /* FALLTHROUGH */ case 's': /* seconds */ break; } r += t; } return pos ? r : -r; } /* lifted and modified from sendmail/milter.c * props and copyrights to sendmail/org and consortium */ /* ** CALM_PARSE_TIMEOUTS -- parse timeout list ** ** Called when proccessing argv ** ** Parameters: ** ctx -- context ** spec -- the timeout list. ** calm_timeouts -- timeout array. ** ** Returns: ** 0 on error ** 1 on success */ enum CALM_TO { CALM_TO_DNS_A = 0, CALM_TO_DNS_MX, CALM_TO_SM_LOOKUP, CALM_TO_SM_OLOOKUP, CALM_TO_CONNECT, CALM_TO_OCONNECT, CALM_TO_SEND, CALM_TO_NSEND, CALM_TO_READ, CALM_TO_EREAD, CALM_TO_MILTER, CALM_TO_CLEANUP, CALM_TO_EOM, CALM_TO_SIZE }; static time_t Calm_Timeouts[CALM_TO_SIZE]; static int calm_parse_timeouts(priv, spec, calm_timeouts) struct mlfiPriv *priv; const char *spec; time_t *calm_timeouts; { char fcode; int tcode; register char *p, *lspec; if (!spec) return 0; else lspec = p = strdup(spec); if (!p) return 0; VERBOSE_FUNCLINE_MSG(5, priv, "alloc lspec %p timeouts %s\n", lspec, p); /* now scan through and assign info from the fields */ while (p && *p != '\0') { char *delimptr; while (*p != '\0' && (*p == ';' || (isascii(*p) && isspace(*p)))) p++; /* p now points to field code */ fcode = *p; while (*p != '\0' && *p != ':') p++; if (*p++ != ':') { VERBOSE(2, priv, "did not find field code in %s\n", spec); VERBOSE_FUNCLINE_MSG(5, priv, "free lspec %p\n", lspec); free(lspec); return 0; } while (isascii(*p) && isspace(*p)) p++; /* p now points to the field body */ delimptr = strchr(p, ';'); if (delimptr) *(delimptr++) = '\0'; tcode = -1; /* install the field into the filter struct */ switch (fcode) { case 'D': tcode = CALM_TO_DNS_A; break; case 'X': tcode = CALM_TO_DNS_MX; break; case 'L': tcode = CALM_TO_SM_LOOKUP; break; case 'K': tcode = CALM_TO_SM_OLOOKUP; break; case 'C': tcode = CALM_TO_CONNECT; break; case 'O': tcode = CALM_TO_OCONNECT; break; case 'S': tcode = CALM_TO_SEND; break; case 'N': tcode = CALM_TO_NSEND; break; case 'E': tcode = CALM_TO_EREAD; break; case 'R': tcode = CALM_TO_READ; break; case 'M': tcode = CALM_TO_EOM; break; case 'I': tcode = CALM_TO_MILTER; break; case 'U': tcode = CALM_TO_CLEANUP; break; default: VERBOSE(1, priv, "unknown timeout code %c\n", fcode); VERBOSE_FUNCLINE_MSG(5, priv, "free lspec %p\n", lspec); free(lspec); return 0; break; } if (tcode >= 0) { calm_timeouts[tcode] = convtime(priv, p, 's'); VERBOSE(4, priv, "fcode %c seconds %u\n", fcode, (u_long) calm_timeouts[tcode]); } p = delimptr; } VERBOSE_FUNCLINE_MSG(5, priv, "free lspec %p\n", lspec); free(lspec); return 1; } static int calm_timedout(struct mlfiPriv *priv, time_t time_start, int which) { time_t time_end = time(NULL); int time_diff; VERBOSE_FUNCLINE(5, priv); if (which > CALM_TO_EOM) return 0; if (!Calm_Timeouts[which]) return 0; VERBOSE_FUNCLINE_MSG(4, priv, "time_start %u time_end %u priv->dns_mx_start %u " "priv->olookup_start %u priv->milter_start %u which %d timeout %u\n", time_start, time_end, (priv) ? priv->dns_mx_start : 0, (priv) ? priv->olookup_start : 0, (priv) ? priv->milter_start : 0, which, Calm_Timeouts[which]); if (priv && which == CALM_TO_DNS_MX && priv->dns_mx_start) { if ((time_diff = time_end - priv->dns_mx_start) > Calm_Timeouts[which]) { VERBOSE(2, priv, "timed out %i seconds DNS MX with timeout %d, %u seconds\n", time_diff, which, Calm_Timeouts[which]); return 1; } VERBOSE(5, priv, "timing: %d seconds for timeout DNS MX\n", time_diff); } if (priv && which == CALM_TO_SM_LOOKUP && priv->olookup_start) { if ((time_diff = (time_end - priv->olookup_start)) > Calm_Timeouts[CALM_TO_SM_OLOOKUP]) { VERBOSE(2, priv, "timed out %i seconds sendmail lookups with timeout %d, %u seconds\n", time_diff, CALM_TO_SM_OLOOKUP, Calm_Timeouts[CALM_TO_SM_OLOOKUP]); return 1; } VERBOSE(5, priv, "timing: %d seconds for timeout OLOOKUP\n", time_diff); } if ((time_diff = time_end - time_start) > Calm_Timeouts[which]) { VERBOSE(2, priv, "timed out %i seconds with timeout %d, %u seconds\n", time_diff, which, Calm_Timeouts[which]); return 1; } VERBOSE(5, priv, "timing: %d seconds for timeout %d\n", time_diff, which); if (priv && (time_diff = time_end - priv->milter_start) > Calm_Timeouts[CALM_TO_EOM]) { VERBOSE(2, priv, "timed out %i seconds with timeout %d, %u seconds\n", time_diff, CALM_TO_EOM, Calm_Timeouts[CALM_TO_EOM]); return 1; } else if ((time_diff = time_end - time_start) > Calm_Timeouts[CALM_TO_EOM]) { VERBOSE(2, priv, "timed out %i seconds with timeout %d, %u seconds\n", time_diff, CALM_TO_EOM, Calm_Timeouts[CALM_TO_EOM]); return 1; } return 0; } void calm_freehostent(struct mlfiPriv *priv, struct calm_hostent *host) { int i; if (!host) return; VERBOSE_FUNCLINE_MSG(5, priv, "free host->host %p free host->buf %p free host %p\n", host->host, host->buf, host); for (i = 0; i < host->num; i++) { VERBOSE_FUNCLINE_MSG(5, priv, "free host->host[%d] %p free host->buf[%d] %p\n", i, host->host[i], i, host->buf[i]); if (!host->buf && !host->host) break; if (host->host) free(host->host[i]); if (host->buf) free(host->buf[i]); } free(host); return; } struct calm_hostent *calm_sm_gethostbyname(struct mlfiPriv *priv, char *host) { char *lhost = NULL; int host_len = 0; int lhost_len = 0; time_t lookup_start; char *pbuf[2]; char **pp = pbuf; int rwstat = EX_OK -1; struct calm_hostent *calm_host = malloc(sizeof(struct calm_hostent)); struct hostent *hp = malloc(sizeof(struct hostent)); int buflen = 1024; char *buf = NULL; char *bufpos = buf; struct in_addr addr; #if NETINET6 struct hostent *hp2 = malloc(sizeof(struct hostent)); int buf2len = 1024; char *buf2 = NULL; char *buf2pos = buf2; struct in6_addr addr6; #else /* NETINET6 */ struct hostent *hp2 = hp; char *buf2 = buf; #endif /* NETINET6 */ int num6addrs = 0; int num4addrs = 0; int i = 0; int spaceneeded; VERBOSE_FUNCLINE(5, priv); if (calm_host) calm_host->num = 0; else goto sm_hbn_clandl; VERBOSE_FUNCLINE(5, priv); if (!host || !priv || !priv->ctx || !hostname_sm_lookup || !hp2 || !hp) goto sm_hbn_clandl; VERBOSE_FUNCLINE_MSG(5, priv, "host %p == \"%s\" hp %p hp2 %p\n", host, host, hp, hp2); host_len = strlen(host); if (!host_len) goto sm_hbn_clandl; VERBOSE_FUNCLINE_MSG(5, priv, "host_len %d\n", host_len); bufpos = buf2 = buf = malloc(buflen + host_len); #if NETINET6 VERBOSE_FUNCLINE(5, priv); buf2pos = buf2 = malloc(buflen + host_len); #endif if (!buf || !buf2) goto sm_hbn_clandl; lookup_start = time(NULL); if (priv && !priv->olookup_start) priv->olookup_start = lookup_start; if (inet_pton(AF_INET, host, &addr) > 0) { VERBOSE_FUNCLINE(5, priv); num4addrs = 1; lhost = host; } #if NETINET6 else if(inet_pton(AF_INET6, host, &addr6) > 0) { VERBOSE_FUNCLINE(5, priv); num6addrs = 1; lhost = host; } #endif /* NETINET6 */ else { VERBOSE_FUNCLINE_MSG(5, priv, "buf %p buf2 %p lhost %p\n", buf, buf2, lhost); lhost = malloc(host_len + 2); if (!lhost) goto sm_hbn_clandl; lhost[0] = '@'; memcpy(lhost+1, host, host_len); lhost[host_len + 1] = '\0'; VERBOSE_FUNCLINE_MSG(5, priv, "host %s lhost %p == \"%s\"\n", host, lhost, lhost ); pp[0] = lhost; pp[1] = NULL; if(calm_timedout(priv, lookup_start, CALM_TO_SM_LOOKUP)) goto sm_hbn_clandl; if (smfi_rewrite(priv->ctx, "canonify", &pp, &rwstat, SMFRW_MULTIPLE | SMFRW_UNFILTER) == MI_SUCCESS && rwstat >= EX_OK) { VERBOSE_FUNCLINE_MSG(5, priv, "lhost %p == \"%s\"\n", lhost, lhost ); if(calm_timedout(priv, lookup_start, CALM_TO_SM_LOOKUP)) goto sm_hbn_clandl; if (pp && pp[0]) { VERBOSE_FUNCLINE(5, priv); lhost = pp[0]; lhost_len = strlen(lhost); if (lhost_len < 5) goto sm_hbn_clandl; if (lhost[0] == '<') { lhost++; lhost_len--; } if (lhost[0] == '@') { lhost++; lhost_len--; } if (lhost[lhost_len-1] == '>') lhost[--lhost_len] = '\0'; VERBOSE_FUNCLINE(5, priv); } else goto sm_hbn_clandl; } else goto sm_hbn_clandl; } VERBOSE_FUNCLINE_MSG(5, priv, "pbuf[0] %p == \"%s\" lhost %p == \"%s\"\n", pbuf[0], pbuf[0], lhost, lhost); hp->h_aliases = (void *)buf; bufpos += sizeof(buf) * 2; hp->h_aliases[0] = NULL; if (pbuf[0] && strncasecmp(pbuf[0] + 1, lhost, lhost_len)) { char *tmpbuf; VERBOSE_FUNCLINE(5, priv); if ((host_len + lhost_len) >= (buflen - (bufpos - buf) -1)) { VERBOSE_FUNCLINE(5, priv); tmpbuf = realloc(buf, (buflen * 2) + host_len + lhost_len); if (!tmpbuf) goto sm_hbn_clandl; bufpos = tmpbuf + (bufpos - buf); buf = tmpbuf; buflen *= 2; VERBOSE_FUNCLINE(5, priv); } VERBOSE_FUNCLINE_MSG(5, priv, "bufpos %p, host %p == \"%s\" host_len %d\n", bufpos, host, host, host_len); memmove(bufpos, host, host_len); hp->h_aliases[0] = bufpos; hp->h_aliases[1] = NULL; bufpos+=host_len; *(bufpos++) = '\0'; } else { VERBOSE_FUNCLINE(5, priv); lhost = host; lhost_len = host_len; } VERBOSE_FUNCLINE_MSG(5, priv, "bufpos %p lhost %p == \"%s\" lhost_len %d\n", bufpos, lhost, lhost, lhost_len); memmove(bufpos, lhost, lhost_len); hp->h_name = bufpos; if (pbuf[0]) free(pbuf[0]); pbuf[0] = bufpos; bufpos+=lhost_len; *(bufpos++) = '\0'; if (!num4addrs && !num6addrs) { VERBOSE_FUNCLINE_MSG(5, priv, "pbuf[0] %p == \"%s\" bufpos %p bufpos - lhost_len - 1 " "(%d) %p == \"%s\"\n", pbuf[0], pbuf[0], bufpos, lhost_len + 1, bufpos - lhost_len - 1, bufpos - lhost_len - 1); if (pp) { VERBOSE_FUNCLINE(5, priv); for (i = 0; pp[i]; i++) free(pp[i]); free(pp); } VERBOSE_FUNCLINE(5, priv); pp = pbuf; if (smfi_sm_map(priv->ctx, hostname_sm_lookup, &pp, &rwstat, SMFRW_MULTIPLE | SMFRW_MAPDELIM | SMFRW_ALLMATCH) != MI_SUCCESS || rwstat < EX_OK || !pp || !pp[0]) { pbuf[0] = NULL; goto sm_hbn_clandl; } else pbuf[0] = NULL; if(calm_timedout(priv, lookup_start, CALM_TO_SM_LOOKUP)) goto sm_hbn_clandl; VERBOSE_FUNCLINE(5, priv); for (i = 0; pp && pp[i]; i++); } spaceneeded = (sizeof buf) * (i+2); #if NETINET6 spaceneeded += (sizeof addr6) * (i+1); #else spaceneeded += (sizeof addr) * (i+1); #endif /* NETINET6 */ VERBOSE_FUNCLINE_MSG(5, priv, "spaceneeded %d\n", spaceneeded); if (spaceneeded >= (buflen - (bufpos - buf))) { char *tmpbuf = realloc(buf, buflen + spaceneeded); VERBOSE_FUNCLINE(5, priv); if (!tmpbuf) goto sm_hbn_clandl; VERBOSE_FUNCLINE(5, priv); hp->h_name = tmpbuf + (hp->h_name - buf); hp->h_aliases = (char **)tmpbuf + ((char *)hp->h_aliases - buf); if (hp->h_aliases[0]) hp->h_aliases[0] = tmpbuf + (hp->h_aliases[0] - buf); bufpos = tmpbuf + (bufpos - buf); buf = tmpbuf; buflen += spaceneeded; } VERBOSE_FUNCLINE(5, priv); hp->h_addr_list = (void *)bufpos; bufpos += (sizeof buf) * (i+2); hp->h_addrtype = AF_INET; hp->h_length = sizeof(struct in_addr); #if NETINET6 if (buf2len != buflen) { char *tmpbuf = realloc(buf2, buflen); VERBOSE_FUNCLINE(5, priv); if (!tmpbuf) goto sm_hbn_clandl; buf2pos = buf2 = tmpbuf; } VERBOSE_FUNCLINE(5, priv); memcpy(buf2, buf, bufpos - buf); hp2->h_name = buf2 + (hp->h_name - buf); hp2->h_aliases = (char **)buf2 + ((char *)hp->h_aliases - buf); if (hp->h_aliases[0]) hp2->h_aliases[0] = buf2 + ((char *)hp->h_aliases[0] - buf); hp2->h_addrtype = AF_INET6; hp2->h_length = sizeof(struct in6_addr); hp2->h_addr_list = (char **)buf2 + ((char *)hp->h_addr_list - buf); #endif /* NETINET6 */ VERBOSE_FUNCLINE(5, priv); i=0; if (num4addrs) { VERBOSE_FUNCLINE(5, priv); hp->h_addr_list[i] = bufpos; bufpos += sizeof(addr); memcpy(hp->h_addr_list[i], &addr, sizeof(addr)); } #if NETINET6 else if (num6addrs) { VERBOSE_FUNCLINE(5, priv); hp2->h_addr_list[i] = bufpos; bufpos += sizeof(addr6); memcpy(hp2->h_addr_list[1], &addr, sizeof(addr6)); } #endif /* NETINET6 */ else while(pp && pp[i]) { VERBOSE_FUNCLINE_MSG(5, priv, "pp[%d] == \"%s\" + sizeof(\"IPv6\") (%d) == \"%s\"\n", i, pp[i], sizeof("IPv6"), (pp[i])+sizeof("IPv6")); if (strncmp("IPv6:", pp[i], sizeof("IPv6"))==0) { #if NETINET6 if (inet_pton(AF_INET6, (pp[i])+sizeof("IPv6"), &addr6) > 0) { VERBOSE_FUNCLINE_MSG(5, priv, "hp2 %p hp2->h_addr_list %p " "num6addrs %d bufpos %p\n", hp2, hp2->h_addr_list, num6addrs, bufpos); hp2->h_addr_list[num6addrs] = bufpos; bufpos += sizeof(addr6); memcpy(hp2->h_addr_list[num6addrs++], &addr6, sizeof(addr6)); } #endif /* NETINET6 */ VERBOSE_FUNCLINE(5, priv); } else { if (inet_pton(AF_INET, pp[i], &addr) > 0) { VERBOSE_FUNCLINE(5, priv); hp->h_addr_list[num4addrs] = bufpos; bufpos += sizeof(addr); memcpy(hp->h_addr_list[num4addrs++], &addr, sizeof(addr)); } VERBOSE_FUNCLINE(5, priv); } i++; } sm_hbn_clandl: VERBOSE_FUNCLINE(5, priv); i = 0; if (num4addrs) { char address[256]; int j = 0; VERBOSE_FUNCLINE(5, priv); hp->h_addr = hp->h_addr_list[0]; hp->h_addr_list[num4addrs] = NULL; calm_host->host[i] = hp; calm_host->buf[i] = buf; calm_host->num++; i++; VERBOSE_FUNCLINE_MSG(5, priv, "hp %p hp->h_name %s hp->h_addr %p == \"%s\" " "hp->h_length %d hp->h_addrtype %d hp->h_addr_list %p hp->h_aliases %p\n", hp, hp->h_name, hp->h_addr, (inet_ntop(hp->h_addrtype, hp->h_addr, address, sizeof(address))) ? address : "error", hp->h_length, hp->h_addrtype, hp->h_addr_list, hp->h_aliases); while(hp->h_addr_list[j]) { VERBOSE_FUNCLINE_MSG(5, priv, "hp->h_addr_list[%d] %p == \"%s\"\n", j, hp->h_addr_list[j], (inet_ntop(hp->h_addrtype, hp->h_addr_list[j], address, sizeof(address))) ? address : "error"); j++; } j = 0; while(hp->h_aliases[j]) { VERBOSE_FUNCLINE_MSG(5, priv, "hp->h_aliases[%d] %p == \"%s\"\n", j, hp->h_aliases[j], hp->h_aliases[j]); j++; } } else { VERBOSE_FUNCLINE(5, priv); if (hp) free(hp); if (buf) free(buf); } VERBOSE_FUNCLINE(5, priv); if (hp2 && hp2 != hp) { VERBOSE_FUNCLINE(5, priv); if (num6addrs) { char address[256]; int j = 0; VERBOSE_FUNCLINE(5, priv); hp2->h_addr = hp2->h_addr_list[0]; hp2->h_addr_list[num6addrs] = NULL; calm_host->host[i] = hp2; calm_host->buf[i] = buf2; calm_host->num++; VERBOSE_FUNCLINE_MSG(5, priv, "hp2 %p hp2->h_name %s hp2->h_addr %p == \"%s\" " "hp2->h_length %d hp2->h_addrtype %d hp2->h_addr_list " "%p hp2->h_aliases %p\n", hp2, hp2->h_name, hp2->h_addr, (inet_ntop(hp2->h_addrtype, hp2->h_addr, address, sizeof(address))) ? address : "error", hp2->h_length, hp2->h_addrtype, hp2->h_addr_list, hp2->h_aliases); while(hp2->h_addr_list[j]) { VERBOSE_FUNCLINE_MSG(5, priv, "hp2->h_addr_list[%d] %p == \"%s\"\n", j, hp2->h_addr_list[j], (inet_ntop(hp2->h_addrtype, hp2->h_addr_list[j], address, sizeof(address))) ? address : "error"); j++; } j = 0; while(hp2->h_aliases[j]) { VERBOSE_FUNCLINE_MSG(5, priv, "hp2->h_aliases[%d] %p == \"%s\"\n", j, hp2->h_aliases[j], hp2->h_aliases[j]); j++; } } else { VERBOSE_FUNCLINE(5, priv); if (hp2) free(hp2); if (buf2) free(buf2); } } VERBOSE_FUNCLINE(5, priv); if (rwstat < EX_OK || (!num4addrs && !num6addrs)) { VERBOSE_FUNCLINE(5, priv); if (calm_host) calm_freehostent(priv, calm_host); calm_host = NULL; } if (pbuf[0]) { VERBOSE_FUNCLINE(5, priv); free(pbuf[0]); } if (pp && pp != pbuf) { VERBOSE_FUNCLINE(5, priv); for (i = 0; pp[i]; i++) if (pp[i]) free(pp[i]); free(pp); } VERBOSE_FUNCLINE(5, priv); return calm_host; } struct calm_hostent *calm_gethostbyname(struct mlfiPriv *priv, char *host) { struct hostent *hp1, *hp; size_t hstbuflen; char *tmphstbuf; int res = 0; int herr; struct calm_hostent *calm_host = NULL; time_t dns_start = time(NULL); int af = AF_INET; VERBOSE_FUNCLINE_MSG(5,priv, "host %s\n", host ? host : "(nil)"); if (!host) return calm_host; #if NETINET6 calm_gethost_redo_lookup: #endif /* NETINET6 */ VERBOSE_FUNCLINE(5,priv); if (priv && hostname_sm_lookup) { calm_host = calm_sm_gethostbyname(priv, host); if (calm_host) return(calm_host); VERBOSE_FUNCLINE(5,priv); } hstbuflen = 1024; /* Allocate buffer, remember to free it to avoid memory leakage. */ tmphstbuf = malloc(hstbuflen); if (!tmphstbuf) return calm_host; hp1 = malloc(2*sizeof(struct hostent)); /* why 2? dunno, fudge factor*/ if (!hp1) { VERBOSE_FUNCLINE_MSG(5, priv, "free tmphstbuf %p\n", tmphstbuf); free(tmphstbuf); return calm_host; } VERBOSE_FUNCLINE_MSG(5, priv, "alloc hp1 %p alloc tmphstbuf %p\n", hp1, tmphstbuf); while ((res = gethostbyname2_r(host, af, hp1, tmphstbuf, hstbuflen, &hp, &herr)) == ERANGE) { /* Enlarge the buffer. */ char * tbuf = realloc(tmphstbuf, (hstbuflen *= 2)); VERBOSE_FUNCLINE_MSG(5, priv, "alloc tbuf %p tmphstbuf %p\n", tbuf, tmphstbuf); if (!tbuf) break; else tmphstbuf = tbuf; } /* Check for errors. */ VERBOSE_FUNCLINE(5, priv); if (!calm_host) { calm_host = malloc(sizeof(struct calm_hostent)); if (calm_host) calm_host->num = 0; } if (res || hp == NULL || !calm_host || calm_timedout(priv, dns_start, CALM_TO_DNS_A)) { VERBOSE_FUNCLINE_MSG(5, priv, "free hp1 %p free tmphstbuf %p\n", hp1, tmphstbuf); VERBOSE(2, priv, "res %d, hp %p, calm_host %p, herr %d\n", res, hp, calm_host, herr); free(hp1); free(tmphstbuf); if (calm_host->num) return calm_host; calm_freehostent(priv, calm_host); return NULL; } VERBOSE_FUNCLINE_MSG(5, priv, "alloc calm_host %p\n", calm_host); if (hp != hp1) { VERBOSE_FUNCLINE_MSG(5, priv, "free hp1 %p\n", hp1); free(hp1); } calm_host->host[calm_host->num] = hp; calm_host->buf[calm_host->num] = tmphstbuf; calm_host->num++; VERBOSE_FUNCLINE(5, priv); #if NETINET6 if (af != AF_INET6) { af = AF_INET6; goto calm_gethost_redo_lookup; } VERBOSE_FUNCLINE(5, priv); #endif /* NETINET6 */ return calm_host; } int calm_getfqdn(struct mlfiPriv *priv, char *buf, int len) { struct calm_hostent *host; if (!buf || !*buf || len < 2) return 0; host = calm_gethostbyname(priv, buf); if (host) { strncpy(buf,host->host[0]->h_name,len-1); buf[len-1] = '\0'; VERBOSE_FUNCLINE_MSG(5, priv, "host %p\n", host); calm_freehostent(priv, host); return strlen(buf); } return 0; } /* lifted and modified from sendmail/domain.c * props and copyrights to sendmail/org and consortium */ /* ** CALM_GETMXRR -- get MX resource records for a domain ** ** Parameters: ** host -- the name of the host to MX. ** mxs -- a pointer to a return buffer of MX records. ** mxprefs -- a pointer to a return buffer of MX preferences. ** If NULL, don't try to populate. ** buf -- a pointer to space that should be freed when mxhosts is no longer needed. ** buflen -- the length of the buffer ** ** Returns: ** The number of MX records found. ** -1 if there is an internal failure. ** If no MX records are found, mxhosts[0] is set to host ** and 1 is returned. ** ** Side Effects: ** buf and buflen may be altered, realloced, alloced. */ # ifndef MAXPACKET # define MAXPACKET 8192 /* max packet size used internally by BIND */ # endif /* ! MAXPACKET */ # ifndef HFIXEDSZ # define HFIXEDSZ 12 /* sizeof(HEADER) */ # endif /* ! HFIXEDSZ */ #ifndef GETSHORT # define GETSHORT _getshort #endif #ifndef GETLONG # define GETLONG _getlong #endif #define MAXCNAMEDEPTH 10 # if defined(__RES) && (__RES >= 19940415) # define RES_UNC_T char * # else /* defined(__RES) && (__RES >= 19940415) */ # define RES_UNC_T unsigned char * # endif /* defined(__RES) && (__RES >= 19940415) */ union querybuf { HEADER qb1; unsigned char qb2[MAXPACKET]; }; pthread_mutex_t getmxrr_mutex = PTHREAD_MUTEX_INITIALIZER; int calm_getmxrr(struct mlfiPriv *priv, char *host, char ***mxs, unsigned short **mxprefs, char **buf, int *blen, int depth) { time_t dns_start = time(NULL); register unsigned char *eom = NULL, *cp = NULL; register int i, j, n, bufpos = 0; int nmx = 0; register char *bp = NULL; HEADER *hp; union querybuf answer; int ancount = 0, qdcount, buflen, expnmx; unsigned short pref = 0, type; bool trycanon = false; unsigned short *prefs = NULL; int (*resfunc) __P((const char *, int, int, u_char *, int)); int *weight = NULL; extern int res_query(), res_search(); char **mxhosts = NULL; char *bufp = NULL; char *lhost = NULL; if (priv && !priv->dns_mx_start) priv->dns_mx_start = dns_start; VERBOSE_FUNCLINE(5, priv); if (!host || *host == '\0' || !buf || !blen) return -1; if(calm_timedout(priv, dns_start, CALM_TO_DNS_MX)) return -1; /* efficiency hack -- numeric or non-MX lookups */ if (host[0] == '[') goto mx_punt; VERBOSE_FUNCLINE(5, priv); errno = 0; resfunc = res_search; pthread_mutex_lock(&getmxrr_mutex); if ((_res.options & RES_INIT) == 0 && res_init() == -1) { pthread_mutex_unlock(&getmxrr_mutex); return -1; } n = (*resfunc)(host, C_IN, T_MX, (unsigned char *) &answer, sizeof(answer)); pthread_mutex_unlock(&getmxrr_mutex); VERBOSE_FUNCLINE(5, priv); if (n < 0) { VERBOSE(3, priv, "res_search(%s) failed (errno=%d, h_errno=%d)\n", host == NULL ? "" : host, errno, h_errno); switch (h_errno) { case NO_DATA: trycanon = true; /* FALLTHROUGH */ case NO_RECOVERY: /* no MX data on this host */ goto mx_punt; case HOST_NOT_FOUND: case 0: /* broken resolvers */ /* host doesn't exist in DNS; might be in /etc/hosts */ trycanon = true; goto mx_punt; case TRY_AGAIN: case -1: return -1; break; default: VERBOSE(2, priv, "res_search (%s) failed with impossible h_errno (%d)\n", host, h_errno); return EX_OSERR; break; } /* irreconcilable differences */ return -1; } VERBOSE_FUNCLINE(5, priv); if(calm_timedout(priv, dns_start, CALM_TO_DNS_MX)) return -1; /* avoid problems after truncation in tcp packets */ if (n > sizeof(answer)) n = sizeof(answer); /* find first satisfactory answer */ hp = (HEADER *)&answer; cp = (unsigned char *)&answer + HFIXEDSZ; eom = (unsigned char *)&answer + n; for (qdcount = ntohs((unsigned short) hp->qdcount); qdcount--; cp += n + QFIXEDSZ) { if ((n = dn_skipname(cp, eom)) < 0) goto mx_punt; } VERBOSE_FUNCLINE(5, priv); ancount = ntohs((unsigned short) hp->ancount); expnmx = ancount+1; mx_storage: VERBOSE_FUNCLINE(5, priv); if (expnmx && *buf && *blen > 0) { bufp = bp = *buf; buflen = *blen; VERBOSE_FUNCLINE_MSG(5, priv, "bufp %p\n", bufp); } else if (expnmx) { VERBOSE_FUNCLINE(5, priv); bufp = bp = malloc(BUFSIZ); if (!bp) return -1; buflen = BUFSIZ; VERBOSE_FUNCLINE_MSG(5, priv, "alloc bufp %p\n", bufp); } if (expnmx) { VERBOSE_FUNCLINE(5, priv); mxhosts = malloc((expnmx+1) * sizeof(char *)); prefs = malloc((expnmx+1) * sizeof(short)); weight = malloc((expnmx+1) * sizeof(int)); if (host < bp+buflen && host >= bp) { lhost = strdup(host); host = lhost; } VERBOSE_FUNCLINE_MSG(5, priv, "alloc mxhosts %p alloc prefs %p alloc weight %p alloc host %p\n", mxhosts, prefs, weight, host); if (!mxhosts || !prefs || !weight || !host) goto mx_cleanup_and_leave; mxhosts[expnmx] = NULL; } /* See RFC 1035 for layout of RRs. */ while (--ancount >= 0 && cp < eom) { int ttl; VERBOSE_FUNCLINE(5, priv); if ((n = dn_expand((unsigned char *)&answer, eom, cp, (RES_UNC_T) bp+bufpos, buflen-bufpos)) < 0) break; cp += n; GETSHORT(type, cp); cp += INT16SZ; /* skip over class */ GETLONG(ttl, cp); GETSHORT(n, cp); /* rdlength */ if (type == T_MX) GETSHORT(pref, cp); /* very crude*/ if (bufpos > buflen/2) { VERBOSE_FUNCLINE(5, priv); bufp = realloc(bp, (buflen *= 2)); if (!bufp) goto mx_cleanup_and_leave; bp = bufp; } VERBOSE_FUNCLINE(5, priv); if ((n = dn_expand((unsigned char *)&answer, eom, cp, (RES_UNC_T) bp+bufpos, buflen-bufpos)) < 0) break; cp += n; n = strlen(bp+bufpos); VERBOSE(3, priv, "got name %s\n", bp+bufpos); VERBOSE_FUNCLINE_MSG(5, priv, " bp %p bufpos %d n %d bp[bufpos+n-1] %c bp %s bp+bufpos %s\n", bp, bufpos, n, bp[bufpos+n-1], (n || bufpos) ? bp : "", (n) ? bp+bufpos : ""); if (bp[bufpos+n-1] != '.') { struct in_addr ia; #if NETINET6 struct in6_addr ia6; #endif if (!getmx_detect_ipa || ( !inet_pton(AF_INET, bp+bufpos, &ia) && #if NETINET6 !inet_pton(AF_INET6, bp+bufpos, &ia6) && #endif 1)) bp[bufpos+n++] = '.'; } prefs[nmx] = pref; *(bp+bufpos+n++) = '\0'; VERBOSE_FUNCLINE(4, priv); if (type != T_MX) { VERBOSE_FUNCLINE(5, priv); if (type == T_CNAME) { VERBOSE_FUNCLINE(5, priv); if (depth++ >= MAXCNAMEDEPTH) { VERBOSE(3, priv, "CNAME recursion depth exeeeded\n"); goto mx_cleanup_and_leave; } VERBOSE_FUNCLINE_MSG(5, priv, "free mxhosts %p free prefs %p\n", mxhosts, prefs); free(mxhosts); free(prefs); prefs = NULL; mxhosts = NULL; bufp = bp; nmx = calm_getmxrr(priv, bp+bufpos, &mxhosts, &prefs, &bufp, &buflen, depth); if (nmx && bufp) bp = bufp; VERBOSE_FUNCLINE(5, priv); goto mx_return_and_leave; } VERBOSE(4, priv, "unexpected answer type %d, size %d\n", type, n); cp += n; continue; } mxhosts[nmx++] = bp+bufpos; VERBOSE(3, priv, "found mx host %s\n", mxhosts[nmx-1]); VERBOSE_FUNCLINE(5, priv); bufpos+=n; } mxhosts[nmx] = NULL; VERBOSE_FUNCLINE(5, priv); /* sort the records */ for (i = 0; i < nmx; i++) { for (j = i + 1; j < nmx; j++) { if (prefs[i] > prefs[j] || (prefs[i] == prefs[j] && weight[i] > weight[j])) { register int temp; register char *temp1; temp = prefs[i]; prefs[i] = prefs[j]; prefs[j] = temp; temp1 = mxhosts[i]; mxhosts[i] = mxhosts[j]; mxhosts[j] = temp1; temp = weight[i]; weight[i] = weight[j]; weight[j] = temp; } } } VERBOSE_FUNCLINE(5, priv); /* delete duplicates from list (yes, some bozos have duplicates) */ for (i = 0; i < nmx - 1; ) { if (strcasecmp(mxhosts[i], mxhosts[i + 1]) != 0) i++; else { /* compress out duplicate */ for (j = i + 1; j < nmx; j++) { mxhosts[j] = mxhosts[j + 1]; prefs[j] = prefs[j + 1]; } nmx--; } } VERBOSE_FUNCLINE(5, priv); if (priv && priv->macro_j) { for (i = 0; i < nmx; i++) { if(strcasecmp(mxhosts[i], priv->macro_j) == 0) { mxhosts[i] = NULL; nmx = i; break; } } } VERBOSE_FUNCLINE(5, priv); if (nmx == 0) { int hlen; mx_punt: VERBOSE_FUNCLINE(5, priv); if (!bp || !mxhosts || !prefs || !weight) { expnmx = 1; goto mx_storage; } hlen = strlen(host); if (hlen < (buflen - bufpos - 1)) memmove(bp, host, hlen); else { mx_punt2: VERBOSE_FUNCLINE(5, priv); bufp = realloc(bp, (buflen*=2)); if (!bufp) goto mx_cleanup_and_leave; bp = bufp; goto mx_punt; } bp[hlen] = '\0'; mxhosts[0] = bp; prefs[0] = 0; nmx = 1; if (bp[0] == '[' && bp[hlen-1] == ']') { VERBOSE_FUNCLINE(5, priv); (mxhosts[0])++; *(bp+hlen-1) = '\0'; } else if (trycanon && (hlen = calm_getfqdn(priv, bp, buflen))) { VERBOSE_FUNCLINE(5, priv); if (hlen >= buflen - 2) goto mx_punt2; if (bp[hlen-1] != '.' && hlen < buflen - 2) { bp[hlen++] = '.'; bp[hlen] = '\0'; } VERBOSE_FUNCLINE(5, priv); } } VERBOSE_FUNCLINE(5, priv); if (!nmx) goto mx_cleanup_and_leave; mx_return_and_leave: VERBOSE_FUNCLINE_MSG(5, priv, "priv %p %sprefs %p %sbp %p %sbufp %p buf %p *buf %p" "%smxhosts %p free weight %p free lhost %p\n", priv, (mxprefs) ? "" : "free ", prefs, (buf || bufp || !bp) ? "" : "free ", bp, buf, (buf || !bufp) ? "" : "free ", bufp, (buf) ? *buf : NULL, (mxs) ? "" : "free ", mxhosts, weight, lhost); if (priv) priv->dns_mx_start = 0; if (buf) *buf = bufp; else if (bufp) free(bufp); else if (bp) free(bp); VERBOSE_FUNCLINE(5, priv); if (blen) *blen = buflen; if (mxs) *mxs = mxhosts; else if (mxhosts) free(mxhosts); VERBOSE_FUNCLINE(5, priv); if (mxprefs) *mxprefs = prefs; else if (prefs) free(prefs); VERBOSE_FUNCLINE(5, priv); if (weight) free(weight); if (lhost) free(lhost); VERBOSE_FUNCLINE(5, priv); return nmx; mx_cleanup_and_leave: VERBOSE_FUNCLINE_MSG(5, priv, "priv %p free prefs %p %sbp %p %sbufp %p buf %p *buf %p" "free mxhosts %p free weight %p free lhost %p\n", priv, prefs, (bp && (!buf || *buf != bp)) ? "free " : "", bp, buf, bufp, (buf) ? *buf : NULL, mxhosts, weight, lhost); if (priv) priv->dns_mx_start = 0; if (prefs) free(prefs); if (bp && (!buf || *buf != bp)) { free(bp); if (*buf) *buf = NULL; } if (mxhosts) free(mxhosts); if (weight) free(weight); if (lhost) free(lhost); return -1; } /* all this looping through remotes response charachter by charachter ..... */ #define LAST_SMTPLINE(a,oline) do { \ char * pline, *nline; \ \ oline = pline = a; \ while((nline=strchr(pline+1,'\n'))) \ { \ oline = pline; \ pline = nline; \ } \ if (oline != a) \ oline++; \ } while (0) int read_smtp(struct mlfiPriv *priv, int consock, char **buf, ssize_t *buflen, ssize_t *bytes_read) { int retval; time_t read_start = time(NULL); int loopcnt = MAXREADLOOPS; VERBOSE_FUNCLINE(5, priv); if (!buf || !buflen || !bytes_read || consock < 0) return -1; if (!*buflen) *buflen = BUFSIZ; if (!*buf) *buf = malloc(*buflen); VERBOSE_FUNCLINE_MSG(5, priv, "alloc *buf %p\n", *buf); if (!*buf) return -1; *bytes_read = 0; VERBOSE_FUNCLINE(5, priv); retval = calm_select_read(priv, consock, Calm_Timeouts[CALM_TO_READ], 0); while (retval > 0 && loopcnt--) { char * nline; int k; VERBOSE_FUNCLINE(5, priv); if (calm_timedout(priv, read_start, Calm_Timeouts[CALM_TO_READ])) goto read_cleanup_and_leave; k = read(consock, (*buf)+ *bytes_read, *buflen - *bytes_read - 1); if (calm_timedout(priv, read_start, CALM_TO_READ)) goto read_cleanup_and_leave; if (k < 0) { VERBOSE_FUNCLINE(5, priv); if (errno == EAGAIN || errno == EINTR) { VERBOSE_FUNCLINE(5, priv); continue; } else { read_cleanup_and_leave: VERBOSE_FUNCLINE_MSG(5, priv, "free *buf %p\n", *buf); free(*buf); *buf = NULL; *buflen = 0; *bytes_read = 0; return -1; } } VERBOSE_FUNCLINE(5, priv); *bytes_read += k; (*buf)[*bytes_read] = '\0'; if (*bytes_read > (*buflen - 1)) { char * bufp = realloc(*buf, (*buflen *= 2)); VERBOSE_FUNCLINE_MSG(5, priv, "realloc *buf %p bufp %p\n", *buf, bufp); if (!bufp) goto read_cleanup_and_leave; *buf = bufp; } VERBOSE_FUNCLINE(5, priv); if (*((*buf)+*bytes_read-1) == '\n') nline = (*buf)+*bytes_read-1; else nline = strchr(*buf, '\n'); if (nline) { char * lline = *buf; char * lbuf = *buf; VERBOSE_FUNCLINE(5, priv); LAST_SMTPLINE(lbuf,lline); if ((lline - lbuf) > (*bytes_read - 4)) nline = NULL; else if (lline[3] == '-') nline = NULL; } VERBOSE_FUNCLINE(5, priv); VERBOSE(4, priv, "nline %s\n", nline); if (!nline || *bytes_read < 4 || (*buf)[3] == '-') retval = calm_select_read(priv, consock, nline ? 0 : Calm_Timeouts[CALM_TO_READ], nline ? 50000 : 0); else return 1; VERBOSE_FUNCLINE(5, priv); if (retval < 0) return 1; } if (!loopcnt) VERBOSE(2, priv, "reached MAXREADLOOPS of %d with a bytes_read %d and buflen %d\n", MAXREADLOOPS, *bytes_read, *buflen); VERBOSE_FUNCLINE(5, priv); return retval; } int write_smtp(struct mlfiPriv *priv, int consock, char *buf, ssize_t buflen, struct iovec *vecs, int num_vecs) { struct iovec lvector[32]; struct iovec *vector = lvector; int num_vectors = (sizeof lvector)/(sizeof lvector[0]); int j,i = 0; ssize_t len = 0; ssize_t bytes_wrote = 0; time_t write_start = time(NULL); VERBOSE_FUNCLINE(5, priv); if (consock < 0) return -1; if (buf && buflen) { vector[i].iov_base = buf; vector[i++].iov_len = buflen; len = buflen; if (num_vecs + i > num_vectors) { vector = malloc(sizeof(struct iovec) * num_vecs + i); VERBOSE_FUNCLINE_MSG(5, priv, "alloc vector %p\n", vector); if (!vector) return -1; num_vectors = num_vecs + i; } for (j = 0; i < num_vectors; i++) { vector[i].iov_base = vecs[j].iov_base; vector[i].iov_len = vecs[j].iov_len; len += vector[i].iov_len; } VERBOSE_FUNCLINE(5, priv); } else { vector = vecs; num_vectors = num_vecs; for (i = 0; i < num_vectors; i++) len += vector[i].iov_len; } VERBOSE_FUNCLINE(5, priv); i = 0; for (;;) { ssize_t k; VERBOSE_FUNCLINE(5, priv); if (calm_select_write(priv, consock, Calm_Timeouts[CALM_TO_SEND], 0) < 0) break; VERBOSE_FUNCLINE(5, priv); if (calm_timedout(priv, write_start, CALM_TO_SEND)) break; k = writev(consock, vector, num_vectors); if (calm_timedout(priv, write_start, CALM_TO_SEND)) break; if (k > 0) { VERBOSE_FUNCLINE(5, priv); bytes_wrote += k; if (k >= len) break; } else if (errno != EAGAIN && errno != EINTR) break; VERBOSE_FUNCLINE(5, priv); for (i = 0; i < num_vectors; i++) { VERBOSE_FUNCLINE(5, priv); if (vector[i].iov_len > (unsigned int) k) { VERBOSE_FUNCLINE(5, priv); vector[i].iov_base = (char *)vector[i].iov_base + k; vector[i].iov_len -= (unsigned int) k; break; } k -= (int) vector[i].iov_len; vector[i].iov_len = 0; } VERBOSE_FUNCLINE(5, priv); if (i == num_vectors) /* this shouldnt happen */ break; } VERBOSE_FUNCLINE(5, priv); if (vector != lvector && vector != vecs) { VERBOSE_FUNCLINE_MSG(5, priv, "free vector %p\n", vector); free(vector); } if (bytes_wrote == len) return 1; VERBOSE_FUNCLINE(5, priv); return -1; } #define CHECK_READ_SMTP(a,b,c,d) do {\ if (bytes_read > 2) \ { \ char * line = buf; \ LAST_SMTPLINE(buf,line); \ if (strncmp(line, a, b) != 0) \ { \ if (d) \ { \ d = 0; \ } \ else \ goto c; \ } \ } \ else \ if (d) \ { \ d = 0; \ } \ else \ goto c; \ } while (0) /* return to caller * 1 Verified Good, chek p * 0 Verified Bad, check p. * -1 Error, possible temporary, check p. * -2 Error, possible temporary, check p, close consock. * -3 Error, possible temporary, check p, close consock, open consock, retry. * -4 Error. free p. Close consock. retry with different dsthost only. * * p may contain a dynamic allocated string from smtp server. If p != NULL you must free it. * * rset = 1, try to RSET open connection * rset = 0, new connection with helo * rset = -1, do smtp quit * rset = -2, just do RSET */ int verify_smtprcpt(struct mlfiPriv *priv, int consock, int *rset, char * envfrom, char * envrcpt, char **p, ssize_t *plen) { char *buf = NULL; ssize_t buflen; ssize_t bytes_read = 0; struct iovec vector[10]; int envfrom_len = 0, envrcpt_len = 0, curaddr_len; char *curaddr; char smtp_helo_cmd[] = "helo "; char smtp_ehlo_cmd[] = "ehlo "; char smtp_mail_cmd[] = "mail from:"; char smtp_rcpt_cmd[] = "rcpt to:"; char smtp_rset_cmd[] = "rset"; char smtp_quit_cmd[] = "quit"; char smtp_newline[] = "\r\n"; char smtp_from_null[] = "<>"; char smtp_to_postm[] = ""; char smtp_left_brack[] = "<"; char smtp_right_brack[] = ">"; int retval = -1; char myhname[1024]; int myhname_len = 0; int i = 0; VERBOSE_FUNCLINE_MSG(4, priv, "consock %d rset %p %d envfrom %s envrcpt %s p %p *p %p plen %p\n", consock, rset, (rset) ? *rset : 0, envfrom, envrcpt, p, (p) ? *p : NULL, plen); if (p) buf = *p; if (plen) buflen = *plen; /* XXX: * perhaps there should be dedicated functions for rset, quit */ if (rset && *rset == -1) { retval = 1; VERBOSE_FUNCLINE(5, priv); goto do_quit; } else if (rset && *rset == -2) goto do_rset; if (envrcpt && envrcpt[0]) { VERBOSE_FUNCLINE(5, priv); envrcpt_len = strlen(envrcpt); } else if (calm_envrcpt) { VERBOSE_FUNCLINE(5, priv); envrcpt = calm_envrcpt; envrcpt_len = calm_envrcpt_len; } else { VERBOSE_FUNCLINE(5, priv); envrcpt = smtp_to_postm; envrcpt_len = sizeof(smtp_to_postm) - 1; } if (envfrom && envfrom[0]) { VERBOSE_FUNCLINE(5, priv); envfrom_len = strlen(envfrom); } else if (envfrom && (!envfrom[0])) { VERBOSE_FUNCLINE(5, priv); envfrom = smtp_from_null; envfrom_len = sizeof(smtp_from_null) - 1; } else if (priv && priv->sender_address && verify_sender) { VERBOSE_FUNCLINE(5, priv); envfrom = priv->sender_address; envfrom_len = priv->sender_address_len; } else if (calm_envfrom) { VERBOSE_FUNCLINE(5, priv); envfrom = calm_envfrom; envfrom_len = calm_envfrom_len; } else { VERBOSE_FUNCLINE(5, priv); envfrom = smtp_from_null; envfrom_len = sizeof(smtp_from_null) - 1; } VERBOSE_FUNCLINE(5, priv); vector[2].iov_base = smtp_newline; vector[2].iov_len = sizeof(smtp_newline) - 1; if(!rset || !*rset) { VERBOSE_FUNCLINE(5, priv); bytes_read = 0; if(read_smtp(priv, consock, &buf, &buflen, &bytes_read) < 0) goto cleanup_and_leave; VERBOSE_FUNCLINE(5, priv); retval = -1; VERBOSE(4, priv, "buflen %d bytes_read %d buf %s\n", buflen, bytes_read, buf); CHECK_READ_SMTP("220",3,cleanup_and_leave,retval); if (!retval) { VERBOSE_FUNCLINE(5, priv); retval = -1; VERBOSE(4, priv, "buflen %d bytes_read %d buf %s\n", buflen, bytes_read, buf); CHECK_READ_SMTP("4",1,cleanup_and_leave,retval); if (!retval) retval = -1; else retval = -2; do_quit: VERBOSE_FUNCLINE(5, priv); vector[0].iov_base = smtp_quit_cmd; vector[0].iov_len = sizeof(smtp_quit_cmd) - 1; vector[1].iov_base = smtp_newline; vector[1].iov_len = sizeof(smtp_newline) - 1; write_smtp(priv, consock, NULL, 0, vector, 2); goto cleanup_and_leave; } VERBOSE_FUNCLINE(5, priv); if (priv && priv->macro_j && bytes_read > 4+1+priv->macro_j_len && buf[4+priv->macro_j_len] == ' ' && (strncasecmp(buf+4,priv->macro_j, priv->macro_j_len) == 0)) { VERBOSE_FUNCLINE(5, priv); retval = -4; if (rset) *rset = 0; VERBOSE(2, priv,"I was talking to my own connected MTA?\n"); goto do_quit; } if (strstr(buf,"ESMTP")) { VERBOSE_FUNCLINE(5, priv); vector[0].iov_base = smtp_ehlo_cmd; vector[0].iov_len = sizeof(smtp_ehlo_cmd) - 1; } else { VERBOSE_FUNCLINE(5, priv); vector[0].iov_base = smtp_helo_cmd; vector[0].iov_len = sizeof(smtp_helo_cmd) - 1; } if (priv && priv->myhnamep && !MyHname) { VERBOSE_FUNCLINE(5, priv); vector[1].iov_base = priv->myhnamep; vector[1].iov_len = priv->myhname_len; } else if (!priv || !MyHname) { get_my_hname: VERBOSE_FUNCLINE(5, priv); gethostname(myhname, sizeof(myhname)); if (!(myhname_len = calm_getfqdn(priv, myhname, sizeof(myhname)))) myhname_len = strlen(myhname); vector[1].iov_base = myhname; vector[1].iov_len = myhname_len; } else if (MyHname) { VERBOSE_FUNCLINE(5, priv); vector[1].iov_base = MyHname; vector[1].iov_len = MyHname_len; } else goto get_my_hname; VERBOSE_FUNCLINE(5, priv); if(write_smtp(priv, consock, NULL, 0, vector, 3) < 0) goto cleanup_and_leave; VERBOSE_FUNCLINE(5, priv); bytes_read = 0; if(read_smtp(priv, consock, &buf, &buflen, &bytes_read) < 0) goto cleanup_and_leave; VERBOSE(4, priv, "buflen %d bytes_read %d buf %s\n", buflen, bytes_read, buf); CHECK_READ_SMTP("250",3,cleanup_and_leave,retval); if (!retval) { retval = -4; if (rset) *rset = 0; goto do_quit; } if (rset) *rset = 1; VERBOSE_FUNCLINE(5, priv); } else if (rset && *rset) { do_rset: VERBOSE_FUNCLINE(5, priv); vector[0].iov_base = smtp_rset_cmd; vector[0].iov_len = sizeof(smtp_rset_cmd) - 1; vector[1].iov_base = smtp_newline; vector[1].iov_len = 2; VERBOSE_FUNCLINE(5, priv); if(write_smtp(priv, consock, NULL, 0, vector, 2) < 0) goto cleanup_and_leave; bytes_read = 0; if(read_smtp(priv, consock, &buf, &buflen, &bytes_read) < 0) goto cleanup_and_leave; VERBOSE_FUNCLINE(5, priv); retval = -1; VERBOSE(4, priv, "buflen %d bytes_read %d buf %s\n", buflen, bytes_read, buf); CHECK_READ_SMTP("250",3,cleanup_and_leave,retval); if (!retval) { *rset = 0; goto do_quit; } if (rset && *rset == -2) { retval = 1; goto cleanup_and_leave; } VERBOSE_FUNCLINE(5, priv); } VERBOSE_FUNCLINE(5, priv); vector[i].iov_base = smtp_mail_cmd; vector[i++].iov_len = sizeof(smtp_mail_cmd) - 1; curaddr = envfrom; curaddr_len = envfrom_len; do_mailrcpt: VERBOSE_FUNCLINE(5, priv); if (*curaddr == '<') { vector[i].iov_base = curaddr; vector[i++].iov_len = curaddr_len; } else { vector[i].iov_base = smtp_left_brack; vector[i++].iov_len = 1; vector[i].iov_base = curaddr; vector[i++].iov_len = curaddr_len; } if (curaddr[curaddr_len-1] != '>') { vector[i].iov_base = smtp_right_brack; vector[i++].iov_len = 1; } if (i > 2) { vector[i].iov_base = smtp_newline; vector[i++].iov_len = sizeof(smtp_newline) - 1; } else i++; VERBOSE_FUNCLINE(5, priv); if(write_smtp(priv, consock, NULL, 0, vector, i) < 0) goto cleanup_and_leave; bytes_read = 0; if(read_smtp(priv, consock, &buf, &buflen, &bytes_read) < 0) goto cleanup_and_leave; retval = -1; VERBOSE_FUNCLINE(5, priv); VERBOSE(4, priv, "buflen %d bytes_read %d buf %s\n", buflen, bytes_read, buf); CHECK_READ_SMTP("250",3,cleanup_and_leave,retval); if (!retval) { VERBOSE_FUNCLINE(5, priv); CHECK_READ_SMTP("421",3,cleanup_and_leave,retval); CHECK_READ_SMTP("5",1,cleanup_and_leave,retval); retval = -1; CHECK_READ_SMTP("4",1,cleanup_and_leave,retval); } VERBOSE_FUNCLINE(5, priv); if (vector[0].iov_base == smtp_rcpt_cmd) { retval = 1; goto cleanup_and_leave; } i = 0; vector[i].iov_base = smtp_rcpt_cmd; vector[i++].iov_len = sizeof(smtp_rcpt_cmd) - 1; curaddr = envrcpt; curaddr_len = envrcpt_len; VERBOSE_FUNCLINE(5, priv); goto do_mailrcpt; if (!rset) goto do_quit; cleanup_and_leave: VERBOSE_FUNCLINE(5, priv); if (buf && p) { VERBOSE_FUNCLINE_MSG(5, priv, "buf %p *p %p\n",*buf, p); *p = buf; if (plen) *plen = bytes_read; } else if (buf) { VERBOSE_FUNCLINE_MSG(5, priv, "buf %p\n", p); free(buf); } return retval; } void calm_smtpshutdown(struct mlfiPriv *priv, int consock) { int rset = -1; int lconsock; VERBOSE_FUNCLINE(5, priv); if (!priv && consock < 0) return; if (priv && priv->consock > -1 && priv->consock_con) lconsock = priv->consock; else lconsock = consock; if(lconsock > -1 && verify_smtprcpt(priv, lconsock, &rset, NULL, NULL, NULL, NULL) > -1) { VERBOSE_FUNCLINE(5, priv); shutdown(lconsock, SHUT_RDWR); close(lconsock); } if (priv && priv->consock_con && priv->consock == lconsock) { VERBOSE_FUNCLINE(5, priv); priv->consock = -1; priv->consock_con = 0; } VERBOSE_FUNCLINE(5, priv); } /* returned is entry number in table on success or -1 on error * entry pointer is stored in centry if provided * centry is used if possible, otherwise appropriate location looked up * consock is returned and entry is unlocked for next user * * if active == false then all waiting users should pick up on that and move on */ int calm_conc_putcon(struct mlfiPriv *priv, struct conc_entry **centry, int consock, bool active) { int i = 0; int j = -1; struct conc_entry *lentry = NULL; bool locking = false; if (!centry || !*centry) { if ((!priv || !priv->conc_table) && pt_host_connection_caching) return -1; if (pt_host_connection_caching) { for (i = 0; i < conc_conf[CONF_TABLESZ]; i++) { if (j < 0 && !(priv->conc_table[i]).active) j = i; if ((priv->conc_table[i]).consock == consock) break; } if (i < conc_conf[CONF_TABLESZ]) lentry = &priv->conc_table[i]; else if (j > -1) lentry = &priv->conc_table[j]; } else if (conc_table) { for (i = 0; i < conc_conf[CONF_TABLESZ]; i++) { if (j < 0 && !(conc_table[i]).active) j = i; else if ((conc_table[i]).consock == consock) break; } if (i < conc_conf[CONF_TABLESZ]) lentry = &conc_table[i]; else if (j > -1) lentry = &conc_table[j]; locking = true; } } else { lentry = *centry; locking = lentry->locking; } if (!lentry) { /* we werent provided one and apparently there is either no more room or none preexisting */ if (centry) *centry = lentry; return i; } if (locking) { if (!lentry->active && pthread_mutex_unlock(&lentry->conc_mutex) == EINVAL) { pthread_mutex_init(&lentry->conc_mutex, NULL); pthread_mutex_init(&lentry->conc_cond_mutex, NULL); pthread_cond_init(&lentry->conc_cond, NULL); lentry->locking = true; lentry->waiters = 0; lentry->consock = consock; lentry->active = active; } else { pthread_mutex_lock(&lentry->conc_cond_mutex); lentry->active = active; if (lentry->waiters > 0) pthread_cond_broadcast(&lentry->conc_cond); pthread_mutex_unlock(&lentry->conc_cond_mutex); lentry->consock = true; lentry->active = true; pthread_mutex_unlock(&lentry->conc_mutex); } } else { lentry->consock = consock; lentry->locking = false; lentry->active = active; } if (priv && priv->consock_con && priv->consock == consock) { /* good design means the program should not be relying on this */ priv->consock = -1; priv->consock_con = false; } if (centry) *centry = lentry; return i; } /* -1 no consock found or entry deleted as per delentry * -2 entry is locked with too many waiters * > -1 consock to be used * * upon succes entry slot is stored in centry if provided */ int calm_conc_getcon(struct mlfiPriv *priv, struct conc_entry *ctable, struct conc_entry **centry, int num, bool delentry) { int getcon_start = time(NULL); struct conc_entry * lentry = NULL; int consock = -1; if (num < -1 || !(num < conc_conf[CONF_TABLESZ])) return consock; if (ctable) lentry = &ctable[num]; else if (priv && priv->conc_table) { lentry = &priv->conc_table[num]; } else lentry = &conc_table[num]; if (!lentry->active) return consock; if (!lentry->locking) { /* no locking, no waiting */ consock = lentry->consock; if (delentry) { lentry->consock = -1; lentry->active = false; } } else { /*lock?*/ if (!delentry && conc_conf[CONF_MAXWAITERS] && lentry->waiters > conc_conf[CONF_MAXWAITERS]) return -2; do { struct timespec tv; int wait_ret = 0; tv.tv_sec = conc_conf[CONF_GETCONWT]; tv.tv_nsec = 0; pthread_mutex_lock(&lentry->conc_cond_mutex); lentry->waiters++; if (pthread_mutex_trylock(&lentry->conc_mutex) == EBUSY) { wait_ret = pthread_cond_timedwait(&lentry->conc_cond, &lentry->conc_cond_mutex, &tv); pthread_mutex_lock(&lentry->conc_cond_mutex); lentry->waiters--; pthread_mutex_unlock(&lentry->conc_cond_mutex); } else { if ((lentry->active && delentry) || !lentry->active) { if (lentry->active) consock = lentry->consock; else lentry->consock = consock; lentry->active = false; pthread_mutex_unlock(&lentry->conc_mutex); while (lentry->waiters) { if(pthread_mutex_trylock(&lentry->conc_cond_mutex)) break; pthread_cond_broadcast(&lentry->conc_cond); pthread_mutex_unlock(&lentry->conc_cond_mutex); } } else if (lentry->active) { /* our caller is responsible for unlocking centry->conc_mutex */ /* typically this is done with with calm_con_putcon() */ consock = lentry->consock; } break; } } while ((time(NULL) - getcon_start) < conc_conf[CONF_GETCONWTMX]); } if (priv && consock > -1) { /* good design means the program should not be relying on this */ if (priv->consock_con) calm_conc_putcon(priv, NULL, priv->consock, priv->consock_con); priv->consock = consock; priv->consock_con = true; } if (centry) *centry = lentry; return consock; } int calm_conc_closeone(struct mlfiPriv *priv, struct conc_entry *centry) { int consock = -1; consock = calm_conc_getcon(priv, centry, NULL, 0, true); if (consock > -1) calm_smtpshutdown(priv, consock); return (consock > -1); } int calm_conc_closeall(struct mlfiPriv *priv, struct conc_entry *ctable, int ctable_sz) { int i,j; if (!ctable || ctable_sz < 1) return 0; for (j = i = 0; i < ctable_sz; i++) j += calm_conc_closeone(priv, &ctable[i]); return j; } #if DBCACHE time_t calm_cache_breakdown(struct cache_breakdown *cb, time_t ctime, int thresh, int cb_len) { int i, j; time_t threshold = 0; struct cache_breakdown lcb; if (!cb || cb_len < 1 || !ctime || !thresh) return threshold; for (i = 0; i < cb_len; i++) if (!(cb[i])->occurences) break; for (j = cb_len - 1; j; j--) if (!(cb[j])->occurences) break; } /* returns number of records deleted * keeps semi-persistent info in ce */ int conc_walk_expire(struct mlfiPriv *priv, int work_secs, struct cache_expire **ce) { time_t work_start = time(NULL); double lwork_secs = work_secs; struct conc_entry *ltable; DB *dbp = NULL; DBC *cp = NULL; DBT *key = NULL, *data = NULL; bool *lconc_stop; conc_data *concp = NULL; time_t expire_now = time(NULL); int i = 0, dbret = 0; struct cache_breakdown *cb = NULL; int num_records = 0; int cursor_flag = 0; time_t cb_threshold = 0; int cb_scale = 0; int *full_flag = NULL; if (priv && priv->conc_dbp) { dbp = priv->conc_dbp; lconc_stop = &priv->conc_stop; ltable = priv->conc_table; full_flag = &priv->conc_full; } else if (conc_dbp) { dbp = conc_dbp; lconc_stop = &conc_stop; ltable = conc_table; full_flag = &conc_full; } else return 0; if (!ce || !*ce) { if(dbp->cursor(dbp, NULL, &cp, 0)) goto conc_we_clandl; key = malloc(sizeof(DBT)); if (key) memset(key, 0, sizeof(DBT)); data = malloc(sizeof(DBT)); if (data) { memset(data, 0, sizeof(DBT)); data->flags |= DB_DBT_REALLOC; } if (!*ce) *ce = malloc(sizeof(struct cache_expire)); cursor_flag = DB_FIRST; } else { cp = (*ce)->cp; key = (*ce)->key; data = (*ce)->data; cb = (*ce)->cb; num_recs = (*ce)->num_recs; cursor_flag = (*ce)->cursor_flag; } if (conc_conf[CONF_MAXENTRIES] && !cb) { cb_scale = (conf_conf[CONF_THRESHOLD] % 10) ? 100 : 10; cb = malloc(sizeof(struct cache_breakdown)* cb_scale); if (cb) memset(cb, '\0', ssizeof(struct cache_breakdown)* cb_scale); } if (cp && key && data && cb && (!ce || *ce)) while (!(*lconc_stop) && !(dbret = cp->c_get(cp, &key, &data, cursor_flag))) { cursor_flag = DB_NEXT; concp = data.data; if (!concp || concp->magic != CALLAHEADMAGIC || concp->len != data.size) { cp->c_del(cp, 0); i++; num_recs--; } else if ((conc_conf[CONF_ABSEXP] && expire_time - concp->con_start > conc_conf[CONF_ABSEXP]) || (conc_conf[CONF_UPDEXP] && expire_time - concp->con_updated > conc_conf[CONF_UPDEXP]) || (conc_conf[CONF_MAXRST] && concp->rsets > conc_conf[CONF_MAXRST])) { if (concp->num > -1) calm_conc_closeone(priv, lconc_table[concp->num]); cp->c_del(cp, 0); i++; num_recs--; } else if (cb && full_flag && *full_flag && num_recs > (conc_conf[CONF_MAXENTRIES] * (.01 * conc_conf[CONF_THRESHOLD]))) { if (!cb_threshold) cb_threshold = calm_cache_breakdown(cb, concp->con_updated, conc_conf[CONF_THRESHOLD], cb_scale); if (concp->con_updated < cb_threshold) { if (concp->num > -1) calm_conc_closeone(priv, lconc_table[concp->num]); cp->c_del(cp, 0); i++; num_recs--; } } else { num_recs++; if (cb) { cb_threshold = calm_cache_breakdown(cb, concp->con_updated, conc_conf[CONF_THRESHOLD], cb_scale); if (num_recs >= conc_conf[CONF_MAXENTRIES]) { if (full_flag) *full_flag = 1; } else if (full_flag) *full_flag = 0; } } if (time(NULL) - expire_now >= work_secs) break; } if (!ce || !*ce) { if (cp) cp->close(cp); if (key && key->data) free(key->data); if (key) free(key); if (data && data->data) free(data->data) if (data) free(data); if (cb) free(cb); } else { (*ce)->cp = cp; (*ce)->key = key; (*ce)->data = data; (*ce)->cb = cb; (*ce)->num_recs = num_recs; if (dbret == DB_NOTFOUND) { cursor_flag = DB_FIRST; (*ce)->total_recs = num_recs; } (*ce)->cursor_flag = cursor_flag; } return i; } struct conc_entry * calm_conc_table_init(int num) { int i; struct conc_entry *conc_table = conc_table = malloc(num * sizeof(struct conc_entry)); if (!conc_table) return NULL; #if FULL_CONC_TABLE_INIT memset(conc_table, '\0', conc_conf[CONF_TABLESZ] * sizeof(struct conc_entry)); #else for (i = 0; i < num; i++) conc_table[i].active = false; /* only preinitialized field */ #endif /* FULL_CONC_TABLE_INIT */ return conc_table; } void calm_cache_expire_free(struct mlfiPriv *priv, struct cache_expire **ce) { if (ce && *ce) { if ((*ce)->key) { if ((*ce)->key->data) free((*ce)->key->data); free((*ce)->key); } if ((*ce)->data) { if ((*ce)->data->data) free((*ce)->data->data); free((*ce)->data); } if ((*ce)->cb) free((*ce)->cb); if ((*ce)->cp) (*ce)->cp->close(ce->cp); free((*ce)); *ce = NULL; } return; } void * calm_conc_maint(struct mlfiPriv *priv) { DB *dbp; DB_ENV *db_envp; int sleep_secs; int work_secs; bool *lconc_stop; int retval = 0; struct conc_entry *ltable; struct cache_expire *ce = NULL; if (!priv || !priv->conc_dbp) { dbp = conc_dbp; db_envp = conc_env; lconc_stop = &conc_stop; ltable = conc_table; } else { dbp = priv->conc_dbp; db_envp = priv->conc_env; lconc_stop = &priv->conc_stop; ltable = priv->conc_table; } if (!dbp || !db_envp) return priv; sleep_secs = conc_conf[CONF_SINIT]; if (sleep_secs < 1) sleep_secs = 10; work_secs = conc_conf[CONF_WINIT]; while(!(*lconc_stop) && calm_select_sleep(priv, sleep_secs, 0)) { if (conc_walk_expire(priv, work_secs, &ce)) { /* did stuff */ if (sleep_secs/2 < conc_conf[CONF_SLEEP_MIN]) sleep_secs = conc_conf[CONF_SLEEP_MIN]; else sleep_secs = sleep_secs/2; if (work_secs *2 > conc_conf[CONF_WORK_MAX]) work_secs = conc_conf[CONF_WORK_MAX]; else work_secs *= 2; } else { /* nothing to do */ if ((sleep_secs * 2) > conc_conf[CONF_SLEEP_MAX]) sleep_secs = conc_conf[CONF_SLEEP_MAX]; else sleep_secs *= 2; if ((work_secs/2) < conc_conf[CONF_WORK_MIN]) work_secs = conc_conf[CONF_WORK_MIN]; else work_secs = work_secs/2; } } calm_cache_expire_free(priv, ce); calm_conc_closeall(priv, ltable, conc_conf[CONF_TABLESZ]); dbp->close(dbp, 0); db_envp->close(db_envp, 0); free(ltable); if (priv && priv->conc_dbp) { pthread_mutex_unlock(&priv->conc_mutex); /* if per thread connection caching is turned on, we needed priv after mlfi_close() */ free(priv); pthread_exit(&retval); return priv; } conc_dbp = NULL; conc_env = NULL; pthread_mutex_unlock(&conc_mutex); pthread_exit(&retval); return priv; } void * calm_cache_maint(void *p) { DB *dbp = p; DB_ENV *ep; int sleep_secs; int work_secs; int retval = 0; if (!p) { pthread_mutex_unlock(&conc_mutex); return p; } ep = p->get_env(p); sleep_secs = conc_conf[CONF_SINIT]; if (sleep_secs < 1) sleep_secs = 10; work_secs = conc_conf[CONF_WINIT]; while(!cach_stop && calm_select_sleep(NULL, sleep_secs, 0)) { if (cach_walk_expire(dbp, work_secs)) { /* did stuff */ if (sleep_secs/2 < cach_conf[CONF_SLEEP_MIN]) sleep_secs = cach_conf[CONF_SLEEP_MIN]; else sleep_secs = sleep_secs/2; if (work_secs *2 > cach_conf[CONF_WORK_MAX]) work_secs = cach_conf[CONF_WORK_MAX]; else work_secs *= 2; } else { /* nothing to do */ if ((sleep_secs * 2) > cach_conf[CONF_SLEEP_MAX]) sleep_secs = cach_conf[CONF_SLEEP_MAX]; else sleep_secs *= 2; if ((work_secs/2) < cach_conf[CONF_WORK_MIN]) work_secs = cach_conf[CONF_WORK_MIN]; else work_secs = work_secs/2; } } p->close(p, 0); ep->close(ep, 0); pthread_mutex_unlock(&conc_mutex); pthread_exit(&retval); return p; } #endif /* DBCACHE */ int calm_cache_init(SMFICTX *ctx) { #if DBCACHE pthread_attr_t pthread_attr; struct mlfiPriv *priv = NULL; if (ctx) priv = MLFIPRIV; if (!cache_disk_file && !cache_spec) return 1; /* this is supposed to stay locked until cache is done */ pthread_mutex_lock(&cach_mutex); pthread_attr_init(&pthread_attr); pthread_attr_setdetachstate(&pthread_attr,PTHREAD_CREATE_DETACHED); if (cach_env) { pthread_mutex_unlock(&cach_mutex); return 1; } if(!db_env_create(&cach_env, 0) || !cach_env) goto cach_init_clandl; if (!cach_env->open(cach_env, NULL, DB_INIT_LOCK | DB_CREATE | DB_PRIVATE | DB_THREAD , 0)) goto cach_init_clandl; if (cach_conf[CONF_CACHESZ] && !cach_env->set_cachesize(cach_env, 0, cach_conf[CONF_CACHESZ], 0)) goto cach_init_clandl; if (!db_create(&cach_dbp, cach_env, 0)) goto cach_init_clandl; if (!cach_dbp->open(cach_dbp, NULL, cache_disk_file, "calm-cache", DB_CREATE | DB_THREAD)) goto cach_init_clandl; if (!pthread_create(&cach_thread, &pthread_attr, calm_cache_maint, (void *)cach_dbp)) goto cach_init_clandl; return 1; cach_init_clandl: if (cach_dbp) cach_dbp->close(cach_dbp,0); if (cach_env) cach_env->close(cach_env,0); pthread_mutex_unlock(&cach_mutex); return 0; #endif /* DBCACHE */ return 1; } int calm_conc_init(SMFICTX *ctx) { #if DBCACHE pthread_attr_t pthread_attr; struct mlfiPriv *priv = NULL; if (ctx) priv = MLFIPRIV; pthread_attr_init(&pthread_attr); pthread_attr_setdetachstate(&pthread_attr,PTHREAD_CREATE_DETACHED); if (host_connection_caching) { if (conc_env || conc_table) return 1; /* this is supposed to stay locked until cache is done */ pthread_mutex_lock(&conc_mutex); if (conc_env || conc_table) { pthread_mutex_unlock(&conc_mutex); return 1; } if(!db_env_create(&conc_env, 0) || !conc_env) goto conc_init_clandl; if (!conc_env->open(conc_env, NULL, DB_INIT_LOCK | DB_CREATE | DB_PRIVATE | DB_THREAD , 0)) goto conc_init_clandl; if (conc_conf[CONF_CACHESZ] && !conc_env->set_cachesize(conc_env, 0, conc_conf[CONF_CACHESZ], 0)) goto conc_init_clandl; if (!db_create(&conc_dbp, conc_env, 0)) goto conc_init_clandl; if (!conc_dbp->open(conc_dbp, NULL, NULL, "calm-conc", DB_CREATE | DB_THREAD)) goto conc_init_clandl; conc_table = calm_conc_table_init(conc_conf[CONF_TABLESZ]); if (!conc_table) goto conc_init_clandl; else we_conc_tabled = true; if (!pthread_create(&conc_thread, &pthread_attr, calm_conc_maint, (void *)priv)) goto conc_init_clandl; return 1; conc_init_clandl: if (conc_dbp) conc_dbp->close(conc_dbp,0); if (conc_env) conc_env->close(conc_env,0); if (conc_table) { free(conc_table); conc_table = NULL; } pthread_mutex_unlock(&conc_mutex); return 0; } else if (pt_host_connection_caching && priv) { if (priv->conc_env || priv->conc_table) return 1; /* this is supposed to stay locked until cache is done */ pthread_mutex_lock(&priv->conc_mutex); if (priv->conc_env || priv->conc_table) { pthread_mutex_unlock(&priv->conc_mutex); return 1; } if(!db_env_create(&priv->conc_env, 0) || !priv->conc_env) goto conc_pt_init_clandl; if (!priv->conc_env->open(priv->conc_env, NULL, DB_INIT_LOCK | DB_CREATE | DB_PRIVATE | DB_THREAD , 0)) goto conc_pt_init_clandl; if (conc_conf[CONF_CACHESZ] && !priv->conc_env->set_cachesize(priv->conc_env, 0, conc_conf[CONF_CACHESZ], 0)) goto conc_pt_init_clandl; if (!db_create(&priv->conc_dbp, priv->conc_env, 0)) goto conc_pt_init_clandl; if (!priv->conc_dbp->open(priv->conc_dbp, NULL, NULL, NULL, DB_CREATE | DB_THREAD)) goto conc_pt_init_clandl; priv->conc_table = calm_conc_table_init(conc_conf[CONF_TABLESZ]); if (!priv->conc_table) goto conc_pt_init_clandl; if (!pthread_create(&priv->conc_thread, &pthread_attr, calm_conc_maint, (void *)priv)) goto conc_init_clandl; return 1; conc_pt_init_clandl: if (priv->conc_dbp) priv->conc_dbp->close(priv->conc_dbp,0); if (priv->conc_env) priv->conc_env->close(conc_env,0); if (priv->conc_table) { free(priv->conc_table); priv->conc_table = NULL; } pthread_mutex_unlock(&priv->conc_mutex); return 0; } #endif /* DBCACHE */ return 1; } int calm_connect(struct mlfiPriv *priv, char **dsthost) { int retval, i, j = 0; struct calm_hostent *calm_host = NULL; struct hostent *host = NULL; int consock; char addrbuf[256]; time_t oconnect_start = time(NULL); struct servent *servport; uint16_t service_port = htons(25); VERBOSE_FUNCLINE(5, priv); if (!dsthost) return -1; servport = getservbyname("smtp","tcp"); if (servport) service_port = servport->s_port; for(consock = -1 ; dsthost[0] && *dsthost[0] && consock == -1; dsthost++) { long sock_flags; bool lretry_without_dot = retry_without_dot; connect_retry_dotless: VERBOSE_FUNCLINE(5, priv); calm_host = calm_gethostbyname(priv, *dsthost); if (calm_timedout(priv, oconnect_start, CALM_TO_OCONNECT)) { calm_freehostent(priv, calm_host); break; } if (!calm_host && !lretry_without_dot) continue; else if (!calm_host && lretry_without_dot) { int hlen = strlen(dsthost[0]); if (dsthost[0][hlen-1] == '.') { dsthost[0][hlen - 1] = '\0'; lretry_without_dot = false; goto connect_retry_dotless; } else continue; } if (prefer_inet6) j = calm_host->num - 1; connect_next_hostent: host = calm_host->host[j]; VERBOSE_FUNCLINE_MSG(5, priv, "priv %p host %p calm_host %p dsthost == \"%s\"\n", priv, host, calm_host, *dsthost); if (host->h_addrtype == AF_INET) for (i = 0; consock == -1 && host->h_addr_list[i]; i++) { struct sockaddr_in sock; time_t connect_start = time(NULL); sock.sin_family = AF_INET; sock.sin_addr = *(struct in_addr *) host->h_addr_list[i]; sock.sin_port = service_port; VERBOSE_FUNCLINE(5, priv); consock = socket(PF_INET, SOCK_STREAM, 0); if (consock < 0) break; if ((sock_flags = fcntl(consock, F_GETFL)) < 0) break; if (fcntl(consock, F_SETFL, sock_flags | O_NONBLOCK) < 0) break; VERBOSE_FUNCLINE(5, priv); VERBOSE(1, priv, "connecting to %s port %d for host %s\n", inet_ntop(AF_INET, &sock.sin_addr, addrbuf, sizeof(addrbuf)), ntohs(service_port), dsthost[0]); retval = connect(consock, (struct sockaddr *)&sock, sizeof(sock)); if (!retval && !calm_timedout(priv, connect_start, CALM_TO_CONNECT)) break; if (retval < 0 && errno == EINPROGRESS) { VERBOSE_FUNCLINE(5, priv); retval = calm_select_connect(priv, consock, Calm_Timeouts[CALM_TO_CONNECT], 0); if (retval > 0) break; } VERBOSE_FUNCLINE(5, priv); shutdown(consock, SHUT_RDWR); close(consock); consock = -1; if (calm_timedout(priv, oconnect_start, CALM_TO_OCONNECT)) break; } #if NETINET6 else if (host->h_addrtype == AF_INET6) for (i = 0; consock == -1 && host->h_addr_list[i]; i++) { struct sockaddr_in6 sock; time_t connect_start = time(NULL); sock.sin6_family = AF_INET6; sock.sin6_addr = *(struct in6_addr *)host->h_addr_list[i]; sock.sin6_port = service_port; VERBOSE_FUNCLINE(5, priv); VERBOSE(1, priv, "connecting to %s port %d for host %s\n", inet_ntop(AF_INET6, &sock.sin6_addr, addrbuf, sizeof(addrbuf)), ntohs(service_port), dsthost[0]); consock = socket(PF_INET, SOCK_STREAM, 0); if (consock < 0) break; if (fcntl(consock, F_SETFD, O_NONBLOCK) < 0) break; VERBOSE_FUNCLINE(5, priv); retval = connect(consock, (struct sockaddr *)&sock, sizeof(sock)); if (!retval && !calm_timedout(priv, connect_start, CALM_TO_CONNECT)) break; if (retval < 0 && errno == EINPROGRESS) { VERBOSE_FUNCLINE(5, priv); retval = calm_select_connect(priv, consock, Calm_Timeouts[CALM_TO_CONNECT], 0); if (retval > 0) break; } VERBOSE_FUNCLINE(5, priv); shutdown(consock, SHUT_RDWR); close(consock); consock = -1; if (calm_timedout(priv, oconnect_start, CALM_TO_OCONNECT)) break; } #endif if (consock == -1) { VERBOSE_FUNCLINE_MSG(5, priv, "host %p prefer_inet6 %d j %d calm_host->num %d\n", host, prefer_inet6, j, (calm_host) ? calm_host->num : 0); if (prefer_inet6) { if (!--j) goto connect_next_hostent; } else if (++j < calm_host->num) goto connect_next_hostent; } VERBOSE_FUNCLINE_MSG(5, priv, "calm_host %p\n", calm_host); calm_freehostent(priv, calm_host); } VERBOSE_FUNCLINE(5, priv); return consock; } int calm_smtprcpt(struct mlfiPriv *priv, char ** dsthost, char * envfrom, char * envrcpt, char **p, ssize_t *plen) { int consock = -1; int rset = 0; int retval = -1; VERBOSE_FUNCLINE_MSG(4, priv, "dsthost %p %s envfrom %s envrcpt %s p %p plen %p\n", dsthost, (dsthost) ? dsthost[0] : NULL, envfrom, envrcpt, p, plen); if (!dsthost || !*dsthost) return -1; if (priv && priv->consock_con) { VERBOSE_FUNCLINE(5, priv); consock = priv->consock; if(calm_select_connect(priv, consock, 0, 10000) < 0) { VERBOSE_FUNCLINE(5, priv); VERBOSE_FUNCLINE(5, priv); priv->consock_con = 0; priv->consock = consock = -1; } else rset = 1; VERBOSE_FUNCLINE(5, priv); } if (consock < 0) { bad_consock: VERBOSE_FUNCLINE(5, priv); if (!*dsthost) return retval; consock = calm_connect(priv, dsthost); VERBOSE_FUNCLINE(5, priv); rset = 0; if (priv) { VERBOSE_FUNCLINE(5, priv); priv->consock_con = (consock > -1); priv->consock = consock; } } if (consock < 0) return retval; VERBOSE_FUNCLINE(5, priv); retval = verify_smtprcpt(priv, consock, &rset, envfrom, envrcpt, p, plen); if (retval == -2) { VERBOSE_FUNCLINE(5, priv); shutdown(consock, SHUT_RDWR); close(consock); if (priv) { priv->consock = -1; priv->consock_con = 0; } } else if (retval == -3) { VERBOSE_FUNCLINE(5, priv); shutdown(consock, SHUT_RDWR); close(consock); goto bad_consock; } else if (retval == -4) { VERBOSE_FUNCLINE(5, priv); shutdown(consock, SHUT_RDWR); close(consock); dsthost++; goto bad_consock; } VERBOSE_FUNCLINE(5, priv); if (!priv) { rset = -1; VERBOSE_FUNCLINE(5, priv); verify_smtprcpt(priv, consock, &rset, NULL, NULL, p, plen); shutdown(consock, SHUT_RDWR); close(consock); } VERBOSE_FUNCLINE(5, priv); return retval; } /* lifted from sendmail/err.c * props and copyrights to sendmail/org and consortium */ int extenhsc(s, delim, e) const char *s; int delim; char *e; { int l, h; if (s == NULL) return 0; if (!((*s == '2' || *s == '4' || *s == '5') && s[1] == '.')) return 0; h = 0; l = 2; e[0] = s[0]; e[1] = '.'; while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h])) { e[l + h] = s[l + h]; ++h; } if (h == 0 || s[l + h] != '.') return 0; e[l + h] = '.'; l += h + 1; h = 0; while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h])) { e[l + h] = s[l + h]; ++h; } if (h == 0 || s[l + h] != delim) return 0; e[l + h] = '\0'; return l + h; } /* * mode == 0 envfrom * mode == 1 envrcpt * */ sfsistat mlfi_frrc(ctx, envrcpt, mode) SMFICTX *ctx; char **envrcpt; int mode; { sfsistat retval = smfis_tempfail; #if _FFR_MILTER_REWRITE || _FFR_MILTER_SM_MAP char *ruleset_321 = "3,2,0"; char *ruleset_local = "localaddr"; char *rset = ruleset_321; char *pbuf[2]; time_t lookup_start; #endif /* _FFR_MILTER_REWRITE || _FFR_MILTER_SM_MAP */ char **pp; char *p = NULL; char *p1 = NULL; int plen = 0; int p1len = 0; char *dsthosts[2]; char **dsthost = dsthosts; int rwstat = 0; int i = 0; int local_recipient = 0; char * macro_rcpt_mailer = NULL; struct mlfiPriv *priv = MLFIPRIV; bool ldo_mx_lookups = do_mx_lookups; VERBOSE_FUNCLINE(5, priv); VERBOSE(1, priv, "mlfi_frrc(): %s %s\n", mode ? "envrcpt" : "envfrom" , envrcpt[0]); if (!priv) return retval; priv->olookup_start = time(NULL); if (envrcpt && envrcpt[0][0] == '<' && envrcpt[0][1] == '>') return SMFIS_CONTINUE; dsthost[1] = NULL; dsthost[0] = smfi_getsymval(ctx, mode ? "{rcpt_host}" : "{mail_host}"); VERBOSE(3, priv, "dsthost[0] %s lfallbackdsthost %s fallbackhost %s\n", dsthost[0], lfallbackdsthost, fallbackdsthost); if (lfallbackdsthost) { VERBOSE_FUNCLINE(5, priv); macro_rcpt_mailer = smfi_getsymval(ctx, mode ? "{rcpt_mailer}" : "{mail_mailer}"); if (macro_rcpt_mailer && strcasecmp(macro_rcpt_mailer, "local") == 0) local_recipient = 1; else VERBOSE_FUNCLINE(5, priv); VERBOSE(3, priv, "macro_rcpt_mailer %s local_recipient %d\n", macro_rcpt_mailer, local_recipient); } #if _FFR_MILTER_REWRITE if (local_recipient) rset = ruleset_local; pbuf[0] = envrcpt[0]; pbuf[1] = NULL; pp = pbuf; restart_rewrite: lookup_start = time(NULL); if((((!*dsthost || !*dsthost[0]) && rewrite_maybe) || rewrite_anyways) && (smfi_rewrite(ctx, rset, &pp, &rwstat, SMFRW_MULTIPLE | SMFRW_UNFILTER)) == MI_SUCCESS) { VERBOSE_FUNCLINE(5, priv); do { if (rwstat < EX_OK) break; VERBOSE_FUNCLINE(5, priv); if (!pp || !pp[0]) break; VERBOSE_FUNCLINE_MSG(5, priv, "pp[0] %s\n", pp[0]); if ((pp[0][0] & 0377) != CANONNET) break; VERBOSE_FUNCLINE(5, priv); while(pp[0][++i]) if ((pp[0][i] & 0377) == CANONHOST) break; VERBOSE_FUNCLINE_MSG(5, priv, "pp[0][i] %c\n", pp[0][i]); pp[0][i++] = '\0'; if (!local_recipient && strcmp("local", pp[0] + 1) == 0) { VERBOSE_FUNCLINE(5, priv); local_recipient = 1; for (i = 0; pp && pp[i]; i++) { VERBOSE_FUNCLINE_MSG(5, priv, "free pp[%d] %p\n", i, pp[i]); free(pp[i]); } VERBOSE_FUNCLINE_MSG(5, priv,"free pp %p\n", pp); free(pp); if (calm_timedout(priv, lookup_start, CALM_TO_SM_LOOKUP)) goto frrc_cleanup_and_leave; rset = ruleset_local; pp = pbuf; VERBOSE_FUNCLINE(5, priv); goto restart_rewrite; } VERBOSE_FUNCLINE_MSG(5, priv, "free pp[0][i] %c\n", pp[0][i]); p = pp[0] + i; if (!*p) break; VERBOSE_FUNCLINE(5, priv); for (i = 0; p[i]; i++) if ((p[i] & 0377) == CANONUSER) break; VERBOSE_FUNCLINE(5, priv); if (!p[i]) break; VERBOSE_FUNCLINE(5, priv); p[i] = '\0'; dsthost[0] = strdup(p); VERBOSE_FUNCLINE_MSG(5, priv, "alloc dsthost strdup %p\n", dsthost[0]); for (i = 0; pp && pp[i]; i++) { VERBOSE_FUNCLINE_MSG(5, priv, "free pp[%d] %p\n", i, pp[i]); free(pp[i]); } pp[0] = dsthost[0]; dsthost = pp; pp = NULL; break; } while (0); VERBOSE_FUNCLINE(5, priv); VERBOSE(3, priv, "dsthost %p %s\n", dsthost, (dsthost) ? dsthost[0] : ""); if (pp && pp[0]) { VERBOSE_FUNCLINE(5, priv); for (i = 0; pp[i]; i++) { VERBOSE_FUNCLINE_MSG(5, priv, "free pp[%d] %p\n", i, pp[i]); free(pp[i]); } free(pp); VERBOSE_FUNCLINE_MSG(5, priv, "free pp %p\n", pp); } pp = NULL; if (calm_timedout(priv, lookup_start, CALM_TO_SM_LOOKUP)) goto frrc_cleanup_and_leave; pp = NULL; p = NULL; } VERBOSE_FUNCLINE(5, priv); #endif /* _FFR_MILTER_REWRITE */ if (!dsthost[0] || !*dsthost[0]) { VERBOSE_FUNCLINE(5, priv); if (local_recipient && lfallbackdsthost) { VERBOSE_FUNCLINE(5, priv); dsthost[0] = lfallbackdsthost; } else dsthost[0] = fallbackdsthost; } else if (only_mx_supressed) { if(dsthost[0][0] != '[') { VERBOSE_FUNCLINE(5, priv); retval = SMFIS_CONTINUE; goto frrc_cleanup_and_leave; } else ldo_mx_lookups = 1; /* basically strip [] */ } # if _FFR_MILTER_SM_MAP VERBOSE_FUNCLINE_MSG(5, priv, "pp %p, bestmx_sm_map %s, rwstat %d\n", pp, bestmx_sm_map, rwstat); if((dsthost[0]) && *dsthost[0] && bestmx_sm_map_lookup) { lookup_start = time(NULL); VERBOSE_FUNCLINE(5, priv); pp = dsthost; /* SMFRW_MULTIPLE to workaround bug in milter-rrres v14 */ /* SMFRW_CONDELSE | SMFRW_MAPDELIM has a new feature to it in milter-rrres v16 */ if (smfi_sm_map(ctx, bestmx_sm_map, &pp, &rwstat, SMFRW_MULTIPLE | SMFRW_MAPDELIM | SMFRW_CONDELSE) == MI_SUCCESS) { VERBOSE_FUNCLINE(5, priv); if(pp && pp[0]) { if (dsthost != dsthosts) { for (i = 0; dsthost[i]; i++) { VERBOSE_FUNCLINE_MSG(5, priv, "free dsthost[%d] %p\n", i, dsthost[i]); free(dsthost[i]); } free(dsthost); VERBOSE_FUNCLINE_MSG(5, priv, "free dsthost %p\n", dsthost); } dsthost = pp; } VERBOSE_FUNCLINE(5, priv); if (calm_timedout(priv, lookup_start, CALM_TO_SM_LOOKUP)) goto frrc_cleanup_and_leave; ldo_mx_lookups = 0; } } # endif /* _FFR_MILTER_SM_MAP */ if (dsthost[0] && *dsthost[0] && ldo_mx_lookups) { VERBOSE_FUNCLINE(5, priv); if (verbosity > 2) for (i = 0; dsthost[i]; i++) VERBOSE(3, priv, "dsthost[%d] %s\n", i, dsthost[i]); pp = NULL; if (calm_getmxrr(priv, dsthost[0], &pp, NULL, &p1, &p1len, 0) > 0) { VERBOSE_FUNCLINE(5, priv); if (dsthost != dsthosts) { for (i = 0; dsthost[i]; i++) { VERBOSE_FUNCLINE_MSG(5, priv, "free dsthost[%d] %p\n", i, dsthost[i]); free(dsthost[i]); } free(dsthost); VERBOSE_FUNCLINE_MSG(5, priv, "free dsthost %p\n", dsthost); } dsthost = pp; } else { VERBOSE_FUNCLINE_MSG(5, priv, "free p1 %p\n", p1); free(p1); p1 = NULL; } } VERBOSE_FUNCLINE(5, priv); if (dsthost[0] && *dsthost[0]) { VERBOSE_FUNCLINE_MSG(5, priv, "only_mx_supressed %d\n", only_mx_supressed); if (verbosity > 2) for (i = 0; dsthost[i]; i++) VERBOSE(3, priv, "dsthost[%d] %s\n", i, dsthost[i]); if (verify_sender && mode) { /*rcpt verification using the envelope mail from:<> try to catch spam send to legit user from dsthost but from illegit user on dsthost many times this will still result in double-bounce, which we want to prevent */ VERBOSE_FUNCLINE(5, priv); rwstat = calm_smtprcpt(priv, dsthost, NULL, (prevent_loop) ? NULL : envrcpt[0] , &p, &plen); if (rwstat > 0 && prevent_loop) { VERBOSE_FUNCLINE(5, priv); rwstat = calm_smtprcpt(priv, dsthost, "", envrcpt[0] , &p, &plen); } } else { VERBOSE_FUNCLINE(5, priv); rwstat = calm_smtprcpt(priv, dsthost, NULL, envrcpt[0] , &p, &plen); } VERBOSE_FUNCLINE(5, priv); calm_smtpshutdown(priv, -1); VERBOSE(3, priv, "rwstat %d plen %d p %s\n", rwstat, plen, (plen) ? p : ""); } else { VERBOSE(3, priv, "no host to connect to, not validating.\n"); retval = SMFIS_CONTINUE; } if (rwstat > 0 || never_stop_anything) retval = SMFIS_CONTINUE; VERBOSE_FUNCLINE(5, priv); if (!never_stop_anything && rwstat == 0 && p) { char smtp_xcode[15]; char smtp_rcode[4]; int xcode = 0; int fudge = 0; int envrcpt_len = 0; #if !_FFR_MULTILINE_ERRORS char *end_p = NULL; if (p) end_p = strpbrk(p,"\r\n"); if (end_p) end_p[0] = '\0'; #else if (p[plen-2] == '\r') { p[plen-2] = '\0'; plen -= 2; } else if (p[plen-1] == '\n') { p[plen-1] = '\0'; plen -= 1; } #endif /* !_FFR_MULTILINE_ERRORS */ if (plen > 3) xcode = extenhsc(p+4,' ',smtp_xcode); if (plen > 2) strncpy(smtp_rcode, p, 3); else strcpy(smtp_rcode, "550"); smtp_rcode[3] = '\0'; VERBOSE_FUNCLINE(5, priv); VERBOSE(1, priv,"Code: %s %s: Line from remote: \"%s\"\n", smtp_rcode, (xcode) ? smtp_xcode : "", (plen) ? p : ""); if (cleanup_reply && plen > 4+xcode+3+(xcode != 0)) { VERBOSE_FUNCLINE(5, priv); envrcpt_len = strlen(envrcpt[0]); if (envrcpt[0][0] != '<' && *(p+xcode+4+(xcode != 0)) == '<') fudge++; if (strncasecmp(envrcpt[0], p+xcode+4+(xcode != 0)+fudge, envrcpt_len) == 0) { VERBOSE_FUNCLINE(5, priv); fudge += envrcpt_len; while(4+xcode+(xcode != 0) + fudge < plen && *(p+4+xcode+(xcode != 0)+fudge) == '.') fudge++; if (*(p+4+xcode+(xcode != 0)+fudge) == ' ') fudge++; } VERBOSE_FUNCLINE_MSG(4, priv, "4+xcode+3+(xcode != 0) = %d; " "*(p+xcode+4+(xcode != 0)) == %c; " "xcode+4+(xcode != 0)+fudge == %d; " "envrcpt[0] == %s; " "fudge == %d\n", 4+xcode+3+(xcode != 0), *(p+xcode+4+(xcode != 0)), xcode+4+(xcode != 0)+fudge, envrcpt[0], fudge); } if (smfi_setreply(ctx, smtp_rcode, xcode ? smtp_xcode : NULL, (plen > 5) ? p+xcode+4+(xcode != 0)+fudge : "Unknown error") != MI_FAILURE) { VERBOSE(4, priv, "smfi_setreply() succeeded: smtp_rcode %s smtp_xcode %s string %s\n", smtp_rcode, xcode ? smtp_xcode : "", (plen > 5) ? p+xcode+4+(xcode != 0)+fudge : "Unknown error"); VERBOSE_FUNCLINE(5, priv); if (smtp_rcode[0] == '5') retval = SMFIS_REJECT; if (smtp_rcode[0] == '4') retval = smfis_remote_tempfail; } else VERBOSE(1, priv, "smfi_setreply() failed: smtp_rcode %s smtp_xcode %s string %s\n", smtp_rcode, xcode ? smtp_xcode : "", (plen > 5) ? p+xcode+4+(xcode != 0)+fudge : "Unknown error"); } else VERBOSE(2, priv, "%s will not be rejected\n", envrcpt[0]); frrc_cleanup_and_leave: VERBOSE_FUNCLINE(5, priv); if (p1) { VERBOSE_FUNCLINE_MSG(5, priv, "free p1 %p\n", p1); free(p1); VERBOSE_FUNCLINE_MSG(5, priv, "free dsthost %p\n", dsthost); free(dsthost); } else if (dsthost != dsthosts) { VERBOSE_FUNCLINE(5, priv); for (i = 0; dsthost && dsthost[i]; i++) { VERBOSE_FUNCLINE_MSG(5, priv, "free sthost[%d] %p\n", i, dsthost[i]); free(dsthost[i]); } VERBOSE_FUNCLINE_MSG(5, priv, "free dsthost %p\n", dsthost); free(dsthost); } VERBOSE_FUNCLINE(5, priv); if(p) { VERBOSE_FUNCLINE_MSG(5, priv,"free p %p\n", p); free(p); } VERBOSE_FUNCLINE(5, priv); return retval; } sfsistat mlfi_envfrom(ctx, envfrom) SMFICTX *ctx; char **envfrom; { struct mlfiPriv *priv = MLFIPRIV; VERBOSE_FUNCLINE(5, priv); VERBOSE(2, priv, "envfrom %s\n", envfrom && envfrom[0] ? envfrom[0] : "NULL"); /* allocate some private memory */ priv = malloc(sizeof *priv); VERBOSE_FUNCLINE_MSG(5, NULL,"alloc priv %p\n", priv); if (!priv) /* can't accept this message right now */ return smfis_tempfail; memset(priv, '\0', sizeof *priv); /* save the private data */ smfi_setpriv(ctx, priv); priv->milter_start = time(NULL); priv->ctx = ctx; priv->thread_id = pthread_self(); if (pt_host_connection_caching) pthread_mutex_init(&priv->conc_mutex, NULL); if (verify_sender || callback_sender) { priv->sender_address = strdup(envfrom[0]); VERBOSE_FUNCLINE_MSG(5, priv, "alloc priv->sender_address %p\n", priv->sender_address); if (!priv->sender_address) return smfis_tempfail; priv->sender_address_len = strlen(envfrom[0]); } priv->queue_id = smfi_getsymval(ctx, "i"); if (!priv->queue_id) priv->queue_id = noqueue_id; priv->macro_j = smfi_getsymval(ctx, "j"); if (priv->macro_j) priv->macro_j_len = strlen(priv->macro_j); if (MyHname) { priv->myhnamep = MyHname; priv->myhname_len = MyHname_len; } else if (use_j_macro) { priv->myhnamep = priv->macro_j; priv->myhname_len = priv->macro_j_len; } if (!priv->myhnamep) { if (gethostname(priv->myhname, sizeof(priv->myhname))) { priv->myhnamep = "localhost.localdomain"; priv->myhname_len = strlen(priv->myhnamep); } else if(!(priv->myhname_len = calm_getfqdn(priv, priv->myhname, sizeof(priv->myhname)))) { priv->myhname_len = strlen(priv->myhname); priv->myhnamep = priv->myhname; } } VERBOSE_FUNCLINE(5, priv); if (callback_sender) { /* verification is performed with mail from: rcpt to: */ return(mlfi_frrc(ctx, envfrom, 0)); } /* continue processing */ VERBOSE_FUNCLINE(5, priv); return SMFIS_CONTINUE; } sfsistat mlfi_envrcpt(ctx, envrcpt) SMFICTX *ctx; char **envrcpt; { struct mlfiPriv *priv = MLFIPRIV; VERBOSE_FUNCLINE(5, priv); if (!priv) return smfis_tempfail; priv->milter_start = time(NULL); return(mlfi_frrc(ctx, envrcpt,1)); } sfsistat mlfi_cleanup(ctx, ok) SMFICTX *ctx; bool ok; { sfsistat rstat = SMFIS_CONTINUE; struct mlfiPriv *priv = MLFIPRIV; VERBOSE_FUNCLINE(5, priv); if (priv == NULL) return rstat; if (priv->sender_address) { VERBOSE_FUNCLINE_MSG(5, priv, "free priv->sender_address %p\n", priv->sender_address); free(priv->sender_address); } if (pt_host_connection_caching && priv->conc_table) { time_t cleanup_time = time(NULL); VERBOSE_FUNCLINE_MSG(5, priv, "priv->conc_stop = true\n"); priv->conc_stop = true; while (pthread_mutex_trylock(&priv->conc_mutex) == EBUSY) { if(calm_timedout(priv, cleanup_time, CALM_TO_CLEANUP)) break; if(conc_conf[CONF_CLEANUP]) calm_select_sleep(priv, conc_conf[CONF_CLEANUP], 0); } pthread_mutex_unlock(&priv->conc_mutex); pthread_mutex_destroy(&priv->conc_mutex); } else { calm_smtpshutdown(priv, -1); VERBOSE_FUNCLINE_MSG(5, priv, "free priv %p\n", priv); free(priv); priv = NULL; } smfi_setpriv(ctx, priv); VERBOSE_FUNCLINE(5, priv); /* return status */ return rstat; } sfsistat mlfi_eom(ctx) SMFICTX *ctx; { struct mlfiPriv *priv = MLFIPRIV; if (priv == NULL) return SMFIS_CONTINUE; return mlfi_cleanup(ctx, true); } sfsistat mlfi_close(ctx) SMFICTX *ctx; { struct mlfiPriv *priv = MLFIPRIV; if (priv == NULL) return SMFIS_ACCEPT; return mlfi_cleanup(ctx, true); } sfsistat mlfi_abort(ctx) SMFICTX *ctx; { struct mlfiPriv *priv = MLFIPRIV; if (priv == NULL) return SMFIS_CONTINUE; return mlfi_cleanup(ctx, false); } struct smfiDesc smfilter = { PROGNAME, /* filter name */ SMFI_VERSION, /* version code -- do not change */ 0, /* flags */ NULL, /* connection info filter */ NULL, /* SMTP HELO command filter */ mlfi_envfrom, /* envelope sender filter */ mlfi_envrcpt, /* envelope recipient filter */ NULL, /* header filter */ NULL, /* end of header */ NULL, /* body block filter */ NULL, /* end of message */ mlfi_abort, /* message aborted */ mlfi_close /* connection cleanup */ }; void usage(int exitval, char **argv) { fprintf(stderr, "Usage: %s [-vh?] [-6DaAEmxXTRjsSlNuz] [-M bestmxmap] [-b fallbackdsthost] [-B localfallback] [-d debuglvl] [-p sockpath] [-f envfrom] [-r envrcpt [-t testhost] [-H myhostname] [-C cachefile] [-c cachespec] [-o timeoutspec] [-I cachespec] [-i cachespec] [-g] [-G label] [-n hostnamemap] [-w timespec]\n" , argv[0]); fprintf(stderr, "Options :\n" " -6 : prefer inet6 addresses over inet4, if supported.\n" " -a : perform sendmail rewriting to obtain dsthost even if macro {rcpt_host} is set.\n" " -A : perform sendmail rewriting to obtain dsthost only if macro {rcpt_host} isnt set.\n" " -b fallbacksthost : perform verification against this host if it is unknown where else to.\n" " : This generally means that the recipient is considered local to the MTA.\n" " -B localfallback : Use this for dsthost ONLY if the recipient is considered local to the MTA.\n" " -c cachespec : Caching specification. Turns on address validation cache.\n" " -C cachefile : Use persistent on disk database for -c caching.\n" " -d debuglvl : sets libmilter debug levels.\n" " -D : retry connecting to mxhost without the sendmail style final dot.\n" " -E : Only use dsthost entries that look like sendmail mx supressed names.\n" " : Typically, using this suggests only verifying against mailertable entries.\n" " -f envfrom address : use this envelope from address while testing verification\n" " : during normal operation, use this envfrom instead of <>\n" " -g : log to syslog.\n" " -G label : log to syslog with this label.\n" " -H myhostname : use this hostname while connecting to smtp servers.\n" " -i cachespec : number/time/rcpts of smtp connection to cache, per thread caching.\n" " OR : -I will take precedence over -i.\n" " -I cachespec : number/time/rcpts of smtp connection to cache, cross thread caching.\n" " -j : use the j macro from sendmail for hostname (-H takes priority)\n" " -l : avoid callback<->callahead loops while verifying sender address.\n" " -L logfile : while being verbose, write output to this file instead of stderr.\n" " -m : perform sendmail map lookup to find mx destinations for dsthost.\n" " -M bestmxmap : perform sendmail map lookups using this map name.\n" " : Either -M|-m or -x is required for proper operation without -E or with -S.\n" " -n hostnamemap : perform sendmail map lookups for hostname in this map.\n" " -N : Never cause any email to be stopped -- this is for testing purposes.\n" " -o timeoutspec : timeouts. Specified as \"D:timecal;X:timeval\". Following are the timeout codes.\n" "\n" " Dns address lookup, dns mX lookup, sendmail Lookup, sendmail overall looKup, Connect,\n" " Overall connect, Send, eNd send, Read, End read, return froM milter callback,\n" " mIlter read/write, milter cleanUp time\n" " Default timeouts:\n" " %s\n" "\n" " -p sockpath : sets libmilter socket path. This is required.\n" " -r envrcpt address : use this envelope rcpt address while testing verification.\n" " -R : do not tempfail email for temporary failures that are from the smtp server.\n" " -s : verify the sender address on the rcpt server as well.\n" " -S : verify the sender address (callback).\n" " -t dsthost : use this host while testing verification with -f -r values.\n" " -T : do not tempfail email for temporary failurs that are not from the smtp server.\n" " -u : attempt to cleanup the remote reply.\n" " -v : Print program version.\n" " -V : Be verbose. More vebosity for each occurence of -V (compiled with -DDEBUG)\n" " -w respawntime : Specify minimum amount of time milter needs to run before it will be respawned.\n" " : Respawning will typically be attempted if libmilter stop due to lack of resources.\n" " : It is recommended to set this to at least an hour if possible.\n" " : You can workaround suspected resource leaks by using ulimit and respawning.\n" " -x : Do MX record dns lookups for dsthost (contrast with -m|-M)\n" " : Either this option or -m|-M is required for proper operation without -E or with -S.\n" " -X : Detect IP addresses as MX names, in violation of RFC's\n" " -z : Daemonize\n" " -h|-? : This message\n\n" , timeouts_spec); exit (exitval); } void print_version(char ** argv) { fprintf(stderr,"%s : callahead-milter version %s\n\n", argv[0], VERSION); usage(0,argv); } int main(argc, argv) int argc; char *argv[]; { bool setconn = false; int c; const char *args = "6n:DgG:ui:I:o:Ec:C:aAmM:L:VNTRjH:Sslvb:B:d:p:f:r:t:w:xz"; bool testmode = false; int retval = MI_SUCCESS; time_t respawn_interval = 0; time_t launch_time = time(NULL); char *sockpath = NULL; time_t cleanup_time; #if DEBUG mtrace(); #endif /* DEBUG */ /* prepare timeouts*/ if (!calm_parse_timeouts(NULL, timeouts_spec, Calm_Timeouts)) { fprintf(stderr,"couldnt parse initial timeouts: %s\n", strerror(errno)); usage(EX_USAGE, argv); } /* Process command line options */ while ((c = getopt(argc, argv, args)) != -1) { switch (c) { case '6': prefer_inet6 = true; break; case 'u': cleanup_reply = true; break; case 'c': cache_spec = optarg; break; case 'C': cache_disk_file = optarg; break; case 'E': only_mx_supressed = true; break; case 'd': smfi_setdbg(atoi(optarg)); break; case 'D': retry_without_dot = true; break; case 'p': if (optarg == NULL || *optarg == '\0') { (void) fprintf(stderr, "Illegal conn: %s\n", optarg); exit(EX_USAGE); } sockpath = optarg; (void) smfi_setconn(sockpath); setconn = true; break; case 'o': timeouts_spec = optarg; break; case 'b': fallbackdsthost = optarg; break; case 'B': lfallbackdsthost = optarg; break; case 'f': calm_envfrom = optarg; calm_envfrom_len = strlen(calm_envfrom); break; case 'G': syslog_label = optarg; /*fall-through*/ case 'g': use_syslog = true; openlog(syslog_label, LOG_PID, LOG_MAIL); break; case 'r': calm_envrcpt = optarg; calm_envrcpt_len = strlen(calm_envrcpt); break; case 't': { char * dsthosts[2]; char ** dsthost = dsthosts; char *p = NULL; int plen = 0; char *p1 = NULL; int p1len = 0; dsthost[0] = optarg; dsthost[1] = NULL; if (do_mx_lookups && (calm_getmxrr(NULL, optarg, &dsthost, NULL, &p1, &p1len, 0) < 1)) dsthost = dsthosts; retval = calm_smtprcpt(NULL, dsthost, calm_envfrom, calm_envrcpt, &p, &plen); if (p) free(p); if (p1) free(p1); testmode = true; } break; case 'v': print_version(argv); break; case 's': verify_sender = 1; break; case 'S': callback_sender = 1; break; case 'l': prevent_loop = 1; break; case 'H': MyHname = optarg; MyHname_len = strlen(optarg); break; case 'I': host_connection_caching = optarg; break; case 'i': pt_host_connection_caching = optarg; break; case 'j': use_j_macro = 1; break; case 'T': smfis_tempfail = SMFIS_CONTINUE; break; case 'R': smfis_remote_tempfail = SMFIS_CONTINUE; break; case 'n': hostname_sm_lookup = optarg; #if _FFR_MILTER_SM_MAP /* if the below line does not compile, your libmilter is not suitably patched * * look for patch milter-rrres at >= version 15 * * See the milter-rrres patch http://www.jmaimon.com/sendmail */ smfilter.xxfi_flags |= SMFIF_SM_MAP; smfilter.xxfi_flags |= SMFIF_REWRITE; #endif /* _FFR_MILTER_SM_MAP */ break; case 'N': never_stop_anything = 1; break; case 'V': verbosity++; break; case 'L': logfile = fopen(optarg,"a+"); if(!logfile) { fprintf(stderr, "opening logfile \"%s\" errno %d error %s\n", optarg, errno, strerror(errno)); usage(1,argv); } break; case 'a': rewrite_anyways = true; break; case 'A': rewrite_maybe = true; break; #if _FFR_MILTER_SM_MAP case 'M': bestmx_sm_map = optarg; /* fall through */ case 'm': bestmx_sm_map_lookup = true; /* if the below line does not compile, your libmilter is not suitably patched * * look for patch milter-rrres at >= version 15 * * See the milter-rrres patch http://www.jmaimon.com/sendmail */ smfilter.xxfi_flags |= SMFIF_SM_MAP; break; #else /* _FFR_MILTER_SM_MAP */ case 'm': case 'M': break; #endif /* _FFR_MILTER_SM_MAP */ case 'w': respawn_interval = convtime(NULL, optarg, 's'); break; case 'x': do_mx_lookups = true; break; case 'X': getmx_detect_ipa = true; break; case 'z': do_daemonize = true; break; default: usage(0,argv); break; } } if (testmode) { if (retval == 1) return 0; if (!retval) return 1; if (retval < 0) return 2; } #if _FFR_MILTER_REWRITE /* if the below line does not compile, your libmilter is not suitably patched * * look for patch milter-rrres at >= version 7 * * See the milter-rrres patch http://www.jmaimon.com/sendmail */ if (rewrite_anyways || rewrite_maybe) smfilter.xxfi_flags |= SMFIF_REWRITE; #endif /* _FFR_MILTER_REWRITE */ if (!calm_parse_timeouts(NULL, timeouts_spec, Calm_Timeouts)) usage(EX_USAGE, argv); if (!setconn) { fprintf(stderr, "%s: Missing required -p argument\n", argv[0]); usage(EX_USAGE, argv); } if (smfi_register(smfilter) == MI_FAILURE) { fprintf(stderr, "smfi_register failed\n"); exit(EX_UNAVAILABLE); } if (Calm_Timeouts[CALM_TO_MILTER] && smfi_settimeout(Calm_Timeouts[CALM_TO_MILTER] * 2) != MI_SUCCESS) { fprintf(stderr, "smfi_settimeout(%d) failed\n", (int)Calm_Timeouts[CALM_TO_MILTER]); exit(EX_UNAVAILABLE); } if (do_daemonize && (daemon(0,0) == -1)) { perror("daemon"); exit(EX_UNAVAILABLE); } retval = smfi_main(); cleanup_time = time(NULL); conc_stop = true; cach_stop = true; if (cache_spec) { while (pthread_mutex_trylock(&cach_mutex) == EBUSY) { if(calm_timedout(NULL, cleanup_time, CALM_TO_CLEANUP)) break; if(cach_conf[CONF_CLEANUP]) calm_select_sleep(NULL, cach_conf[CONF_CLEANUP], 0); } pthread_mutex_unlock(&cach_mutex); } if (host_connection_caching && !pt_host_connection_caching) { while (pthread_mutex_trylock(&conc_mutex) == EBUSY) { if(calm_timedout(NULL, cleanup_time, CALM_TO_CLEANUP)) break; if(conc_conf[CONF_CLEANUP]) calm_select_sleep(NULL, conc_conf[CONF_CLEANUP], 0); } pthread_mutex_unlock(&conc_mutex); } if (retval != MI_SUCCESS && respawn_interval && ((time(NULL)) - launch_time) >= respawn_interval) { /* got this idea from dnsbl milter */ VERBOSE(1, NULL, "restarting after %d seconds runtime\n", ((time(NULL)-launch_time))); smfi_setconn(sockpath); if (smfi_opensocket(true) != MI_SUCCESS) { VERBOSE(1, NULL, "smfi_opensocket() could not prepare %s\n", sockpath); } else execvp(argv[0], argv); } else VERBOSE(4, NULL, "ran for %d seconds\n", cleanup_time - launch_time); return (retval == MI_SUCCESS) ? 0 : EX_UNAVAILABLE; }