/*
** Copyright (c) 2007 Sendmail, Inc. and its suppliers.
** All rights reserved.
*/
#ifndef lint
static char dkim_policy_c_id[] = "@(#)$Id: dkim-policy.c,v 1.23 2007/11/02 20:15:24 msk Exp $";
#endif /* !lint */
/* system includes */
#include <sys/param.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <netdb.h>
#include <resolv.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include <ctype.h>
/* libsm includes */
#include <sm/string.h>
/* libar includes */
#if USE_ARLIB
# include <ar.h>
#endif /* USE_ARLIB */
/* libdkim includes */
#include "dkim.h"
#include "dkim-types.h"
#include "dkim-policy.h"
#ifdef QUERY_CACHE
# include "dkim-cache.h"
#endif /* QUERY_CACHE */
/* prototypes */
extern void dkim_error __P((DKIM *, const char *, ...));
/* local definitions needed for DNS queries */
#define MAXPACKET 8192
#if defined(__RES) && (__RES >= 19940415)
# define RES_UNC_T char *
#else /* __RES && __RES >= 19940415 */
# define RES_UNC_T unsigned char *
#endif /* __RES && __RES >= 19940415 */
/*
** DKIM_GET_POLICY_FILE -- acquire a domain's policy record using a local file
**
** Parameters:
** dkim -- DKIM handle
** query -- query to execute
** buf -- buffer into which to write policy
** buflen -- number of bytes available at "buf"
** qstatus -- query result code (DNS-style)
**
** Return value:
** 1 -- query completed, answer returned in "buf"
** 0 -- query completed, no answer available
** -1 -- failure
*/
int
dkim_get_policy_file(DKIM *dkim, unsigned char *query, unsigned char *buf,
size_t buflen, int *qstatus)
{
bool found;
int n;
char *path;
unsigned char *p;
FILE *f;
unsigned char inbuf[BUFRSZ + 1];
assert(dkim != NULL);
assert(query != NULL);
assert(buf != NULL);
assert(qstatus != NULL);
path = dkim->dkim_libhandle->dkiml_queryinfo;
f = fopen(path, "r");
if (f == NULL)
{
dkim_error(dkim, "%s: fopen(): %s", path,
strerror(errno));
return -1;
}
n = strlen(query);
memset(inbuf, '\0', sizeof inbuf);
found = FALSE;
while (!found && fgets(inbuf, sizeof inbuf - 1, f) != NULL)
{
for (p = inbuf; *p != '\0'; p++)
{
if (*p == '\n' || *p == '#')
{
*p = '\0';
break;
}
}
/* is this a match? */
if (strncasecmp(inbuf, query, n) == 0 &&
isascii(inbuf[n]) && isspace(inbuf[n]))
{
found = TRUE;
/* move past spaces */
for (p = &inbuf[n] + 1;
isascii(*p) && isspace(*p);
p++)
continue;
sm_strlcpy(buf, p, buflen);
*qstatus = NOERROR;
fclose(f);
return 1;
}
}
if (ferror(f))
{
dkim_error(dkim, "%s: fgets(): %s", path, strerror(errno));
fclose(f);
return -1;
}
fclose(f);
*qstatus = NXDOMAIN;
return 0;
}
/*
** DKIM_GET_POLICY_DNS -- acquire a domain's policy record using DNS queries
**
** Parameters:
** dkim -- DKIM handle
** query -- query to execute
** usemx -- use an MX record?
** buf -- buffer into which to write policy
** buflen -- number of bytes available at "buf"
** qstatus -- query result code (DNS-style)
**
** Return value:
** 1 -- policy retrieved, stored in buffer
** 0 -- no policy found
** -1 -- failure
*/
int
dkim_get_policy_dns(DKIM *dkim, unsigned char *query, bool usemx,
unsigned char *buf, size_t buflen, int *qstatus)
{
int qdcount;
int ancount;
int status;
int n;
int c;
int qtype;
int type = -1;
int class = -1;
#ifdef QUERY_CACHE
int ttl;
#endif /* QUERY_CACHE */
size_t anslen;
#if USE_ARLIB
AR_LIB ar;
AR_QUERY q;
int arerror;
#endif /* USE_ARLIB */
DKIM_LIB *lib;
unsigned char *p;
unsigned char *cp;
unsigned char *eom;
unsigned char ansbuf[MAXPACKET];
unsigned char namebuf[DKIM_MAXHOSTNAMELEN + 1];
unsigned char outbuf[BUFRSZ + 1];
#if USE_ARLIB
struct timeval timeout;
#endif /* USE_ARLIB */
HEADER hdr;
assert(dkim != NULL);
assert(query != NULL);
assert(buf != NULL);
assert(qstatus != NULL);
lib = dkim->dkim_libhandle;
#ifdef QUERY_CACHE
if (lib->dkiml_cache != NULL)
{
int err = 0;
size_t blen = buflen;
dkim->dkim_cache_queries++;
status = dkim_cache_query(lib->dkiml_cache, query, 0,
buf, &blen, &err);
if (status == 0)
{
dkim->dkim_cache_hits++;
return (status == DKIM_STAT_OK ? 0 : -1);
}
/* XXX -- do something with errors here */
}
#endif /* QUERY_CACHE */
qtype = (usemx ? T_MX : T_TXT);
#if USE_ARLIB
# ifdef _FFR_DNS_UPGRADE
for (c = 0; c < 2; c++)
{
switch (c)
{
case 0:
ar = dkim->dkim_libhandle->dkiml_arlib;
break;
case 1:
ar = dkim->dkim_libhandle->dkiml_arlibtcp;
break;
}
timeout.tv_sec = dkim->dkim_timeout;
timeout.tv_usec = 0;
q = ar_addquery(ar, query, C_IN, qtype, MAXCNAMEDEPTH, ansbuf,
sizeof ansbuf, &arerror,
dkim->dkim_timeout == 0 ? NULL : &timeout);
if (q == NULL)
{
dkim_error(dkim, "ar_addquery() for `%s' failed",
query);
return -1;
}
if (lib->dkiml_dns_callback == NULL)
{
status = ar_waitreply(ar, q, NULL, NULL);
}
else
{
for (;;)
{
timeout.tv_sec = lib->dkiml_callback_int;
timeout.tv_usec = 0;
status = ar_waitreply(ar, q, NULL, &timeout);
if (status != AR_STAT_NOREPLY)
break;
lib->dkiml_dns_callback(dkim->dkim_user_context);
}
}
(void) ar_cancelquery(ar, q);
/* see if the UDP reply was truncated */
if (c == 0 && status == AR_STAT_SUCCESS)
{
memcpy(&hdr, ansbuf, sizeof hdr);
if (hdr.tc)
continue;
}
break;
}
# else /* _FFR_DNS_UPGRADE */
ar = dkim->dkim_libhandle->dkiml_arlib;
timeout.tv_sec = dkim->dkim_timeout;
timeout.tv_usec = 0;
q = ar_addquery(ar, query, C_IN, qtype, MAXCNAMEDEPTH, ansbuf,
sizeof ansbuf, &arerror,
dkim->dkim_timeout == 0 ? NULL : &timeout);
if (q == NULL)
{
dkim_error(dkim, "ar_addquery() failed for `%s'", query);
return -1;
}
if (lib->dkiml_dns_callback == NULL)
{
status = ar_waitreply(ar, q, NULL, NULL);
}
else
{
for (;;)
{
timeout.tv_sec = lib->dkiml_callback_int;
timeout.tv_usec = 0;
status = ar_waitreply(ar, q, NULL, &timeout);
if (status != AR_STAT_NOREPLY)
break;
lib->dkiml_dns_callback(dkim->dkim_user_context);
}
}
(void) ar_cancelquery(ar, q);
# endif /* _FFR_DNS_UPGRADE */
#else /* USE_ARLIB */
status = res_query(query, C_IN, qtype, ansbuf, sizeof ansbuf);
#endif /* USE_ARLIB */
#if USE_ARLIB
if (status == AR_STAT_ERROR || status == AR_STAT_EXPIRED)
{
dkim_error(dkim, "ar_waitreply(): `%s' query %s",
query,
status == AR_STAT_ERROR ? "error" : "expired");
if (status == AR_STAT_EXPIRED)
{
*qstatus = SERVFAIL;
return 0;
}
else
{
return -1;
}
}
#else /* USE_ARLIB */
/*
** A -1 return from res_query could mean a bunch of things,
** not just NXDOMAIN. You can use h_errno to determine what
** -1 means. This is poorly documented.
*/
if (status == -1)
{
switch (h_errno)
{
case HOST_NOT_FOUND:
*qstatus = NXDOMAIN;
return 0;
case NO_DATA:
*qstatus = NOERROR;
return 0;
case TRY_AGAIN:
case NO_RECOVERY:
default:
dkim_error(dkim, "res_query(): `%s' %s",
query, hstrerror(h_errno));
*qstatus = SERVFAIL;
return 0;
}
}
#endif /* USE_ARLIB */
/* set up pointers */
anslen = sizeof ansbuf;
memcpy(&hdr, ansbuf, sizeof hdr);
cp = (u_char *) &ansbuf + HFIXEDSZ;
eom = (u_char *) &ansbuf + anslen;
/* skip over the name at the front of the answer */
for (qdcount = ntohs((unsigned short) hdr.qdcount);
qdcount > 0;
qdcount--)
{
/* copy it first */
(void) dn_expand((unsigned char *) &ansbuf, eom, cp,
namebuf, sizeof namebuf);
if ((n = dn_skipname(cp, eom)) < 0)
{
dkim_error(dkim, "`%s' reply corrupt", query);
return -1;
}
cp += n;
/* extract the type and class */
if (cp + INT16SZ + INT16SZ > eom)
{
dkim_error(dkim, "`%s' reply corrupt", query);
return -1;
}
GETSHORT(type, cp);
GETSHORT(class, cp);
}
if (type != qtype || class != C_IN)
{
dkim_error(dkim, "`%s' unexpected reply class/type", query);
return -1;
}
/* if truncated, we can't do it */
if (hdr.tc)
{
dkim_error(dkim, "reply for `%s' truncated", query);
return -1;
}
/* if we got something other than NOERROR, just return it */
*qstatus = hdr.rcode;
if (hdr.rcode != NOERROR)
return 0;
/* get the answer count */
ancount = ntohs((unsigned short) hdr.ancount);
if (ancount == 0)
return 0;
/* walk through the answers looking for the right record */
while (--ancount >= 0 && cp < eom)
{
/* grab the label, even though we know what we asked... */
if ((n = dn_expand((unsigned char *) &ansbuf, eom, cp,
(RES_UNC_T) namebuf, sizeof namebuf)) < 0)
{
dkim_error(dkim, "`%s' reply corrupt", query);
return -1;
}
/* ...and move past it */
cp += n;
/* extract the type and class */
if (cp + INT16SZ + INT16SZ > eom)
{
dkim_error(dkim, "`%s' reply corrupt", query);
return -1;
}
GETSHORT(type, cp);
GETSHORT(class, cp);
/* handle a CNAME (skip it; assume it was resolved) */
if (type == T_CNAME)
{
char chost[DKIM_MAXHOSTNAMELEN + 1];
n = dn_expand((u_char *) &ansbuf, eom, cp,
chost, DKIM_MAXHOSTNAMELEN);
cp += n;
continue;
}
else if (type != qtype)
{
/* reject anything not valid (stupid wildcards) */
dkim_error(dkim, "`%s' unexpected reply class/type",
query);
return -1;
}
/* process it */
break;
}
if (ancount < 0)
{
dkim_error(dkim, "`%s' reply was unresolved CNAME", query);
return -1;
}
/*
** If we're querying something other than a T_TXT here, it was
** used by part of the policy algorithm merely to validate the
** existence of a domain; the payload isn't really required.
*/
if (usemx)
return 1;
#ifdef QUERY_CACHE
GETLONG(ttl, cp);
#else /* QUERY_CACHE */
/* skip the TTL */
cp += INT32SZ;
#endif /* QUERY_CACHE */
/* get payload length */
if (cp + INT16SZ > eom)
{
dkim_error(dkim, "`%s' reply corrupt", query);
return -1;
}
GETSHORT(n, cp);
/* XXX -- maybe deal with a partial reply rather than require it all */
if (cp + n > eom || n > BUFRSZ)
{
dkim_error(dkim, "`%s' reply corrupt", query);
return -1;
}
/* extract the payload */
memset(outbuf, '\0', sizeof outbuf);
p = outbuf;
while (n > 0)
{
c = *cp++;
n--;
while (c > 0)
{
*p++ = *cp++;
c--;
n--;
}
}
#ifdef QUERY_CACHE
if (dkim->dkim_libhandle->dkiml_cache != NULL)
{
int err = 0;
status = dkim_cache_insert(dkim->dkim_libhandle->dkiml_cache,
query, outbuf, ttl, &err);
/* XXX -- do something with errors here */
}
#endif /* QUERY_CACHE */
sm_strlcpy(buf, outbuf, buflen);
return 1;
}
syntax highlighted by Code2HTML, v. 0.9.1