/*
#ident "@(#)smail/src:RELEASE-3_2_0_121:match.c,v 1.40 2005/07/13 17:15:04 woods Exp"
*/
/*
* Copyright (C) 2004 Greg A. Woods
*
* See the file COPYING, distributed with smail, for restriction
* and warranty information.
*
* Some or all of this code may also be released under a separate copyright
* license. Contact the author for details.
*/
/*
* match.c:
* routines for matching values in various ways.
*
* external functions: match_re, match_ip, match_ip_net, flip_inet_addr,
* match_re_list, compile_pcre_list, match_pcre_list,
* free_pcre_list, format_pcre_list, trusted_invoker
*/
#include "defs.h"
#ifdef STANDALONE
# undef HAVE_LIBWHOSON
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <ctype.h>
#include <setjmp.h>
#include <signal.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>
#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
# include <stdlib.h>
# endif
#endif
#ifdef __STDC__
# include <stdarg.h>
#else
# include <varargs.h>
#endif
#ifdef HAVE_STRING_H
# if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H)
# include <memory.h>
# endif
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#if defined(HAVE_UNISTD_H)
# include <unistd.h>
#endif
#ifdef HAVE_LIBWHOSON
# include <whoson.h>
#endif
#include <pcre.h>
#include <pcreposix.h> /* PCRE's POSIX-like API */
#include "smail.h"
#include "main.h"
#include "parse.h"
#include "addr.h"
#include "route.h"
#include "alloc.h"
#include "list.h"
#include "smailsock.h"
#include "smailstring.h"
#include "dys.h"
#include "log.h"
#include "main.h"
#include "match.h"
#ifdef HAVE_BIND
# include "bindsmtpth.h"
# include "bindlib.h"
#endif
#include "exitcodes.h"
#include "smailconf.h"
#include "extern.h"
#include "debug.h"
#include "smailport.h"
/* functions local to this file */
static pcre_info_t *compile_pcre_expr __P((char *, int, char *, char *, int, char **));
static int match_pcre_expr __P((char *, size_t, pcre_info_t *, int, char **));
static char *pcre_expand_msg __P((pcre_info_t *, char *, int *, int));
/*
* match_re - match a string against a regular expression using the POSIX API
*
* NOTE: If icase is non-zero then the goal is to do case-insensitive matching
* by default.
*
* RETURNS:
*
* MATCH_MATCHED - the string matched the RE
* MATCH_NOMATCH - no match.
* MATCH_FAIL - an error occured, error message written to paniclog
*/
int
match_re(key, exp, icase, start, beg, end, last)
char *key; /* string to match */
char *exp; /* RE to match against */
int icase; /* ignore case? */
char **start; /* optional ptr to beg of match */
char **beg; /* optional ptr to beg of sub-expression */
char **end; /* optional ptr to after sub-expression */
char **last; /* optional ptr to after match */
{
regex_t preg;
char re_errmsg[BUFSIZ];
regmatch_t pmatch[2];
int rc;
DEBUG3(DBG_ADDR_HI, "match_re: matching '%v' against expression '%v'%s\n",
key, exp, icase ? ", ignoring case" : "");
/* XXX should consider converting to using the native PCRE API, perhaps
* compile_pcre_expr() and match_pcre_expr()... */
if ((rc = regcomp(&preg, exp, (REG_EXTENDED |
(icase ? REG_ICASE : 0) |
((beg || end) ? 0 : REG_NOSUB))))) {
(void) regerror(rc, &preg, re_errmsg, sizeof(re_errmsg));
write_log(WRITE_LOG_PANIC, "match_re: bad regular expression '%v': %s",
exp, re_errmsg);
regfree(&preg);
return MATCH_FAIL;
}
/* zero indicates match */
rc = regexec(&preg, key, (size_t) 2, pmatch, 0);
switch (rc) {
case 0:
rc = MATCH_MATCHED;
if (start) {
*start = key + pmatch[0].rm_so;
}
/* if (pmatch[1].rm_so == -1) assert(pmatch[1].rm_eo == -1); */
if (beg) {
*beg = key + ((pmatch[1].rm_so == -1) ? pmatch[0].rm_so : pmatch[1].rm_so);
}
if (end) {
*end = key + ((pmatch[1].rm_eo == -1) ? pmatch[0].rm_eo : pmatch[1].rm_eo);
}
if (last) {
*last = key + pmatch[0].rm_eo;
}
break;
case REG_NOMATCH:
rc = MATCH_NOMATCH;
break;
default:
(void) regerror(rc, &preg, re_errmsg, sizeof(re_errmsg));
write_log(WRITE_LOG_PANIC, "match_re: error (%d) matching '%v' with expression '%v': %s",
rc, REG_NOMATCH, key, exp, re_errmsg);
rc = MATCH_FAIL; /* error during matching */
}
regfree(&preg);
DEBUG3(DBG_ADDR_HI, "match_re: '%v' %s expression '%v'\n",
key,
(rc == MATCH_MATCHED) ? "matched" :
((rc == MATCH_NOMATCH) ? "did not match" :
"triggered REGEX error"),
exp);
return rc;
}
/*
* match_re_list - match a string against a list of REs
*
* Takes a list of RE:RE:RE and compares each against the string until a match
* is found or the list is exhausted.
*
* RE is a Regular Expression.
*
* RE may optionally be preceded by "![ \t]*" which will reverse the return
* value (i.e. immediately return 0 instead of 1) if the RE matches.
*
* RE may optionally be followed by ';message text', which of course must not
* contain a ':'
*
* WARNING: cannot diddle with "patterns" storage -- may be read-only string.
*
* RETURNS: MATCH_MATCHED if matched, else MATCH_NOMATCH (unless the matching
* RE was prefixed by '!', in which case it returns the opposite), or
* MATCH_FAIL if anything goes wrong.
*
* if a match is found and if a sub-field accompanies the matching item then
* the *reasonp will be set to point to a copy of its value too.
*
*/
int
match_re_list(str, patterns, icase, reasonp)
char *str; /* string we are matching */
char *patterns; /* list of patterns we are using to match it */
int icase; /* ignore case? */
char **reasonp; /* optional pointer for returned message string */
{
size_t str_len = strlen(str);
char *ent_p;
char *end_ent;
char *no_reason;
if (!patterns) {
/* XXX too bad we don't know the name of the patterns parameter here... */
DEBUG1(DBG_ADDR_HI, "match_re_list(): null patterns pointer, nothing to do for %v...\n", str);
return MATCH_NOMATCH;
}
/* XXX should the second parameter be limited to, say, 40 chars? */
DEBUG3(DBG_ADDR_HI, "match_re_list(%v, %v, %s): called...\n", str, patterns, icase ? "icase" : "0");
if (!reasonp) {
reasonp = &no_reason;
}
*reasonp = NULL;
/* run through the pattern list */
for (ent_p = patterns; /* NOTEST */ ; ent_p = end_ent + 1) {
int matchresult = MATCH_MATCHED;
char prev_c;
/* skip any spaces at the beginning */
while (*ent_p && isspace((int) *ent_p)) {
ent_p++;
}
if (*ent_p == '\0') {
DEBUG(DBG_MAIN_HI, "match_re_list(): skipping empty final entry.\n");
break;
}
/*
* find the end of this entry, i.e. the next unescaped ':'
*
* Note there are no other internal quoting mechanisms (as there are in
* the full PCRE lists) except the backslash so we don't have to worry
* about any other quoting delimiter.
*/
end_ent = ent_p;
prev_c = '\0';
while (*end_ent && (*end_ent != ':' || prev_c == '\\')) {
prev_c = *end_ent;
end_ent++;
}
/* only dig deeper into non-empty entries */
if (end_ent > ent_p) {
char *start_re = ent_p;
char *end_re = NULL;
static struct str exp;
static int exp_inited = FALSE;
static struct str msg;
static int msg_inited = FALSE;
pcre_info_t *re_info;
int result;
/* check for a negation sign */
if (*start_re == '!') {
start_re++;
matchresult = MATCH_NOMATCH; /* a match of a negated item... */
/* skip any spaces after the '!' */
while (*start_re && start_re <= end_ent && isspace((int) *start_re)) {
start_re++;
}
}
/*
* search for the end of the RE, i.e. the next un-escaped ';', or
* the end of the entry, or the end of the string, whichever comes
* first
*/
end_re = start_re;
prev_c = '\0';
while (*end_re &&
(*end_re != ';' || prev_c == '\\') &&
end_re < end_ent) {
prev_c = *end_re;
end_re++;
}
if (start_re == end_re) {
/* skip empty REs */
DEBUG(DBG_MAIN_HI, "match_re_list(): skipping empty RE\n");
continue;
}
if (!exp_inited) {
STR_INIT(&exp);
exp_inited = TRUE;
} else {
STR_CHECK(&exp);
STR_CLEAR(&exp);
}
STR_NEXT(&exp, '^'); /* create a fully anchored RE pattern */
STR_NCAT(&exp, start_re, (size_t) (end_re - start_re));
STR_CAT(&exp, "$"); /* NUL-terminates the string too */
if (!msg_inited) {
STR_INIT(&msg);
msg_inited = TRUE;
} else {
STR_CHECK(&msg);
STR_CLEAR(&msg);
}
if (*end_re == ';') {
/* note we're skipping the ';' itself */
STR_NCAT(&msg, end_re + 1, (size_t) (end_ent - end_re - 1));
STR_NEXT(&msg, '\0');
}
/* RE lists can be dynamic so we cannot cache this... */
if (!(re_info = compile_pcre_expr(STR(&exp), '/', icase ? "i" : "", STR(&msg), matchresult, reasonp))) {
/* exp and msg are reused... */
return MATCH_FAIL;
}
result = match_pcre_expr(str, str_len, re_info, TRUE, reasonp);
/*
* note that since we never pre-compile these patterns we could
* just use matchresult without resetting it from re_info, but
* doing it this way preserves the compile_pcre_expr() API.
*/
matchresult = re_info->matchresult;
/* note re_info->{re,msg} are reused and re_info->opts is static */
xfree((char *) re_info->code);
xfree((char *) re_info);
re_info = NULL;
switch (result) {
case MATCH_NOMATCH:
/* just continue after the matching so the end-of-list test works! */
break;
case MATCH_MATCHED:
DEBUG4(DBG_MAIN_MID, "match_re_list(): '%s%v' %s: returning %s\n",
matchresult == MATCH_NOMATCH ? "! " : "",
str,
result == MATCH_MATCHED ? "matched" : "notmatched",
matchresult == MATCH_MATCHED ? "matched" : "NOT-matched");
return matchresult;
case MATCH_FAIL:
DEBUG1(DBG_MAIN_MID, "match_re_list(): error: %s\n", *reasonp);
return MATCH_FAIL;
#if 0 /* #ifndef NDEBUG */
default:
assert(FALSE);
#endif
}
/* continue with next pattern */
} else {
DEBUG(DBG_MAIN_HI, "match_re_list(): skipping empty list entry\n");
}
if (*end_ent == '\0') {
break; /* if there is one */
}
}
DEBUG2(DBG_ADDR_HI, "string '%v' does not match in '%v'\n",
str, patterns);
return MATCH_NOMATCH;
}
#ifdef HAVE_BSD_NETWORKING
/*
* match_ip - match an ip against a list of host/net numbers
*
* Takes a list of IP:IP:IP and compares against an IP given in ASCII.
*
* 'IP' is an IP number or IP/bits CIDR network spec.
*
* IP may optionally be preceded by "![ \t]*" which will reverse the return
* value (i.e. immediately return 0 instead of 1) if the IP matches.
*
* IP may optionally be followed by ';message text', which of course must not
* contain a ':'
*
* Note that the field separator shown above as ':' is specified by the caller,
* as is message sub-field separator show above as ';'. Normally the field
* separator will be either ':' or ',' and the sub-field separator will be ';'
* or '\0' (i.e. none).
*
* WARNING: cannot diddle with pattern -- may be read-only string.
*
* RETURNS: TRUE if matched, else FALSE (unless the IP was prefixed by '!', in
* which case it returns the opposite).
*
* WARNING: does not use the MATCH_* returns -- callers only expect boolean.
*
* If a match is found and if a sub-field accompanies the matching item then
* the *reasonp will be set to point to a copy of its value too.
*
* WARNING: There is no error reporting capability in this function's API. If
* the pattern is mal-formed then in theory it can never be matched and in
* theory if a mal-formed pattern is negated with a '!' then 1 should be
* returned immediately. However this is not currently the case since given
* the way this function is used such behaviour would most likely cause an
* uninentional wildcard match.
*
* XXX allow_wildcard isn't actually used and should be zapped.
*/
unsigned int
match_ip(ip, patterns, fldsep, msgsep, allow_wildcard, reasonp)
/*const*/ char *ip; /* ip address we are matching */
/*const*/ char *patterns; /* list of patterns we are using to match it */
int fldsep; /* field separator character */
int msgsep; /* optional message string separator */
int allow_wildcard; /* allow 0/0? */
char **reasonp; /* optional pointer for returned message string */
{
/*const*/ char *pat_p;
/*const*/ char *end_p;
size_t len;
if (!patterns) {
DEBUG1(DBG_ADDR_HI, "match_ip(): null patterns pointer, nothing to do for %v...\n", ip);
return FALSE;
}
DEBUG4(DBG_ADDR_HI, "match_ip(%v, %v, %c, %c): called...\n", ip, patterns, fldsep, msgsep);
if (reasonp) {
*reasonp = NULL;
}
/* run through the pattern list */
for (pat_p = patterns; /* NOTEST */ ; pat_p = end_p + 1) {
int matchresult = TRUE;
char prev_c;
/* skip any spaces at front */
while (*pat_p && isspace((int) *pat_p)) {
pat_p++;
}
if (*pat_p == '\0') {
DEBUG(DBG_MAIN_HI, "match_ip(): skipping empty final entry.\n");
break;
}
/* find the end of this entry */
end_p = pat_p;
prev_c = '\0';
while (*end_p && (*end_p != fldsep || prev_c == '\\')) {
prev_c = *end_p;
end_p++;
}
if (end_p > pat_p && *pat_p == '!') {
pat_p++;
matchresult = FALSE; /* a match of a negated item... */
/* skip any spaces after the '!' */
while (*pat_p && pat_p <= end_p && isspace((int) *pat_p)) {
pat_p++;
}
}
/* skip empty patterns */
/* XXX maybe we could allow empty patterns with "; text" too */
len = end_p - pat_p;
if (len > 0) {
char *p;
static struct str exp;
static int inited = FALSE;
if (!inited) {
STR_INIT(&exp);
inited = TRUE;
} else {
STR_CHECK(&exp);
STR_CLEAR(&exp);
}
STR_NCAT(&exp, pat_p, len);
STR_NEXT(&exp, '\0');
p = NULL;
if (msgsep && (p = strchr(STR(&exp), msgsep))) {
*p = '\0';
p = chop(p + 1);
}
chop(STR(&exp));
/* warn if we have a deprecated wild-card at end of the current pattern */
if (*(end_p - 1) == '*') {
/*
* this could get rather noisy for the first time around, but
* keeping track of which ones we've warned about, even per
* process, is damn near impossible to do safely.
*/
write_log(WRITE_LOG_PANIC,
"match_ip(): an unsupported wildcard pattern [%v] was found in '%v'!",
STR(&exp), patterns);
}
/* XXX localnet should be deprecated */
if (strcmpic("localnet", STR(&exp)) == 0) {
DEBUG2(DBG_ADDR_HI,
"match_ip(): found 'localnet' in list...\n",
ip,
smtp_local_net);
/* our pattern will be the string in smtp_local_net */
if (!smtp_local_net) {
DEBUG1(DBG_ADDR_HI, "match_ip(): nil smtp_local_net cannot match %v...\n", ip);
/* continue with next pattern */
} else {
switch (match_ip_net(ip, smtp_local_net, allow_wildcard)) {
case MATCH_NOMATCH:
break; /* not this one! */
case MATCH_FAIL:
/* this should be almost impossible for smtp_local_net */
write_log(WRITE_LOG_PANIC,
"match_ip(): invalid smtp_local_net [%v] was found for localnet in '%v'!",
smtp_local_net, patterns);
break;
default:
if (p && reasonp) {
*reasonp = COPY_STRING(p);
}
DEBUG5(DBG_ADDR_MID,
"ip [%v] matches %slocalnet [%v]%s%v\n",
ip,
matchresult ? "" : "!",
smtp_local_net,
(reasonp && *reasonp) ? " with comment: " : "",
(reasonp && *reasonp) ? *reasonp : "");
return matchresult;
}
}
/* continue with next pattern */
#ifdef HAVE_LIBWHOSON
} else if (strcmpic("whoson", STR(&exp)) == 0) {
int wso;
char retbuf[BUFSIZ];
DEBUG1(DBG_ADDR_HI,
"(checking if [%v] is listed in the 'whoson' database)\n", ip);
*retbuf = '\0';
wso = wso_query(ip, retbuf, (int) sizeof(retbuf));
retbuf[sizeof(retbuf) - 1] = '\0'; /* just in case... */
if (wso < 0 && *retbuf) {
/* XXX hope this doesn't flood the log! */
write_log(WRITE_LOG_PANIC, "wso_query(%v): whoson query failed: %v", ip, retbuf);
/* continue with next pattern */
} else if (wso == 0) {
DEBUG3(DBG_ADDR_MID, "ip [%v] found by whoson: %s%v.\n",
ip,
matchresult ? "" : "!",
retbuf);
return matchresult;
} else {
DEBUG1(DBG_ADDR_MID, "ip [%v] NOT found by whoson\n", ip);
/* continue with next pattern */
}
#endif
} else {
switch (match_ip_net(ip, STR(&exp), allow_wildcard)) {
case MATCH_NOMATCH:
break; /* not this one! */
case MATCH_FAIL:
write_log(WRITE_LOG_PANIC,
"match_ip(): invalid netspec [%v] was found in '%v'!",
STR(&exp), patterns);
break;
default:
if (p && reasonp) {
*reasonp = COPY_STRING(p);
}
DEBUG5(DBG_ADDR_MID,
"ip [%v] matches IP/network of %s[%v]%s%v\n",
ip,
matchresult ? "" : "!",
STR(&exp),
(reasonp && *reasonp) ? " with comment: " : "",
(reasonp && *reasonp) ? *reasonp : "");
return matchresult;
}
/* continue with next pattern */
}
}
if (*end_p == '\0') {
break;
}
}
DEBUG2(DBG_ADDR_HI, "match_ip(): [%v] does not match any IP/network in '%v'\n",
ip, patterns);
return FALSE;
}
/*
* RETURNS: MATCH_MATCHED, MATCH_NOMATCH, or MATCH_FAIL on error.
*
* XXX allow_wildcard isn't actually used and should be zapped.
*/
int
match_ip_net(ipaddr, netspec, allow_wildcard)
char *ipaddr;
char *netspec;
int allow_wildcard;
{
in_addr_t dest;
in_addr_t net;
in_addr_t mask;
int bits = 0;
DEBUG2(DBG_ADDR_HI, "match_ip_net([%v], [%v]) called...\n", ipaddr, netspec);
net = 0; /* net must be pre-initialized */
if ((bits = inet_net_pton(AF_INET, netspec, (void *) &net, sizeof(net))) == -1) {
DEBUG2(DBG_ADDR_LO, "match_ip_net(): [%v] is invalid: %s\n", netspec, strerror(errno));
return MATCH_FAIL;
}
if (bits > 32) {
/* note this is normally impossible with a correct inet_net_pton() impl. */
DEBUG2(DBG_ADDR_LO, "match_ip_net(): [%v] has invalid number of bits: %d\n", netspec, bits);
return MATCH_FAIL;
}
if (!bits && net) {
/*
* note some inet_net_pton() implementations still use inet_addr()
* classful parsing syntax internally and as a result "0/0", for
* example, may not work and "0.0.0.0/0" may be needed instead.
*/
DEBUG1(DBG_ADDR_LO, "match_ip_net(): wildcard [%v] has non-zero bits in network part\n", netspec);
return MATCH_FAIL;
}
if (!bits && !net && !allow_wildcard) { /* all zero bits is a proper wildcard */
DEBUG1(DBG_ADDR_LO, "match_ip_net(): [%s] is an all zeros wildcard.\n", netspec);
return MATCH_FAIL;
}
/*
* Note: ((u_int32_t) << 32) is undefined according to the ANSI C spec.
* Hence the extra test for bits == 0.
*/
mask = (bits == 0) ? 0 : (0xffffffffL << (32 - bits));
net = ntohl(net);
if (mask && !(net & mask)) {
/* should never match anything valid */
DEBUG2(DBG_ADDR_MID, "match_ip_net(): warning: [%v] has all zero bits in net part (0x%x).\n", netspec, net & mask);
}
if (mask && (net & ~mask)) {
/* was giving a host address for the netspec intentional? */
DEBUG2(DBG_ADDR_MID, "match_ip_net(): warning: [%v] has non-zero bits in host part (0x%x).\n", netspec, net & ~mask);
}
dest = 0; /* dest probably should be pre-initialized */
switch (inet_pton(AF_INET, ipaddr, &dest)) {
case 0:
DEBUG1(DBG_ADDR_LO, "match_ip_net(): [%s] is not parsable.\n", ipaddr);
return MATCH_FAIL;
case -1:
DEBUG2(DBG_ADDR_LO, "match_ip_net(): [%s] is invalid: %s\n", ipaddr, strerror(errno));
return MATCH_FAIL;
}
dest = ntohl(dest);
#if 0 /* noisy! for really heavy duty debugging only! */
DEBUG4(DBG_ADDR_HI, "match_ip_net(): dest=0x%x, net=0x%x, bits=%d, mask=0x%x\n", dest, net, bits, mask);
#endif
return ((dest & mask) == (net & mask));
}
/*
* match_dnsbl - look up subdomain in list of domains given in dnsbl
*
* - dnsbl is a colon-separated list of domains in which an A RR for
* the specified name is looked up.
*
* - Optionally a comma-separated list of valid A RR values, either as explicit
* 4-octet ascii-form host addresses, or in a network/mask form, follows a
* semicolon after any domain (given in form suitable for a config file entry):
*
* :dns.bl.domain; 127.0.0.1, 127.0.0.4
* :another.dns.bl.domain; 127/8
*
* Returns 1 if any matched, else 0.
*
* if a match is found then *matchp will be set to the fully expanded
* domain that matched and *addrp will be set to the A RR's value, both as
* ASCII strings; and as well if a TXT RR is found at the same domain then
* the *msgp will be set to point to its value too.
*/
int
match_dnsbl(target, dnsbl, dnsbl_name, matchp, addrp, msgp)
char *target; /* subdomain to match */
char *dnsbl; /* DNS BlackList domain list */
char *dnsbl_name; /* name of the DNSBL */
char **matchp; /* pointer to hold formatted match address */
char **addrp; /* pointer to hold ASCII A RR value address */
char **msgp; /* pointer to hold TXT message pointer */
{
static int dns_took_too_long = FALSE;
static int rbl_took_too_long = FALSE;
char *pat_p;
char *end_p;
int rc = FALSE;
size_t len;
time_t rbl_start_time;
time_t rbl_end_time;
time_t start_getone_time;
time_t end_getone_time;
#define MAX_RES_TIMEOUT ((RES_TIMEOUT * 4/*retry*/ * MAXNS) - 10) /* XXX is this too long? */
#define MAX_DNSBL_TIMEOUT ((smtp_receive_command_timeout / 2) - 2)
if (msgp) {
*msgp = NULL;
}
if (!dnsbl) {
DEBUG1(operation_mode == TEST_DNSBL_MODE ? 0 : DBG_ADDR_HI,
"match_dnsbl(): DNSBL %s is empty\n",
dnsbl_name);
return 0;
}
DEBUG2(DBG_ADDR_HI, "match_dnsbl(): beginning search for %s DNSBL %s\n", target, dnsbl_name);
/* run through the pattern list */
time(&rbl_start_time);
for (pat_p = dnsbl; /* NOTEST */ ; pat_p = end_p + 1) {
char *a_rr_pats;
/*
* this matches any de facto standard RBL value. Don't use 0/0 here!
* Remember the fiasco with relayips.shub-inter.net!
*/
a_rr_pats = "127/8";
/* skip any spaces at front */
while (*pat_p && isspace((int) *pat_p)) {
pat_p++;
}
/* find the end of the next pattern */
end_p = pat_p;
while (*end_p && *end_p != ':' && !isspace((int) *pat_p)) {
end_p++;
}
/* XXX chop spaces off the end too? */
/* skip empty patterns */
len = end_p - pat_p;
if (len > 0) {
char *bl_hname;
char *txtaddr;
size_t bl_hname_len = strlen(target) + len + 1; /* one more for the '.' */
char *p;
struct hostent *hp;
/* WARNING: we can't diddle with dnsbl -- it may be read-only string. */
bl_hname = xmalloc(bl_hname_len + 1);
strcpy(bl_hname, target);
strcat(bl_hname, ".");
strncat(bl_hname, pat_p, len);
bl_hname[bl_hname_len] = '\0';
DEBUG1(DBG_ADDR_HI, "match_dnsbl(): working on DNSBL spec '%v'\n", bl_hname);
if ((p = strchr(bl_hname, ';'))) {
a_rr_pats = p;
*a_rr_pats = '\0';
a_rr_pats++;
DEBUG1(DBG_ADDR_HI, "match_dnsbl(): found explicit IP pattern(s) [%v]\n", a_rr_pats);
}
DEBUG2(DBG_ADDR_MID,
"match_dnsbl(): looking for an A RR matching [%v] at '%v'\n",
a_rr_pats, bl_hname);
/*
* Do we (really?) care if there's a temporary server failure when
* trying to look up an DNSBL? If we were very serious about
* blocking every possible piece of unwanted e-mail then we would
* be, I guess. That would rather complicate things for callers
* though because then we'd have to return a three (or four) state
* value insead of a binary result, and also any relevant
* information about why the query failed.
*/
time(&start_getone_time);
if ((hp = gethostbyname(bl_hname))) {
struct in_addr bl_hname_addr;
time(&end_getone_time);
if (!dns_took_too_long && (end_getone_time - start_getone_time) > MAX_RES_TIMEOUT) {
write_log(WRITE_LOG_PANIC, "successful DNS lookup for %s in %s took %ld seconds!",
bl_hname,
dnsbl_name,
(long int) (end_getone_time - start_getone_time));
dns_took_too_long = TRUE;
} else {
DEBUG2(DBG_ADDR_HI, "match_dnsbl(): successful DNS lookup for %s took %ld seconds\n",
bl_hname, (long int) (end_getone_time - start_getone_time));
}
memcpy((char *) &bl_hname_addr, hp->h_addr_list[0], sizeof(struct in_addr));
txtaddr = COPY_STRING(inet_ntoa(bl_hname_addr));
if (match_ip(txtaddr, a_rr_pats, ',', '\0', FALSE, (char **) NULL)) {
struct error *junkerr;
/* NOTE: don't free bl_hname or txtaddr here -- they are returned! */
*matchp = bl_hname;
*addrp = txtaddr;
if (msgp) {
*msgp = bind_lookup_txt_rr(bl_hname, &junkerr);
}
DEBUG6(DBG_ADDR_LO,
"match_dnsbl(): found an A RR matching [%v] at DNSBL %v%s%s%v%s\n",
a_rr_pats,
bl_hname,
!msgp ? " (with no TXT RR lookup)" : "",
msgp && *msgp ? "\n\twith associated TXT RR(s)\n\t``" : ": error getting TXT RR: ",
msgp && *msgp ? *msgp : (junkerr ? junkerr->message : "NO DATA"),
msgp && *msgp ? "''" : "");
/* XXX if debug > 9 then junkerr leaks */
time(&rbl_end_time);
if (!rbl_took_too_long && (rbl_end_time - rbl_start_time) > MAX_DNSBL_TIMEOUT) {
write_log(WRITE_LOG_PANIC, "partial search through %s DNSBL took %ld seconds!",
dnsbl_name,
(long int) (end_getone_time - start_getone_time));
rbl_took_too_long = TRUE;
} else {
DEBUG3(DBG_ADDR_MID, "match_dnsbl(): partial search for target %s in DNSBL %s took %ld secs\n",
target, dnsbl_name, (long int) (rbl_end_time - rbl_start_time));
}
rc = TRUE;
if (debug < 9) {
return rc;
} else {
/*
* This stuff may leak (especially if more than one
* match is found), but only during debugging.
*/
*matchp = COPY_STRING(bl_hname);
*addrp = COPY_STRING(txtaddr);
*msgp = COPY_STRING(*msgp);
}
}
xfree(txtaddr);
} else {
time(&end_getone_time);
if (!dns_took_too_long && (end_getone_time - start_getone_time) > MAX_RES_TIMEOUT) {
write_log(WRITE_LOG_PANIC, "empty DNS lookup for %s in %s took %ld seconds!",
bl_hname,
dnsbl_name,
(long int) (end_getone_time - start_getone_time));
dns_took_too_long = TRUE;
} else {
DEBUG2(DBG_ADDR_HI, "match_dnsbl(): empty DNS lookup for %s took %ld seconds\n",
bl_hname, (long int) (end_getone_time - start_getone_time));
}
}
xfree(bl_hname);
}
if (*end_p == '\0') {
break;
}
}
time(&rbl_end_time);
if (!rbl_took_too_long && (rbl_end_time - rbl_start_time) > MAX_DNSBL_TIMEOUT) {
write_log(WRITE_LOG_PANIC, "Complete search through %s DNSBL took %ld seconds!",
dnsbl_name,
(long int) (rbl_end_time - rbl_start_time));
rbl_took_too_long = TRUE;
}
DEBUG3(DBG_ADDR_LO, "match_dnsbl(): target %s not found in DNSBL %s (after %ld secs)\n",
target, dnsbl_name, (long int) (rbl_end_time - rbl_start_time));
return rc;
}
#define CHOP_B(x) ((int) ((x) & 0xff))
/*
* flip the octets of an Internet address, e.g. to create an in-addr.arpa name
*/
char *
flip_inet_addr(inaddr)
char *inaddr;
{
in_addr_t addr;
addr = get_inet_addr(inaddr);
addr = ntohl(addr);
return xprintf("%u.%u.%u.%u",
CHOP_B(addr),
CHOP_B(addr >> 8),
CHOP_B(addr >> 16),
CHOP_B(addr >> 24));
}
#undef CHOP_B
#endif /* HAVE_BSD_NETWORKING */
/*
* compile and study the REs in re_list producing a voidplist_t
*/
voidplist_t *
compile_pcre_list(re_list, errorp)
char *re_list;
char **errorp;
{
voidplist_t *nl = NULL;
voidplist_t *cur;
voidplist_t *next;
voidplist_t *new;
/*const*/ char *ent_p;
/*const*/ char *end_ent;
*errorp = NULL;
if (! re_list) {
return NULL;
}
/* run through the pattern list */
for (ent_p = re_list; /* NOTEST */ ; ent_p = end_ent + 1) {
char prev_c;
int delim_c;
/*const*/ char *start_re;
/*const*/ char *end_re = NULL;
char *p;
struct str exp;
struct str opts;
struct str msg;
size_t msglen;
pcre_info_t *re_info;
int matchresult;
/* skip any spaces at the beginning */
while (*ent_p && isspace((int) *ent_p)) {
ent_p++;
}
if (*ent_p == '\0') {
DEBUG(DBG_MAIN_HI, "compile_pcre_list(): skipping empty final entry.\n");
break;
}
/*
* the first thing in an entry is the RE
*/
start_re = end_ent = ent_p;
/*
* unless it is the standard '!' negation character, in which case the
* RE starts at the next non-space char....
*/
if (*start_re == '!') {
start_re++;
matchresult = MATCH_NOMATCH; /* a match of a negated item... */
/* skip any spaces after the '!' */
while (*start_re && isspace((int) *start_re)) {
start_re++;
}
} else {
matchresult = MATCH_MATCHED;
}
/*
* the first non-space char of an RE is the RE delimiter char
*/
delim_c = *start_re++;
if (delim_c == ':') {
DEBUG(DBG_MAIN_HI, "compile_pcre_list(): skipping empty entry.\n");
continue; /* XXX is this always safe? */
}
if (delim_c == ';') {
*errorp = xprintf("';' is not a valid RE delimiter");
return NULL;
}
/*
* walk the entry end pointer up to the end of the RE -- i.e. the next
* unescaped delim_c
*/
end_ent = start_re + 1;
if (*end_ent == '\0') {
*errorp = xprintf("list ends before last RE's closing '%c' delimiter",
delim_c);
return NULL;
}
prev_c = '\0';
while (*end_ent && (*end_ent != delim_c || prev_c == '\\')) {
prev_c = *end_ent;
end_ent++;
}
end_re = end_ent;
if (*end_re != delim_c) {
*errorp = xprintf("RE '%S' has no closing '%c' delimiter",
(size_t) (end_ent - ent_p), ent_p,
delim_c);
return NULL;
}
/*
* walk the the end pointer past any options and message text up to the
* beginning of the next list entry, i.e. the next un-escaped ':'
*/
prev_c = '\0';
while (*end_ent && (*end_ent != ':' || prev_c == '\\')) {
prev_c = *end_ent;
end_ent++;
}
/* don't do this check until after end_ent points to any next entry */
if (start_re == end_re) {
/* skip empty patterns */
DEBUG(DBG_MAIN_HI, "compile_pcre_list(): skipping empty RE\n");
/* this should be safe */
continue;
}
STR_INIT(&exp); /* do not re-use */
STR_NCAT(&exp, start_re, (size_t) (end_re - start_re));
STR_NEXT(&exp, '\0'); /* NUL-terminates the string */
/* point past the final delimiter again */
end_re++;
/* get (optional) options: "/RE/options" */
for (p = end_re; p <= end_ent && *p && *p != ';' && *p != ':'; p++) {
; /* NO-OP */
}
if (p > end_re) {
STR_INIT(&opts); /* do not re-use */
STR_NCAT(&opts, end_re, (size_t) (p - end_re));
STR_NEXT(&opts, '\0');
/* XXX str_chop(&msg); ??? */
} else {
STR_ZAP(&opts); /* do not STR_FREE() any previous one! */
}
/* XXX skip whitespace up to the first ';'??? */
/* get (optional) message: "/RE/options; message" */
msglen = end_ent - p - 1;
if (*p++ == ';' && msglen > 0) {
STR_INIT(&msg); /* do not re-use */
STR_NCAT(&msg, p, msglen);
STR_NEXT(&msg, '\0');
/* XXX str_chop(&msg); ??? */
} else {
STR_ZAP(&msg); /* do not STR_FREE() any previous one! */
}
if (!(re_info = compile_pcre_expr(STR(&exp), delim_c, STR(&opts), STR(&msg), matchresult, errorp))) {
STR_FREE(&exp);
STR_FREE(&opts);
STR_FREE(&msg);
free_pcre_list(nl);
return NULL;
}
nl = add_voidplist(nl, (char *) re_info, free_pcre_info);
/* continue with next pattern */
if (*end_ent == '\0') {
break; /* if there is one */
}
}
/* finally reverse the list so tests happen in the stated order */
/* XXX could implement and use append_voidplist() above to avoid this */
new = NULL;
for (cur = nl; cur; cur = next) {
next = cur->succ;
cur->succ = new;
new = cur;
}
return new;
}
static pcre_info_t *
compile_pcre_expr(exp, delim_c, opts, msg, matchresult, errorp)
char *exp;
int delim_c;
char *opts;
char *msg;
int matchresult;
char **errorp;
{
pcre_info_t *re_info;
int capturecount;
const char *errtxt;
int err_offset;
int rc;
re_info = (pcre_info_t *) xmalloc(sizeof(*re_info));
re_info->re = exp;
re_info->delim_c = delim_c;
re_info->matchresult = matchresult;
re_info->flags = 0;
if (opts) {
const char *o;
re_info->opts = opts;
for (o = re_info->opts; *o; o++) {
switch (*o) {
case 'A':
re_info->flags |= PCRE_ANCHORED;
continue;
case 'E': /* XXX PHP-4.x uses 'D', maybe we could use '$'? */
re_info->flags |= PCRE_DOLLAR_ENDONLY;
continue;
case 'U':
re_info->flags |= PCRE_UNGREEDY;
continue;
case 'X':
re_info->flags |= PCRE_EXTRA;
continue;
case 'i':
re_info->flags |= PCRE_CASELESS;
continue;
case 'm':
re_info->flags |= PCRE_MULTILINE;
continue;
case 's':
re_info->flags |= PCRE_DOTALL;
continue;
case 'x':
re_info->flags |= PCRE_EXTENDED;
continue;
}
if (! isspace((int) *o)) {
*errorp = xprintf("invalid option '%c' for RE %c%v%c%v",
*o, delim_c, re_info->re, delim_c, re_info->opts);
xfree(re_info->re);
xfree(re_info->opts);
xfree((char *) re_info);
return NULL;
}
}
} else {
re_info->opts = NULL;
}
/* get any accompanying message into re_info->msg */
re_info->msg = msg;
/* note: we use %s here so we see the RE in it's "natural" form. */
DEBUG4(DBG_MAIN_HI, "compile_pcre_expr(): compiling RE %c%v%c%v\n",
delim_c, re_info->re, delim_c, re_info->opts ? re_info->opts : "");
if (re_info->msg) {
DEBUG1(DBG_MAIN_HI, "\twith message '%v'\n", re_info->msg);
}
re_info->code = pcre_compile(re_info->re, re_info->flags, &errtxt, &err_offset, (const unsigned char *) NULL);
if (! re_info->code) {
*errorp = xprintf("error in regex %c%v%c%v at offset %d: %s",
delim_c, re_info->re, delim_c,
re_info->opts ? re_info->opts : "",
err_offset, errtxt);
xfree((char *) re_info);
return NULL;
}
errtxt = NULL;
re_info->hints = pcre_study(re_info->code, 0, &errtxt);
if (re_info->hints) {
DEBUG1(DBG_MAIN_HI, "compile_pcre_expr(): pcre_study() returned optimisation stuff: match_limit=%lu\n", re_info->hints->match_limit);
}
if (!re_info->hints && errtxt) {
*errorp = xprintf("error while studying RE %c%v%c%v: %s",
delim_c, re_info->re, delim_c,
re_info->opts ? re_info->opts : "",
errtxt);
free_pcre_info(re_info);
return NULL;
}
if ((rc = pcre_fullinfo(re_info->code,
re_info->hints,
PCRE_INFO_CAPTURECOUNT,
&capturecount)) != 0) {
*errorp = xprintf("pcre_fullinfo(CAPTURECOUNT) failed [%d] for RE %c%v%c%v",
rc,
delim_c, re_info->re, delim_c,
re_info->opts ? re_info->opts : "");
free_pcre_info(re_info);
return NULL;
}
/* we always capture at least one string -- the whole match... */
capturecount++;
re_info->ovecsize = capturecount * 3; /* table is 3 wide */
return re_info;
}
/*
* free_pcre_info - free all storage pointed to by a pcre_info_t pointer.
*
* Note a pointer to this function is passed to add_voidplist() for later use
* by free_voidplist(), thus its "odd" signature using void*.
*/
void
free_pcre_info(val)
void *val;
{
register pcre_info_t *re_info = val;
/*
* XXX does pcre_study() return newly allocated storage which we point to
* using re_info->hints? There's no hint either way in the pcreapi(3)
* manual....
*/
xfree((char *) re_info->code);
xfree((char *) re_info);
}
/*
* match_pcre_list - match the string sp against a pre-compiled list of PCRE REs
*
* RETURNS: MATCH_MATCHED if matched, else MATCH_NOMATCH, and MATCH_FAIL if
* anything goes wrong.
*
* May return MATCH_NOMATCH immediately if a negated expression matches, thus
* terminating the search.
*
* If a match is found, even a negated match, and if re_info->msg is not NULL,
* then the *reasonp will be set to point to a copy of its value, after
* sub-string substitutions have been performed on it if do_subs is non-zero.
*
* If no match is found and no errors encountered, or if re_info->msg is NULL,
* then *reasonp will also be set to NULL.
*
* XXX Maybe "sp" should just be a "struct str *" and get rid of len?
*/
int
match_pcre_list(sp, len, pcre_list, do_subs, reasonp)
char *sp; /* string we are matching */
size_t len; /* length of string */
voidplist_t *pcre_list; /* pre-compiled RE list */
int do_subs; /* perform substitutions on message? */
char **reasonp; /* optional pointer for returned message string */
{
voidplist_t *pat;
char *no_reason;
if (!reasonp) {
reasonp = &no_reason;
}
*reasonp = NULL;
for (pat = pcre_list; pat; pat = pat->succ) {
pcre_info_t *re_info = (pcre_info_t *) pat->storage;
switch (match_pcre_expr(sp, len, re_info, do_subs, reasonp)) {
case MATCH_NOMATCH:
continue;
case MATCH_MATCHED:
return re_info->matchresult;
case MATCH_FAIL:
return MATCH_FAIL;
#if 0 /* #ifndef NDEBUG */
default:
assert(FALSE);
#endif
}
}
return MATCH_NOMATCH;
}
/*
* match_pcre_expr() - match an RE compiled by compile_pcre_expr()
*
* RETURNS: MATCH_MATCHED if matched, else MATCH_NOMATCH, and MATCH_FAIL if
* anything goes wrong.
*
* Note re_info->matchresult is ignored -- it is only useful in the context of
* a list of REs and so it can only sensibly be made use of by the caller.
*
* If a match is found and if re_info->msg is not NULL, then the *reasonp will
* be set to point to a copy of its value, after sub-string substitutions have
* been performed on it if do_subs is non-zero.
*
* If returning MATCH_FAIL then *reasonp will point to an error message;
*
* XXX Maybe "sp" should just be a "struct str *" and get rid of len?
*/
static int
match_pcre_expr(sp, len, re_info, do_subs, reasonp)
char *sp; /* string we are matching */
size_t len; /* length of string */
pcre_info_t *re_info; /* RE stuff [result of compile_pcre_expr()] */
int do_subs; /* perform substitutions on message? */
char **reasonp; /* pointer for returned message string */
{
int rc;
int *ovec;
DEBUG8(DBG_MAIN_MID, "match_pcre_expr(): checking RE %s%c%v%c%v\n against '%V'%s\n",
re_info->matchresult == MATCH_NOMATCH ? "! " : "",
re_info->delim_c, re_info->re, re_info->delim_c,
re_info->opts ? re_info->opts : "",
MIN(len, 60), sp,
(len >= 60) ? "[....]" : "");
ovec = (int *) xmalloc((size_t) re_info->ovecsize * sizeof(int));
rc = pcre_exec(re_info->code, re_info->hints, sp, (int) len, 0, 0, ovec, re_info->ovecsize);
if (rc == PCRE_ERROR_NOMATCH) {
xfree((char *) ovec);
return MATCH_NOMATCH;
}
if (rc <= 0) {
switch (rc) {
case 0:
*reasonp = xprintf("pcre_exec() encountered too many subexpressions in RE %c%v%c%v (%d expected)",
re_info->delim_c, re_info->re, re_info->delim_c,
re_info->opts ? re_info->opts : "",
((re_info->ovecsize / 3) - 1));
break;
case PCRE_ERROR_NULL:
/* XXX should we print 'sp' here too? only if really null? */
*reasonp = xprintf("bad pcre_exec() parameter for RE %c%v%c%v",
re_info->delim_c, re_info->re, re_info->delim_c,
re_info->opts ? re_info->opts : "");
break;
case PCRE_ERROR_NOMEMORY: /* XXX impossible */
*reasonp = xprintf("pcre_exec() ran out of memory while matching RE %c%v%c%v",
re_info->delim_c, re_info->re, re_info->delim_c,
re_info->opts ? re_info->opts : "");
break;
case PCRE_ERROR_MATCHLIMIT: {
int ml;
(void) pcre_config(PCRE_CONFIG_MATCH_LIMIT, (void *) &ml);
*reasonp = xprintf("pcre_exec() hit recursion or backtracking limit using RE %c%v%c%v (%smatch limit = %d)",
re_info->delim_c, re_info->re, re_info->delim_c,
re_info->opts ? re_info->opts : "",
re_info->hints ? "using default " : "",
re_info->hints ? re_info->hints->match_limit : ml);
break;
}
case PCRE_ERROR_BADMAGIC:
case PCRE_ERROR_UNKNOWN_NODE:
*reasonp = xprintf("pcre_exec() given corrupt compiled RE %c%v%c%v",
re_info->delim_c, re_info->re, re_info->delim_c,
re_info->opts ? re_info->opts : "");
break;
default:
*reasonp = xprintf("unexpected pcre_exec() error code #%d using RE %c%v%c%v",
rc,
re_info->delim_c, re_info->re, re_info->delim_c,
re_info->opts ? re_info->opts : "");
break;
}
DEBUG1(DBG_MAIN_LO, "match_pcre_expr(): %s\n", *reasonp);
xfree((char *) ovec);
return MATCH_FAIL;
}
DEBUG6(DBG_MAIN_HI, "match_pcre_expr(): matched RE %s%c%v%c%v\n\twith %d substrings\n",
re_info->matchresult == MATCH_NOMATCH ? "! " : "",
re_info->delim_c, re_info->re, re_info->delim_c, re_info->opts,
rc);
if (re_info->msg) {
if (do_subs) {
*reasonp = pcre_expand_msg(re_info, sp, ovec, rc);
} else {
*reasonp = xprintf("%v", re_info->msg);
}
} else {
const char *matched = NULL;
if ((rc = pcre_get_substring(sp, ovec, rc, 0, &matched)) < 0) {
DEBUG5(DBG_MAIN_LO, "match_pcre_expr(): unexpected error #%d getting matched string for RE %c%v%c%v\n",
rc,
re_info->delim_c, re_info->re, re_info->delim_c, re_info->opts);
matched = sp;
}
*reasonp = xprintf("RE %c%v%c%v matched '%v'",
re_info->delim_c, re_info->re, re_info->delim_c,
re_info->opts ? re_info->opts : "",
matched);
if (rc >= 0) {
pcre_free_substring(matched);
}
}
xfree((char *) ovec);
return MATCH_MATCHED;
}
/*
* Do Python-style variable substitution on the message associated with the RE
* using values matched by the named subpatterns.
*/
/* ARGSUSED */
static char *
pcre_expand_msg(re_info, sp, ovec, nss)
pcre_info_t *re_info;
char *sp; /* len not needed for pcre_get_*substring() */
int *ovec;
int nss;
{
struct str out;
struct str *op = &out;
char *p; /* message index pointer */
char *q; /* digit pointer */
char *ep; /* message end pointer */
char *np; /* substring name */
const char *vp; /* substring value pointer */
int vlen; /* substring value length */
STR_INIT(op);
for (p = re_info->msg; *p; p++) {
if (*p == '$' || *p == '%') {
p++;
if (*p == '{') {
p++;
for (ep = p; *ep && *ep != '}' && (isalnum(*ep) || *ep == '_'); ep++) {
; /* NO-OP */
}
if (*ep != '}') {
DEBUG3(DBG_MAIN_LO, "pcre_expand_msg(): unterminated variable expression: '${%S' in '%v'\n",
(size_t) (ep - p), p,
re_info->msg);
/* just put back the expression */
STR_CAT(op, "${");
STR_NCAT(op, p, (size_t) (ep - p));
STR_NEXT(op, '}');
p = ep;
continue;
}
} else {
for (ep = p; *ep && (isalnum(*ep) || *ep == '_'); ep++) {
; /* NO-OP */
}
}
np = strncpy(xmalloc((size_t) (ep - p + 1)), p, (size_t) (ep - p));
np[ep - p] = '\0';
for (q = np; isdigit(*q); q++) {
; /* NO-OP */
}
vp = NULL;
if (*q == '\0') {
vlen = pcre_get_substring(sp, ovec, nss, atoi(np), &vp);
} else {
vlen = pcre_get_named_substring(re_info->code, sp, ovec, nss, np, &vp);
}
if (vlen < 0) {
switch (vlen) {
case PCRE_ERROR_NOSUBSTRING:
DEBUG2(DBG_MAIN_LO, "pcre_expand_msg(): no valid substring %s '${%s}'\n",
*q ? "named" : "numbered", np);
break;
default:
DEBUG7(DBG_MAIN_LO, "pcre_expand_msg(): unexpected error #%d getting substring %s '${%s}' for RE %c%v%c%v\n",
vlen,
*q ? "named" : "numbered",
np,
re_info->delim_c, re_info->re, re_info->delim_c, re_info->opts);
break;
}
/* substitute the empty string... */
} else {
str_printf(op, "%V", (size_t) vlen, vp);
pcre_free_substring(vp);
}
xfree(np);
p = (*ep == '}') ? ep : (ep - 1);
} else {
STR_NEXT(op, *p);
}
}
STR_NEXT(op, '\0');
return STR(op);
}
void
free_pcre_list(re_list)
voidplist_t *re_list;
{
voidplist_t *pat;
voidplist_t *next;
for (pat = re_list; pat; pat = next) {
pcre_info_t *re_info = (pcre_info_t *) pat->storage;
next = pat->succ;
xfree((char *) re_info->re);
xfree((char *) re_info->code);
if (re_info->opts) {
xfree((char *) re_info->opts);
}
if (re_info->msg) {
xfree((char *) re_info->msg);
}
xfree((char *) re_info);
xfree((char *) pat);
}
return;
}
/*
* turn re_list back into super-safe canonical pretty-printed input form
*/
char *
format_pcre_list(re_list)
voidplist_t *re_list;
{
struct str fmt;
struct str *fmtp = &fmt;
voidplist_t *pat;
STR_INIT(fmtp);
STR_CAT(fmtp, "\"\n\t:");
for (pat = re_list; pat; pat = pat->succ) {
pcre_info_t *re_info = (pcre_info_t *) pat->storage;
STR_NEXT(fmtp, re_info->delim_c);
/* XXX we could wrap with an escaped newline at the special character
* nearest the 80'th column.... */
str_printf(fmtp, "%v", re_info->re);
STR_NEXT(fmtp, re_info->delim_c);
if (re_info->opts) {
STR_CAT(fmtp, re_info->opts);
}
STR_CAT(fmtp, ";\\\n\t\t");
if (re_info->msg) {
/* XXX we could wrap with an escaped newline at the whitespace
* nearest the 80'th column.... */
str_printf(fmtp, "%v", re_info->msg);
}
STR_CAT(fmtp, "\\\n\t:");
}
STR_CAT(fmtp, "\"");
return STR(fmtp);
}
/*
* trusted_invoker -- check if the real_uid is the UID of a user in
* trusted_users, or prog_egid is the GID of a group in trusted_groups.
*/
int
trusted_invoker()
{
char *tm = NULL;
int just_trust = TRUE; /* set false if trusted_users or trusted_groups are set */
if (trusted_users && trusted_users[0]) {
just_trust = FALSE;
/* trusted_users is a colon separated list of user names */
for (tm = strcolon(trusted_users); tm; tm = strcolon((char *) NULL)) {
struct passwd *pw = getpwbyname(FALSE, tm);
if (pw && real_uid == pw->pw_uid) { /* only trust the _real_ user */
DEBUG1(DBG_MAIN_HI, "invoker is a trusted user: %s\n", pw->pw_name);
break;
}
}
}
if (tm == NULL && trusted_groups && trusted_groups[0]) {
just_trust = FALSE;
/* trusted_groups is a colon separated list of group names */
for (tm = strcolon(trusted_groups); tm; tm = strcolon((char *) NULL)) {
struct group *gr = getgrbyname(tm);
if (gr && prog_egid == gr->gr_gid) { /* trust the _effective_ group */
DEBUG1(DBG_MAIN_MID, "effective UID is trusted group: %s\n", gr->gr_name);
break;
}
}
}
if (tm == NULL && !just_trust) {
return FALSE;
}
if (just_trust) {
DEBUG1(DBG_MAIN_LO, "invoker is an unverified user %s\n", local_sender);
}
return TRUE;
}
/*
* Local Variables:
* c-file-style: "smail"
* End:
*/
syntax highlighted by Code2HTML, v. 0.9.1