Hi Sam and All,
let me try and resume this:
-allow=zone[,varspec]...
By example --so as to solicit comments from more people-- the change
enables to configure an additional variable in etc/esmtpd:
WHITELISTS='-allow=swl.spamhaus.org'
The default behavior is to set an empty BLOCK= so that if one had set
BLACKLISTS='-block=...' with the same default variable, when the
former lookup succeeds, the latter lookup is preempt(i)ed, so to say.
Now for passing the results to global filters. In 2010 we thought of
inventing an X-DNSWL: header just for this purpose (see below). That
is not needed, as there exists an Authentication-Results: header
designed for exactly this purpose. It is to be added at border MTAs
and examined by downstream filters and even MUAs. We just need to
define what values we may need to know for DNS lists. We certainly
need a zone, albeit we can lookup a local copy of it, for example:
WHITELISTS='-allow=dnswl.local,'
WHITELISTS+='_A_R_DNSWL_ZONE=list.dnswl.org,' # always set this
That varspec sets the globally known name of the list. However, we
need to examine the value that the list returns, in order to decide if
that is a pass or a neutral response. Regular expressions can do
that, for example:
WHITELISTS+='/[1-3]$/,' # match only low-hi scores
WHITELISTS+='_A_R_DNSWL_RESULT:pass,' # set "pass" if matched
WHITELISTS+='//,' # match anything
WHITELISTS+='_A_R_DNSWL_RESULT:found,' # set "found" if matched
WHITELISTS+='_A_R_DNSWL_RESULT=fail,' # set "fail" otherwise
In the above examples, the assignments done with ':' require that the
record is found and the last regex matched, while '=' sets the
variable unconditionally, provided that it is not set already.
Of course, regex can also capture substrings. By providing a name to
a captured string, it is possible to compose a variable name, like so:
WHITELISTS+='_A_R_DNSWL/(?<VALUE>[0-9.]*)/,'
WHITELISTS+=\
'_A_R_DNSWL|(?<DOMAIN>[-a-z0-9.]+)\s*(?<URL>https?://\S*)|:t'
The last line exemplifies using a delimiter different from "/", so as
to avoid backslashes. However, parentheses and question marks create
difficulties with the shell, and it may be simpler to leave them in
the environment. The trailing ":t" is for requiring the TXT record.
I hope that is not overly complicated. The patch attached is a draft,
in the sense that it should clarify any detail, but I haven't even
tried to compile it, although I function-wise tested some parts of it.
The idea is that, given the above setting, couriertcpd will set, for
127.0.0.2:
_A_R_DNSWL_ZONE=list.dnswl.org
_A_R_DNSWL_RESULT=found
_A_R_DNSWL=127.0.10.0
_A_R_DNSWL_VALUE=127.0.10.0
Submit will recognize those particular variable names and write a line
like:
Authentication-Results: host.example.com;
dnswl=found dns.zone=list.dnswl.org policy.value=127.0.10.0
Another variable I added to submit is SPFALLOW for whitelisted
senders. (I set my site's SPF record that way, and it works great.
However, not all those who publish -all do so, which is why I need
SPFALLOW.)
In general, if we work out the few issues that remain, -allow can
result in an interesting alternative to -access, where rbldnsd or
similar tool would play a role equivalent to bdb.
What you think?
Sam, if you give me the green light, two specific issues are whether
to modify argparse, and whether we need rfc2047_encode (it seems only
Received: uses that). For All, the properties of the new "dnswl"
authentication method: They will have to be eventually added to
http://www.iana.org/assignments/email-auth/email-auth.xml
On Wed 29/Sep/2010 23:56:50 +0200 Sam Varshavchik wrote:
> Alessandro Vesely writes:
>> On 28/Sep/10 23:18, Sam Varshavchik wrote:
>>> Alessandro Vesely writes:
>>>> An alternative is to define skiplists explicitly, e.g.
>>>>
>>>> -allow=swl.spamhaus.org,ALLOW \
>>>> -block=zen.spamhaus.org,BLOCK,"550 Reject ip=@" \
>>>> -block=some.other.example,BLOCK2 \
>>>> -skipif=ALLOW,BLOCK,BLOCK2
>>>>
>>>> In this case "-allow" is exactly a synonym of "-block", because it
>>>> feels ugly to say "block" for a whitelist.
>>>
>>> This looks more reasonable. [...]
>>>
>>> -skipif looks cleaner. Also, the lookup will be skipped anyway, if its
>>> environment variable is already set, so this is backwards compatible.
>>
>> That's also my opinion. (It would be nice to hear some other user,
>> though.)
>>
>>>> Do we need an additional comma to specify whether the list is
>>>> (supposed to be) IPv6 capable?
>>>
>>> I don't think it will be necessary. IPv6 addresses should
>>> automatically format an IPv6 query. An IPv6-formatted query to an IPv4
>>> list will be quietly ignored, DNSBLs already get a lot of crap
>>> queries, and they should return an NXDOMAIN by default.
>>
>> Matthias Leisi said list.dnswl.org is also planning to do IPv6, "to
>> adapt to the changing landscape of whitelisting", and
>> "implementation should start towards the end of Q1/2011 the latest."
>>
>> In order to avoid noise, we may assume as a rule --until a counter
>> example arises-- that white lists do IPv6 while black ones don't,
>> however coincidental it may be. In any case, we cannot do IPv6
>> queries until they publish their syntax. (I've found an "ugly
>> example" in http://tools.ietf.org/html/rfc5782#section-2.4)
>>
>> Another difference between "-block" and "-allow" may be to change
>> the default variable name to something different from BLOCK, just to
>> avoid rude surprises.
>
> The logical choice, of course, would be ALLOW.
>
> In that case, the logical default for -skipif would be "BLOCK,ALLOW".
> I think this provides meaningful defaults and minimizes the number of
> settings for the most common use cases.
>
>
>>> This would be syntactical sugar. couriertcpd doesn't know anything
>>> about the daemon it spawns for each connection. couriertcpd would use
>>> this to set a private list of environment variables:
>>>
>>> _HEADER1="Blacklist: <DNSBL message>"
>>> _HEADER2="Whitelist: <DNSBL message>"
>>
>> "<DNSBL message>" is the content of the relevant environment
>> variable, including substitution of '@' with the IP number, correct?
>
> Yes, couriertcpd already constructs the message, either from the DNSBL
> itself or by providing a stock message of its own.
>
>>> courieresmtpd can read these variables from the environment, prefix
>>> each one's contents by "X-DNSBL-" and prefix it to every received
>>> message. It would also need to take any existing "X-DNSBL-" header in
>>> the received message and rename it to "X-Old-DNSBL-".
>>
>> Renaming can be done conveniently in submit, along with
>> "Old-Return-Path" and "Old-Received-SPF".
>
> Yes, I see that. This would mean that handling of the environment
> variables from couriertcpd would need to be done in submit rather than
> courieresmtpd, and arrangements will have to be made to preserve the
> environment variables for submit. It makes no sense to have similar
> functionality in two places, and to be consisted it would have to be
> Old-X-DNSBL.
--- courier/submit.original.C 2013-01-12 22:46:57.000000000 +0100
+++ courier/submit.C 2013-01-23 18:16:53.000000000 +0100
@@ -976,7 +976,8 @@
get_spf_errmsg("BOFHSPFMAILFROM", result,
errmsg, buf.c_str());
- if (errmsg_buf.size() > 0)
+ if (errmsg_buf.size() > 0 &&
+ ((p = getenv("SPFALLOW")) == 0 || *p == 0))
{
std::cout << errmsg_buf << std::endl;
return 1;
@@ -1414,6 +1415,127 @@
static void getrcpt(struct rw_info *rwi);
static void rcpttoerr(int, const char *, struct rw_info *);
+// tentative data layout... function to be optimized when layout is final
+static std::string a_r_from_env(const std::string& cme_name)
+{
+ std::string a_r;
+ char *p;
+
+ if ((p = getenv("_A_R_DNSWL_RESULT")) != 0)
+ {
+ a_r = "Authentication-Results: ";
+ a_r += cme_name;
+ a_r += ";\n dnswl=";
+ a_r += p;
+
+ size_t pos = 8 + strlen(p);
+ if ((p = getenv("_A_R_DNSWL_ZONE")) != 0)
+ {
+ static char const n[] = " dns.zone=";
+ size_t const add = sizeof n + strlen(p);
+ if (pos + add > 77)
+ {
+ a_r += "\n ";
+ pos = 1;
+ }
+ a_r += n;
+ a_r += p;
+ pos += add;
+ }
+ if ((p = getenv("_A_R_DNSWL_VALUE")) != 0)
+ {
+ static char const n[] = " policy.value=";
+ size_t const add = sizeof n + strlen(p);
+ if (pos + add > 77)
+ {
+ a_r += "\n ";
+ pos = 1;
+ }
+ a_r += n;
+ a_r += p;
+ pos += add;
+ }
+ if ((p = getenv("_A_R_DNSWL_DOMAIN")) != 0)
+ {
+ static char const n[] = " policy.domain=";
+ size_t const add = sizeof n + strlen(p);
+ if (pos + add > 77)
+ {
+ a_r += "\n ";
+ pos = 1;
+ }
+ a_r += n;
+ a_r += p;
+ }
+ }
+ a_r += "\n";
+ return a_r;
+}
+
+// Initialize the guarded part of cme
+static std::string
+a_r_cme_substring(int a_r_guarded, const std::string& cme_name)
+{
+ std::string cme_guarded;
+ size_t pos = std::string::npos;
+ size_t const l = cme_name.size();
+ size_t u = a_r_guarded;
+ while (u > 0)
+ {
+ pos = cme_name.find_last_of('.', pos);
+ if (pos < --u || pos == std::string::npos)
+ {
+ cme_guarded = cme_name;
+ pos = std::string::npos;
+ break;
+ }
+ --pos;
+ }
+ if (l > 1 && pos < l - 1)
+ cme_guarded = cme_name.substr(pos + 2);
+
+ return cme_guarded;
+}
+
+// Check an A-R, based on its first token --the authserv-id.
+static int
+a_r_is_guarded(const std::string& header, const std::string& cme_guarded)
+{
+ int it_is = 0;
+ struct rfc822t *rfc = rfc822t_alloc_new(header.c_str(), NULL, NULL);
+ // todo: log if error
+ if (rfc)
+ {
+ const rfc822t& r = *rfc;
+ std::string id;
+
+ /*
+ * This is similar in syntax to a fully-qualified domain name.
+ * [...]
+ * Moreover, some implementations have considered appending a
+ * delimiter such as "/" and following it with useful transport
+ * tracing data such as the queue ID or a timestamp.
+ * http://tools.ietf.org/html/rfc5451
+ */
+
+ for (int n = 0; n < r.ntokens; ++n)
+ {
+ const rfc822token& t = r.tokens[n];
+ if (t.token == 0 || t.token == '.')
+ id += std::string(t.ptr, t.len);
+ else if (t.token != '(')
+ break;
+ }
+
+ it_is = id.size() >= cme_guarded.size() &&
+ strcasecmp(id.c_str() + id.size() - cme_guarded.size(),
+ cme_guarded.c_str()) == 0;
+
+ rfc822t_free(rfc);
+ }
+ return it_is;
+}
+
static void getrcpts(struct rw_info *rwi)
{
struct mailfrominfo *mf=(struct mailfrominfo *)rwi->udata;
@@ -1564,6 +1686,7 @@
std::string header, headername, headernameorig;
std::string line;
std::string accumulated_errmsg;
+ std::string cme_name = config_me(), cme_guarded;
int has_msgid=0;
int has_date=0;
int has_tocc=0;
@@ -1571,6 +1694,7 @@
size_t headerlimit=100000;
std::string::iterator line_iter;
int noaddrrewrite=0;
+ int a_r_guarded=-1;
const char *p;
p=getenv("NOADDRREWRITE");
@@ -1580,6 +1704,14 @@
noaddrrewrite=atoi(p);
}
+ p=getenv("BOFHARGUARDATOMS");
+
+ if (p)
+ {
+ if ((a_r_guarded=atoi(p)) >= 0)
+ cme_guarded=a_r_cme_substring(a_r_guarded, cme_name);
+ }
+
p=getenv("BOFHHEADERLIMIT");
if (p)
@@ -1592,6 +1724,23 @@
}
my_rcptinfo.submitfile.MessageStart();
+
+ /*
+ * On the presumption that internal MTAs are fully compliant with
+ * Section 3.6 of [MAIL], and the compliant internal MTAs are using
+ * their own host names or the ADMD's DNS domain name as the
+ * "authserv-id" token, the header field proposed here should always
+ * appear above a Received: header added by a trusted MTA. This can
+ * be used as a test for header field validity.
+ * http://tools.ietf.org/html/rfc5451
+ */
+ line=a_r_from_env();
+ if (line.size())
+ {
+ // todo: check rfc2047_encode?
+ my_rcptinfo.submitfile.Message(line.c_str());
+ }
+
line="Received: from ";
for (p=strchr(receivedfrommta, ';'); *++p == ' '; )
@@ -1607,7 +1756,7 @@
}
line += "\n by ";
- line += config_me();
+ line += cme_name;
line += " with ";
@@ -1898,18 +2047,26 @@
// Quote Return-Path:'s at this point
- if (headername == "return-path")
+ else if (headername == "return-path")
headernameorig="Old-"+headernameorig;
// Rename Received-SPF only if own SPF checking is done
- if (headername == "received-spf" &&
+ else if (headername == "received-spf" &&
(mf->helohost.size() > 0) &&
!(bofh_checkspf("BOFHSPFHELO", "off", "off") &&
bofh_checkspf("BOFHSPFMAILFROM", "off", "off") &&
bofh_checkspf("BOFHSPFFROM", "off", "off")))
headernameorig="Old-"+headernameorig;
+ // Quote A-R if the auth server-id is guarded
+
+ else if (a_r_guarded >= 0 &&
+ headername == "authentication-results" &&
+ a_r_is_guarded(header, cme_guarded))
+ headernameorig="Old-"+headernameorig;
+
+
my_rcptinfo.submitfile.Message(headernameorig.c_str());
my_rcptinfo.submitfile.Message(":");
my_rcptinfo.submitfile.Message(header.c_str());
--- courier/module.esmtp/esmtpd.original.in 2011-04-04 15:01:20.000000000
+0200
+++ courier/module.esmtp/esmtpd.in 2013-01-23 15:22:46.000000000 +0100
@@ -81,5 +81,5 @@
echo ${sbindir}/couriertcpd $TCPDOPTS $PORT \
${sbindir}/courieresmtpd'>/dev/null 2>&1 </dev/null'
-) | @SETENV@ -i @SHELL@
+) | @SETENV@ -i WHITELISTS="$WHITELISTS" @SHELL@
exit 0
--- tcpd/tcpd.original.c 2012-06-22 06:56:28.000000000 +0200
+++ tcpd/tcpd.c 2013-01-23 15:23:23.000000000 +0100
@@ -52,6 +52,11 @@
#include <netdb.h>
+#if HAVE_PCRE_H
+#include <pcre.h>
+#else
+#include <pcre/pcre.h>
+#endif
static const char *accessarg=0;
static const char *accesslocal=0;
@@ -78,6 +83,7 @@
static char *lockfilename;
static void setup_block(const char *);
+static void setup_allow(const char *);
static struct args arginfo[]={
{"access", &accessarg},
@@ -86,6 +92,7 @@
{"drop", &droparg},
{"address", &ipaddrarg},
{"block", 0, setup_block},
+/* :~? {"allow", "WHITELISTS", setup_allow}, */
{"group", &grouparg},
{"listen", &listenarg},
{"maxperc", &maxpercarg},
@@ -135,6 +142,31 @@
extern void closeaccess();
extern char *chkaccess(const char *);
+static const char allowlist_default_variable[] = "BLOCK";
+
+typedef struct allow_varspec {
+ pcre* code;
+ union
+ {
+ pcre_extra *extra;
+ char *fixed;
+ } u;
+ char *var;
+ int capturecount;
+ int flag;
+} allow_varspec;
+
+enum allow_flag
+{allow_flag_default, allow_flag_a, allow_flag_txt, allow_flag_rrmask,
+ allow_flag_ifmatch = 4, allow_flag_bad = 8};
+
+static struct allowlist_s {
+ struct allowlist_s *next;
+ char *zone;
+ size_t count; /* number of allow_varspec */
+ allow_varspec v[1]; /* one or more */
+ } *allowlist=0;
+
static void setup_block(const char *blockinfo)
{
struct blocklist_s *newbl=(struct blocklist_s *)malloc(sizeof(*blocklist));
@@ -170,6 +202,227 @@
}
}
+#if 0
+static void free_allow(void)
+{
+ struct allowlist_s *al = allowlist;
+ allowlist = NULL;
+ while (al)
+ {
+ struct allowlist_s *next = al->next;
+ size_t c;
+ free(al->zone);
+ for (c = 0; c < al->count; ++c)
+ {
+ if (al->v[c].var != (char*)allowlist_default_variable)
+ free(al->v[c].var);
+ if (al->v[c].code)
+ {
+ pcre_free(al->v[c].code);
+ if (al->v[c].u.extra)
+ pcre_free(al->v[c].u.extra);
+ }
+ else if (al->v[c].u.fixed)
+ free(al->v[c].u.fixed);
+ }
+ free(al);
+ al = next;
+ }
+}
+#endif
+
+static char *set_allow_varspec(allow_varspec *v, char *var)
+{
+ char *p = var, *q, *s;
+ int ch, opt = PCRE_CASELESS, flag = 0;
+
+ /* skip var name */
+ while (isalnum(ch = *(unsigned char*)p) || ch == '_')
+ ++p;
+
+ if (v && p > var)
+ {
+ *p = 0;
+ if ((v->var = strdup(var)) == NULL)
+ flag = -1;
+ *p = ch;
+ }
+
+ /* fixed value? */
+ s = p;
+ if (p > var && (ch == '=' || ch == ':') && (*(unsigned char*)++s != 0))
+ {
+ if (ch == ':')
+ flag |= allow_flag_ifmatch;
+ if ((p = strchr(s, ',')) == NULL)
+ p = s + strlen(s);
+ ch = *(unsigned char*)p;
+ if (v && p > s)
+ {
+ *p = 0;
+ if ((v->u.fixed = strdup(s)) == NULL)
+ flag = -1;
+ *p = ch;
+ }
+ }
+
+ if (flag < 0)
+ {
+ perror("strdup");
+ exit(1);
+ }
+
+ /* no regex? */
+ if (ch == 0 || ch == ',')
+ {
+ if (v)
+ v->flag = flag;
+ return p;
+ }
+
+ /* ch is the delimiter */
+ if (strchr("/|%", ch) == NULL || (q = strchr(p + 1, ch)) == NULL)
+ {
+ fprintf(stderr, "Invalid delimiter in regex: %s\n", var);
+ return NULL;
+ }
+
+ /* options */
+ s = &q[1];
+ if (*(unsigned char*)s == ':')
+ {
+ int o;
+ while ((o = *(unsigned char*)++s) != 0 && o != ',')
+ {
+ switch (o)
+ {
+ case 'D': opt = 0; break;
+ case 'a': flag |= allow_flag_a; break;
+ case 'i': opt = PCRE_CASELESS; break;
+ case 't': flag |= allow_flag_txt; break;
+ default:
+ {
+ flag = allow_flag_bad;
+ fprintf(stderr,
+ "Invalid options for allow
%s\n",
+ var);
+ return NULL;
+ }
+ }
+ }
+ }
+
+ if (v)
+ {
+ static const char inv_al_r[] = "Invalid allow regex for";
+ const char *error;
+ int err_off;
+
+ *q = 0;
+ opt |= PCRE_NO_AUTO_CAPTURE; /* captured substrings be named */
+ v->code = pcre_compile(p + 1, opt, &error, &err_off, NULL);
+ if (v->code == NULL)
+ {
+ fprintf(stderr, "%s %s: %s \"%.*s<<ABOUT HERE>>%s\"\n",
+ inv_al_r,
+ v->var,
+ error, err_off, p + 1, p + err_off + 1);
+ flag = allow_flag_bad;
+ }
+ else
+ {
+ v->u.extra = pcre_study(v->code, 0, &error);
+ if (v->u.extra == NULL && error)
+ {
+ fprintf(stderr,
+ "%s %s: %s gets %s\n",
+ inv_al_r, v->var, p + 1, error);
+ flag = allow_flag_bad;
+ }
+
+ if (pcre_fullinfo(v->code, v->u.extra,
+ PCRE_INFO_CAPTURECOUNT, &v->capturecount))
+ {
+ fprintf(stderr,
+ "%s %s: %s gets bad capturecount\n",
+ inv_al_r, v->var, p + 1);
+ flag = allow_flag_bad;
+ }
+ }
+ *q = ch;
+ if (flag < allow_flag_bad && (flag & allow_flag_rrmask) == 0)
+ flag |= allow_flag_a;
+ v->flag = flag;
+ }
+
+ return s;
+}
+
+static void setup_allow(char const *allowinfo)
+{
+ struct allowlist_s *newal;
+ struct allowlist_s **alptr;
+ char *zone = strdup(allowinfo), *p = zone, *q;
+ int ch = 0;
+ size_t count = 0, size;
+
+ /* zone name */
+ if (p)
+ while (isalnum(ch = *(unsigned char*)p) ||
+ (ch && strchr("-.", ch)))
+ ++p;
+
+ /* count variables */
+ q = p;
+ while (ch == ',')
+ {
+ ++count;
+ q = set_allow_varspec(NULL, q + 1);
+ ch = q? *(unsigned char*)q: 0;
+ }
+
+ /* new linked list entry */
+ size = (count? count - 1: 0) * sizeof(allow_varspec) + sizeof
*allowlist;
+ newal = (struct allowlist_s*)malloc(size);
+ if (newal == NULL || zone == NULL)
+ {
+ perror("malloc");
+ exit(1);
+ }
+
+ if (ch != 0 || zone == p)
+ {
+ free(zone);
+ free(newal);
+ fprintf(stderr, "Invalid allow: %s\n", allowinfo);
+ return;
+ }
+
+ /* init entry and variables */
+ memset(newal, 0, size);
+ for (alptr = &allowlist; *alptr; alptr = &(*alptr)->next)
+ ;
+ *alptr = newal;
+
+ if (count == 0)
+ {
+ newal->v[0].var = (char*) allowlist_default_variable;
+ newal->v[0].flag = allow_flag_a | allow_flag_ifmatch;
+ newal->count = 1;
+ }
+ else
+ {
+ size_t c;
+ q = p;
+ for (c = 0; c < count; ++c)
+ q = set_allow_varspec(&newal->v[c], q + 1);
+ newal->count = count;
+ }
+
+ *p = 0;
+ newal->zone = realloc(zone, 1 + p - zone);
+}
+
static int isid(const char *p)
{
while (*p)
@@ -515,6 +768,12 @@
int lockfd=-1;
argn=argparse(argc, argv, arginfo);
+ if (/* not modified */ argparse)
+ {
+ char *allow=getenv("WHITELISTS");
+ if (allow && strncmp(allow, "-allow=", 7) == 0)
+ setup_allow(s + 7);
+ }
if ((stoparg || restartarg) && pidarg == 0)
{
@@ -1411,6 +1670,207 @@
}
/*
+* matched_allowlist is called when a match is found. It adds to the
environment
+* the var, if given, and any additional captured substrings.
+*
+* The name of captured substrings is appended to the var name, if given, adding
+* an underscore as needed.
+*/
+
+static void
+matched_allowlist(int match, allow_varspec *v, char *buf, int *ovector)
+{
+ int namecount, esize;
+ char *nametable;
+
+ if (v->var)
+ {
+ int s = ovector[0], e = ovector[1], ch = *(unsigned
char*)&buf[e];
+ buf[e] = 0;
+ mysetenv(v->var, &buf[s]);
+ buf[e] = ch;
+ }
+
+ if (pcre_fullinfo(v->code, v->u.extra, PCRE_INFO_NAMECOUNT, &namecount)
== 0 &&
+ pcre_fullinfo(v->code, v->u.extra, PCRE_INFO_NAMEENTRYSIZE,
&esize) == 0 &&
+ pcre_fullinfo(v->code, v->u.extra, PCRE_INFO_NAMETABLE,
&nametable) == 0)
+ {
+ size_t vlen = v->var? strlen(v->var): 0;
+ int add_u = vlen? v->var[vlen-1] == '_': 0, i;
+ for (i = 0; i < namecount; ++i)
+ {
+ int n = *(unsigned char*)nametable * 256 +
+ *(unsigned char*)&nametable[1], s, ndx;
+ if (n < match && (s = ovector[ndx = n + n]) >= 0)
+ {
+ int e = ovector[ndx + 1];
+ char *name = nametable + 2;
+ int add_u2 = add_u == 0 && name[0] != '_';
+ size_t nlen = vlen + strlen(name) + add_u2;
+ if (e > s)
+ {
+ int plen = e - s;
+ char *full = malloc(nlen + plen + 2);
+ if (full)
+ {
+ sprintf(full, "%s%s%s%c%.*s",
+ v->var? v->var: "",
+ add_u2? "_": "",
+ name,
+ 0,
+ plen,
+ &buf[s]);
+ if (getenv(full))
+ free(full);
+ else
+ {
+ full[nlen] = '=';
+ putenv(full);
+ }
+ }
+ }
+ }
+ nametable += esize;
+ }
+ }
+ else
+ perror("pcre_fullinfo");
+}
+
+
+/*
+** check_allowlist is called once for each allowlist query to process.
+*/
+
+static void docheckallowlist(struct allowlist_s *al, const char *nameptr)
+{
+ char hostname[RFC1035_MAXNAMESIZE+1];
+ char buf[RFC1035_MAXNAMESIZE+1];
+ struct rfc1035_reply *rr;
+ struct rfc1035_res res;
+ int flag = 0, ovecsize = 0, *ovector = NULL, found, rr_size, i, match;
+ size_t c;
+
+ for (c = 0; c < al->count; ++c)
+ {
+ allow_varspec *v = &al->v[c];
+
+ if (v->var && getenv(v->var))
+ continue;
+
+ if (v->flag < allow_flag_bad)
+ {
+ flag |= (v->flag & allow_flag_rrmask);
+ if (ovecsize < v->capturecount)
+ ovecsize = v->capturecount;
+ }
+ }
+
+ /* All variables that can be set are already there */
+ if (flag == 0)
+ return;
+
+ hostname[0]=0;
+ strncat(hostname, nameptr, RFC1035_MAXNAMESIZE);
+
+ rfc1035_init_resolv(&res);
+ found = rfc1035_resolve_cname(&res, hostname,
+ flag == allow_flag_a? RFC1035_TYPE_A:
+ flag == allow_flag_txt? RFC1035_TYPE_TXT: RFC1035_TYPE_ANY,
+ RFC1035_CLASS_IN, &rr, 0);
+ if (rr == NULL)
+ {
+ rfc1035_destroy_resolv(&res);
+ return;
+ }
+
+ rr_size = rr->ancount + rr->nscount + rr->arcount;
+ if (found < 0)
+ {
+ for (i = 0; i < rr_size; ++i)
+ {
+ unsigned type;
+ rfc1035_replyhostname(rr, rr->allrrs[i]->rrname, buf);
+ if (rfc1035_hostnamecmp(buf, hostname))
+ continue;
+
+ if ((type = rr->allrrs[i]->rrtype) == RFC1035_TYPE_A ||
+ type == RFC1035_TYPE_TXT)
+ {
+ found = i;
+ break;
+ }
+ }
+ }
+
+ match = 0;
+
+ ovecsize = (ovecsize + 2)*3;
+ if ((ovector = (int*)malloc(ovecsize * sizeof(int*))) == NULL)
+ ovecsize = 0;
+
+ for (c = 0; c < al->count; ++c)
+ {
+ allow_varspec *v = &al->v[c];
+
+ if (v->flag >= allow_flag_bad ||
+ (v->var && getenv(v->var)))
+ continue;
+ /*
+ * Fixed var
+ */
+ if (v->code == NULL)
+ {
+ if (v->var == NULL ||
+ ((v->flag & allow_flag_ifmatch) != 0 &&
+ (found < 0 || match < 0)))
+ continue;
+
+ mysetenv(v->var, v->u.fixed? v->u.fixed: "");
+ continue;
+ }
+
+ if (found < 0)
+ continue;
+ /*
+ * Regex: May match any relevant record (TXT or A).
+ *
+ * A non-match of a standalone regex (i.e. one w/o var)
+ * prevents subsequent ifmatch variables from being set.
+ */
+ for (i = found; i < rr_size; ++i)
+ {
+ unsigned type;
+ rfc1035_replyhostname(rr, rr->allrrs[i]->rrname, buf);
+ if (rfc1035_hostnamecmp(buf, hostname))
+ continue;
+
+ if ((type = rr->allrrs[i]->rrtype) == RFC1035_TYPE_A &&
+ (v->flag & allow_flag_a) != 0)
+
rfc1035_ntoa_ipv4(&rr->allrrs[i]->rr.inaddr, buf);
+ else if (type == RFC1035_TYPE_TXT &&
+ (v->flag & allow_flag_txt) != 0)
+ rfc1035_rr_gettxt(rr->allrrs[i], 0,
buf);
+ else
+ continue;
+
+ match = pcre_exec(v->code, v->u.extra, buf,
+ strlen(buf), 0, 0, ovector, ovecsize);
+ /* match would be 0 if ovecsize were wrong */
+
+ if (match < 0 && v->var)
+ match = 0;
+ else if (match > 0)
+ matched_allowlist(match, v, buf, ovector);
+ }
+ }
+
+ free(ovector);
+ rfc1035_replyfree(rr);
+ rfc1035_destroy_resolv(&res);
+}
+
+/*
** check_blocklist is called once for each blocklist query to process.
*/
@@ -1566,24 +2026,28 @@
rfc1035_destroy_resolv(&res);
}
-static void check_blocklist_ipv4(struct blocklist_s *p,
- const struct in_addr *ia)
-{
-unsigned a,b,c,d;
-char hostname[RFC1035_MAXNAMESIZE+1];
-const unsigned char *q=(const unsigned char *)ia;
-
- /* Calculate DNS query hostname */
-
- a=q[0];
- b=q[1];
- c=q[2];
- d=q[3];
-
- sprintf(hostname, "%u.%u.%u.%u.%s", d, c, b, a, p->zone);
- docheckblocklist(p, hostname);
+#define check_Xlist_ipv4(X)\
+static void check_##allow##list_ipv4(struct X##list_s *p,\
+ const struct in_addr *ia)\
+{\
+unsigned a,b,c,d;\
+char hostname[RFC1035_MAXNAMESIZE+1];\
+const unsigned char *q=(const unsigned char *)ia;\
+\
+ /* Calculate DNS query hostname */\
+\
+ a=q[0];\
+ b=q[1];\
+ c=q[2];\
+ d=q[3];\
+\
+ snprintf(hostname, sizeof hostname, "%u.%u.%u.%u.%s", d, c, b, a,
p->zone);\
+ docheck##X##list(p, hostname);\
}
+check_Xlist_ipv4(block)
+check_Xlist_ipv4(allow)
+
#if RFC1035_IPV6
static void check_blocklist(struct blocklist_s *p, const RFC1035_ADDR *ia)
@@ -1597,11 +2061,27 @@
}
}
+static void check_allowlist(struct allowlist_s *p, const RFC1035_ADDR *ia)
+{
+ if (IN6_IS_ADDR_V4MAPPED(ia))
+ {
+ struct in_addr ia4;
+
+ memcpy(&ia4, (const char *)ia + 12, 4);
+ check_allowlist_ipv4(p, &ia4);
+ }
+}
+
#else
static void check_blocklist(struct blocklist_s *p, const RFC1035_ADDR *ia)
{
check_blocklist_ipv4(p, ia);
}
+
+static void check_allowlist(struct allowlist_s *p, const RFC1035_ADDR *ia)
+{
+ check_allowlist_ipv4(p, ia);
+}
#endif
static void check_drop(int sockfd)
@@ -1652,6 +2132,18 @@
static void proxy();
+static void check_lists(const RFC1035_ADDR *addr)
+{
+ struct allowlist_s *al;
+ struct blocklist_s *bl;
+
+ for (al = allowlist; al; al = al->next)
+ check_allowlist(al, addr);
+
+ for (bl=blocklist; bl; bl=bl->next)
+ check_blocklist(bl, addr);
+}
+
static void run(int fd, const RFC1035_ADDR *addr, int addrport,
const char *prog, char **argv)
{
@@ -1662,7 +2154,6 @@
socklen_t i;
int ipcnt, ccnt;
char buf[RFC1035_MAXNAMESIZE+128];
-struct blocklist_s *bl;
const char *remoteinfo;
const char *p;
@@ -1739,8 +2230,7 @@
mysetenv("TCPLOCALPORT", buf);
ip2host(&laddr, "TCPLOCALHOST");
- for (bl=blocklist; bl; bl=bl->next)
- check_blocklist(bl, addr);
+ check_lists(addr);
check_drop(fd);
sox_close(0);
------------------------------------------------------------------------------
Master Visual Studio, SharePoint, SQL, ASP.NET, C# 2012, HTML5, CSS,
MVC, Windows 8 Apps, JavaScript and much more. Keep your skills current
with LearnDevNow - 3,200 step-by-step video tutorials by Microsoft
MVPs and experts. ON SALE this month only -- learn more at:
http://p.sf.net/sfu/learnnow-d2d
_______________________________________________
courier-users mailing list
courier-users@lists.sourceforge.net
Unsubscribe: https://lists.sourceforge.net/lists/listinfo/courier-users