/*
** Copyright (c) 2004-2007 Sendmail, Inc. and its suppliers.
** All rights reserved.
**
** $Id: dk.c,v 1.181 2007/05/31 02:05:39 msk Exp $
*/
#ifndef lint
static char dk_c_id[] = "@(#)$Id: dk.c,v 1.181 2007/05/31 02:05:39 msk Exp $";
#endif /* !lint */
/* system includes */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <ctype.h>
#include <syslog.h>
#include <pthread.h>
/* OpenSSL includes */
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/bio.h>
#include <openssl/err.h>
/* libdk includes */
#include <dk.h>
#include <dk-private.h>
#include <util.h>
/* libsm includes */
#include <sm/gen.h>
#include <sm/cdefs.h>
#include <sm/string.h>
/* libar includes */
#if USE_ARLIB
# include "ar.h"
#endif /* USE_ARLIB */
/* libdk includes */
#include "rfc2822.h"
/* useful stuff */
#ifndef NULL
# define NULL 0
#endif /* ! NULL */
#ifndef FALSE
# define FALSE 0
#endif /* ! FALSE */
#ifndef TRUE
# define TRUE 1
#endif /* ! TRUE */
#ifndef MAXHOSTNAMELEN
# define MAXHOSTNAMELEN 256
#endif /* ! MAXHOSTNAMELEN */
#ifndef __P
# ifdef __STDC__
# define __P(x) x
# else /* __STDC__ */
# define __P(x) ()
# endif /* __STDC__ */
#endif /* ! __P */
#define CRLF "\r\n" /* CRLF */
/* limits, macros, etc. */
#define BUFRSZ 1024 /* block I/O buffer size */
#define DEFERRLEN 128 /* default error buffer size */
#define DEFTIMEOUT 5 /* default DNS timeout */
#define MAXPARAMETER 256 /* biggest parameter we accept */
/* defaults */
#define DK_DEFAULT_SELECTOR "main" /* default selector (filename only) */
#define DK_DNSNAME "_domainkey"
/* common DNS label */
#define DK_TMPDIR "/var/tmp"
/* default dir. for temp files */
/* states */
#define DK_STATE_INIT 0 /* initialized */
#define DK_STATE_HEADER 1 /* receiving headers */
#define DK_STATE_EOH 2 /* at or past EOH */
#define DK_STATE_BODY 3 /* receiving body */
#define DK_STATE_EOM 4 /* at EOM */
/* files */
#define DK_PRIVATEKEY "/var/db/domainkeys/%s.key.pem"
/* template filename for private keys */
/* prototypes */
static void dk_error(DK *dk, const char *format, ...);
/* parameter lookup table */
struct lookup
{
char *str;
int code;
};
struct lookup params[] = {
{ "a", DK_PARAM_SIGNALG },
{ "b", DK_PARAM_SIGNATURE },
{ "c", DK_PARAM_CANONALG },
{ "d", DK_PARAM_DOMAIN },
{ "h", DK_PARAM_HDRLIST },
{ "q", DK_PARAM_QUERYMETHOD },
{ "s", DK_PARAM_SELECTOR },
{ NULL, -1 }
};
/* 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 */
/* base64 alphabet */
static unsigned char alphabet[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/* base64 decode stuff */
static int decoder[256] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0,
0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0,
0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
struct dk_lib
{
int dkl_options;
char * dkl_tmpdir;
void * (*dkl_malloc) (void *closure, size_t nbytes);
void (*dkl_free) (void *closure, void *p);
#if USE_ARLIB
AR_LIB dkl_arlib;
#endif /* USE_ARLIB */
};
/* other stuff */
#ifndef MIN
# define MIN(x,y) ((x) < (y) ? (x) : (y))
#endif /* ! MIN */
#define DK_DEBUG(x) (getenv("DKDEBUG") != NULL && \
strchr(getenv("DKDEBUG"), (x)) != NULL)
#define DK_DECODE_SIGNATURE 0
#define DK_DECODE_KEY 1
#define DK_CRLF_UNKNOWN (-1)
#define DK_CRLF_CRLF 0
#define DK_CRLF_LF 1
#define DK_MODE_UNKNOWN (-1)
#define DK_MODE_SIGN 0
#define DK_MODE_VERIFY 1
#define DK_FLAG_FREESIGNATURE 0001
/*
** ===== PRIVATE SECTION
*/
/*
** DK_MALLOC -- allocate some memory
**
** Parameters:
** libhandle -- DK_LIB handle
** closure -- closure to use
** nbytes -- how many bytes we want
**
** Return value:
** Pointer to new memory, or NULL if it failed.
*/
static void *
dk_malloc(DK_LIB *libhandle, void *closure, size_t nbytes)
{
assert(libhandle != NULL);
if (libhandle->dkl_malloc == NULL)
return malloc(nbytes);
else
return libhandle->dkl_malloc(closure, nbytes);
}
/*
** DK_MFREE -- free some memory
**
** Parameters:
** libhandle -- DK_LIB handle
** closure -- closure to use
** p -- pointer referring to memory to release
**
** Return value:
** None.
*/
static void
dk_mfree(DK_LIB *libhandle, void *closure, void *p)
{
assert(libhandle != NULL);
if (libhandle->dkl_free == NULL)
free(p);
else
libhandle->dkl_free(closure, p);
}
/*
** DK_STRDUP -- duplicate a string
**
** Parameters:
** libhandle -- DK_LIB handle
** closure -- closure to use
** str -- string to clone
**
** Return value:
** Pointer to copy of "str", or NULL on failure.
*/
static char *
dk_strdup(DK_LIB *libhandle, void *closure, char *str)
{
char *new;
size_t len;
assert(libhandle != NULL);
assert(str != NULL);
len = strlen(str) + 1;
new = dk_malloc(libhandle, closure, len);
if (new != NULL)
sm_strlcpy(new, str, len);
return new;
}
/*
** DK_ISFWS -- return TRUE iff the specified character is folding whitespace
**
** Parameters:
** c -- character
**
** Return value:
** TRUE iff the specified character is folding whitespace.
*/
static bool
dk_isfws(int c)
{
return (c == 0x09 || c == 0x0a || c == 0x0d || c == 0x20);
}
/*
** DK_DECODE -- decode a base64-encoded key or signature
**
** Parameters:
** dk -- DK handle
** what -- what to decode (DK_DECODE_KEY or DK_DECODE_SIGNATURE)
**
** Return value:
** TRUE on success, FALSE otherwise (malloc() failure).
*/
static bool
dk_decode(DK *dk, int what)
{
int n;
int bits = 0;
int char_count = 0;
size_t keyidx;
unsigned char *c;
unsigned char *x;
assert(dk != NULL);
assert(what == DK_DECODE_KEY || what == DK_DECODE_SIGNATURE);
if (what == DK_DECODE_KEY)
{
dk->dk_key = dk_malloc(dk->dk_libhandle, dk->dk_closure,
dk->dk_b64len);
if (dk->dk_key == NULL)
{
dk_error(dk, "unable to allocate %d byte(s)",
dk->dk_b64len);
return FALSE;
}
memset(dk->dk_key, '\0', dk->dk_b64len);
x = dk->dk_key;
}
else
{
dk->dk_signature = dk_malloc(dk->dk_libhandle, dk->dk_closure,
dk->dk_b64len);
if (dk->dk_signature == NULL)
{
dk_error(dk, "unable to allocate %d byte(s)",
dk->dk_b64len);
return FALSE;
}
memset(dk->dk_signature, '\0', dk->dk_b64len);
x = dk->dk_signature;
dk->dk_flags |= DK_FLAG_FREESIGNATURE;
}
keyidx = 0;
for (c = dk->dk_b64, n = 0;
n < dk->dk_b64len;
c++, n++)
{
/* end padding */
if (*c == '=')
break;
/* skip stuff not part of the base64 alphabet (RFC2045) */
if (!((*c >= 'A' && *c <= 'Z') ||
(*c >= 'a' && *c <= 'z') ||
(*c >= '0' && *c <= '9') ||
(*c == '+') ||
(*c == '/')))
continue;
/* everything else gets decoded */
bits += decoder[(int) *c];
char_count++;
if (char_count == 4)
{
x[keyidx++] = (bits >> 16);
x[keyidx++] = ((bits >> 8) & 0xff);
x[keyidx++] = (bits & 0xff);
bits = 0;
char_count = 0;
}
else
{
bits <<= 6;
}
}
/* XXX -- don't bother checking for proper termination (for now) */
/* process trailing data, if any */
switch (char_count)
{
case 0:
break;
case 1:
/* base64 decoding incomplete; at least two bits missing */
break;
case 2:
x[keyidx++] = (bits >> 10);
break;
case 3:
x[keyidx++] = (bits >> 16);
x[keyidx++] = ((bits >> 8) & 0xff);
break;
}
/* store the length of what was decoded */
if (what == DK_DECODE_KEY)
{
dk->dk_keylen = keyidx;
}
else
{
dk->dk_signlen = keyidx;
}
return TRUE;
}
/*
** DK_IN_USE -- see if a domain seems to be using DomainKeys
**
** Parameters:
** dk -- DK handle
**
** Return value:
** -1 -- error
** 0 -- domain does not appear to use DomainKeys
** 1 -- domain appears to use DomainKeys
*/
static int
dk_in_use(DK *dk)
{
int qdcount;
int ancount;
int status;
int n;
int c;
int type = -1;
int class = -1;
int state;
#if USE_ARLIB
int error;
#endif /* USE_ARLIB */
size_t plen = 0;
size_t anslen;
#if USE_ARLIB
AR_QUERY q;
#endif /* USE_ARLIB */
char *ssel;
unsigned char *p;
unsigned char *cp;
unsigned char *eom;
unsigned char ansbuf[MAXPACKET];
unsigned char buf[BUFRSZ + 1];
char qname[MAXHOSTNAMELEN + 1];
char tag[MAXPARAMETER + 1];
char value[MAXPARAMETER + 1];
#if USE_ARLIB
struct timeval timeout;
#endif /* USE_ARLIB */
HEADER hdr;
assert(dk != NULL);
ssel = dk_sterilize(dk->dk_domain);
if (ssel == NULL)
return -1;
/* send the NS query */
memset(qname, '\0', sizeof qname);
snprintf(qname, sizeof qname - 1, "%s.%s", DK_DNSNAME,
dk_sterilize(dk->dk_domain));
#if USE_ARLIB
timeout.tv_sec = dk->dk_timeout;
timeout.tv_usec = 0;
if (DK_DEBUG('r'))
{
syslog(LOG_INFO, "thread %p ar_addquery(`%s')", pthread_self(),
qname);
}
q = ar_addquery(dk->dk_libhandle->dkl_arlib, qname, C_IN, T_TXT,
MAXCNAMEDEPTH, ansbuf, sizeof ansbuf, &error,
dk->dk_timeout == 0 ? NULL : &timeout);
if (DK_DEBUG('r'))
{
syslog(LOG_INFO, "thread %p ar_addquery(`%s') done",
pthread_self(), qname);
}
if (q == NULL)
return -1;
status = ar_waitreply(dk->dk_libhandle->dkl_arlib, q, NULL, NULL);
(void) ar_cancelquery(dk->dk_libhandle->dkl_arlib, q);
if (DK_DEBUG('r'))
{
syslog(LOG_INFO, "thread %p ar_cancelquery(`%s')",
pthread_self(), qname);
}
#else /* USE_ARLIB */
status = res_query(qname, C_IN, T_TXT, ansbuf, sizeof ansbuf);
#endif /* USE_ARLIB */
#if USE_ARLIB
if (status == AR_STAT_ERROR || status == AR_STAT_EXPIRED)
{
if (DK_DEBUG('d') && dk->dk_id != NULL)
{
syslog(LOG_NOTICE, "%s: ar_waitreply(): %s", dk->dk_id,
ar_strerror(error));
}
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:
case NO_DATA:
return 0;
case TRY_AGAIN:
case NO_RECOVERY:
default:
return -1;
}
}
#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, qname,
sizeof qname);
if ((n = dn_skipname(cp, eom)) < 0)
return DK_STAT_INTERNAL;
cp += n;
/* extract the type and class */
if (cp + INT16SZ + INT16SZ > eom)
return -1;
GETSHORT(type, cp);
GETSHORT(class, cp);
}
if (type != T_TXT || class != C_IN)
return -1;
/* if NXDOMAIN, return DK_STAT_CANTVRFY */
if (hdr.rcode == NXDOMAIN)
return 0;
/* get the answer count */
ancount = ntohs((unsigned short) hdr.ancount);
if (ancount == 0)
return 0;
/* if truncated, we can't do it */
if (hdr.tc)
return -1;
/* grab the label, even though we know what we asked... */
if ((n = dn_expand((unsigned char *) &ansbuf, eom, cp,
(RES_UNC_T) qname, sizeof qname)) < 0)
return DK_STAT_INTERNAL;
/* ...and move past it */
cp += n;
/* extract the type and class */
if (cp + INT16SZ + INT16SZ > eom)
return -1;
GETSHORT(type, cp);
GETSHORT(class, cp);
/* reject anything that's not valid (stupid wildcards) */
if (type != T_TXT || class != C_IN)
return -1;
/* skip the TTL */
cp += INT32SZ;
/* get payload length */
if (cp + INT16SZ > eom)
return -1;
GETSHORT(n, cp);
/* XXX -- maybe deal with a partial reply rather than require it all */
if (cp + n > eom)
return -1;
/* extract the payload */
memset(buf, '\0', sizeof buf);
p = buf;
while (n > 0)
{
c = *cp++;
n--;
while (c > 0)
{
*p++ = *cp++;
c--;
n--;
}
}
state = 0;
for (p = buf; ; p++)
{
if (*p == '\0')
{
switch (state)
{
case 0:
case 4:
return 1;
case 1:
case 2:
/* malformed */
return -1;
case 3:
state = 4;
break;
}
}
switch (state)
{
case 0: /* before tag */
if (isascii(*p) && isspace(*p))
continue;
memset(tag, '\0', sizeof tag);
plen = 0;
state = 1;
/* FALLTHROUGH */
case 1: /* in tag */
if (*p == '=')
{
state = 2;
continue;
}
else if (isascii(*p) && isspace(*p))
{
continue;
}
else if (plen < sizeof tag)
{
tag[plen++] = *p;
continue;
}
/* FALLTHROUGH */
case 2: /* before value */
if (isascii(*p) && isspace(*p))
continue;
memset(value, '\0', sizeof value);
plen = 0;
state = 3;
/* FALLTHROUGH */
case 3: /* in value */
if (*p == ';')
{
state = 4;
/* FALLTHROUGH */
}
else if (isascii(*p) && isspace(*p))
{
continue;
}
else if (plen < sizeof tag)
{
value[plen++] = *p;
continue;
}
else
{
continue;
}
/* FALLTHROUGH */
case 4: /* after value */
if (strcasecmp(tag, "t") == 0)
{
if (value[0] == 'y')
dk->dk_testing = TRUE;
}
else if (strcasecmp(tag, "o") == 0)
{
if (value[0] == '-')
dk->dk_signall = TRUE;
}
else if (strcasecmp(tag, "n") == 0)
{
/* XXX -- other info? or something */
/* XXX -- yahoo-inc.com uses it, so tolerate */
/* XXX -- (i.e. ignore) it for now */
}
else if (strcasecmp(tag, "r") == 0)
{
sm_strlcpy(dk->dk_reportaddr, value,
sizeof dk->dk_reportaddr);
}
else
{
return -1;
}
state = 0;
break;
}
if (*p == '\0')
break;
}
return 1;
}
/*
** DK_GET_KEY -- acquire the key that will be used to either generate
** or verify a signature
**
** Parameters:
** dk -- DK handle
**
** Return value:
** A DK_STAT.
*/
static DK_STAT
dk_get_key(DK *dk)
{
bool key = FALSE;
int status;
int state;
int qdcount;
int ancount;
#if USE_ARLIB
int error;
#endif /* USE_ARLIB */
int n = 0;
int l;
int type = -1;
int class = -1;
int plen = 0;
size_t anslen;
size_t keyidx;
#if USE_ARLIB
AR_QUERY q;
#endif /* USE_ARLIB */
unsigned char *p;
unsigned char *cp;
unsigned char *eom;
char *ssel;
char qname[MAXHOSTNAMELEN + 1];
unsigned char ansbuf[MAXPACKET];
char tag[MAXPARAMETER + 1];
char value[MAXPARAMETER + 1];
#if USE_ARLIB
struct timeval timeout;
#endif /* USE_ARLIB */
HEADER hdr;
assert(dk != NULL);
if (dk->dk_mode != DK_MODE_VERIFY)
{
FILE *f;
size_t n;
struct stat s;
char fn[MAXPATHLEN + 1];
/* already got one? */
if (dk->dk_key != NULL)
return DK_STAT_OK;
ssel = dk_sterilize(dk->dk_selector);
if (ssel == NULL)
{
dk_error(dk, "unsafe selector data");
return DK_STAT_SYNTAX;
}
memset(fn, '\0', sizeof fn);
snprintf(fn, sizeof fn - 1, DK_PRIVATEKEY,
dk->dk_selector == NULL ? DK_DEFAULT_SELECTOR : ssel);
f = fopen(fn, "r");
if (f == NULL)
{
dk_error(dk, "%s: fopen(): %s", fn, strerror(errno));
return DK_STAT_INTERNAL;
}
if (fstat(fileno(f), &s) != 0)
{
dk_error(dk, "%s: fstat(): %s", fn, strerror(errno));
return DK_STAT_INTERNAL;
}
dk->dk_key = dk_malloc(dk->dk_libhandle, dk->dk_closure,
(size_t) s.st_size);
if (dk->dk_key == NULL)
{
dk_error(dk, "unable to allocate %d byte(s)",
s.st_size);
return DK_STAT_NORESOURCE;
}
dk->dk_keylen = s.st_size;
n = fread(dk->dk_key, (unsigned int) s.st_size, 1, f);
if (n != 1)
{
fclose(f);
dk_error(dk, "%s: fread(): %s", fn, strerror(errno));
return DK_STAT_INTERNAL;
}
fclose(f);
return DK_STAT_OK;
}
snprintf(qname, sizeof qname - 1, "%s.%s.%s", dk->dk_selector,
DK_DNSNAME, dk->dk_domain);
#if USE_ARLIB
timeout.tv_sec = dk->dk_timeout;
timeout.tv_usec = 0;
if (DK_DEBUG('r'))
{
syslog(LOG_INFO, "thread %p ar_addquery(`%s')", pthread_self(),
qname);
}
q = ar_addquery(dk->dk_libhandle->dkl_arlib, qname, C_IN, T_TXT,
MAXCNAMEDEPTH, ansbuf, sizeof ansbuf, &error,
dk->dk_timeout == 0 ? NULL : &timeout);
if (q == NULL)
return DK_STAT_INTERNAL;
status = ar_waitreply(dk->dk_libhandle->dkl_arlib, q, NULL, NULL);
(void) ar_cancelquery(dk->dk_libhandle->dkl_arlib, q);
if (DK_DEBUG('r'))
{
syslog(LOG_INFO, "thread %p ar_cancelquery(`%s')",
pthread_self(), qname);
}
#else /* USE_ARLIB */
status = res_query(qname, C_IN, T_TXT, ansbuf, sizeof ansbuf);
#endif /* USE_ARLIB */
#if USE_ARLIB
if (status == AR_STAT_ERROR || status == AR_STAT_EXPIRED)
{
if (DK_DEBUG('d') && dk->dk_id != NULL)
{
syslog(LOG_NOTICE, "%s: ar_waitreply(): %s", dk->dk_id,
ar_strerror(error));
}
dk_error(dk, "DNS error or timeout for `%s'", qname);
return DK_STAT_CANTVRFY;
}
#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:
case NO_DATA:
dk_error(dk, "`%s' not found", qname);
return DK_STAT_NOKEY;
case TRY_AGAIN:
case NO_RECOVERY:
default:
dk_error(dk, "DNS error or timeout for `%s'", qname);
return DK_STAT_CANTVRFY;
}
}
#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, qname,
sizeof qname);
if ((n = dn_skipname(cp, eom)) < 0)
{
dk_error(dk, "key record corrupted");
return DK_STAT_INTERNAL;
}
cp += n;
/* extract the type and class */
if (cp + INT16SZ + INT16SZ > eom)
return DK_STAT_INTERNAL;
GETSHORT(type, cp);
GETSHORT(class, cp);
}
if (type != T_TXT || class != C_IN)
{
dk_error(dk, "unexpected key reply");
return DK_STAT_INTERNAL;
}
/* if NXDOMAIN, return DK_STAT_NOKEY */
if (hdr.rcode == NXDOMAIN)
{
dk_error(dk, "`%s' not found", qname);
return DK_STAT_NOKEY;
}
/* get the answer count */
ancount = ntohs((unsigned short) hdr.ancount);
if (ancount == 0)
{
dk_error(dk, "DNS error or timeout for `%s'", qname);
return DK_STAT_CANTVRFY;
}
/* if truncated, we can't do it */
if (hdr.tc)
{
dk_error(dk, "DNS reply for `%s' truncated", qname);
return DK_STAT_INTERNAL;
}
/*
** Extract the data from the first answer.
*/
/* grab the label, even though we know what we asked... */
if ((n = dn_expand((unsigned char *) &ansbuf, eom, cp,
(RES_UNC_T) qname, sizeof qname)) < 0)
{
dk_error(dk, "key record corrupted");
return DK_STAT_INTERNAL;
}
/* ...and move past it */
cp += n;
/* extract the type and class */
if (cp + INT16SZ + INT16SZ > eom)
{
dk_error(dk, "key record corrupted");
return DK_STAT_INTERNAL;
}
GETSHORT(type, cp);
GETSHORT(class, cp);
/* skip the TTL */
cp += INT32SZ;
/* get payload length */
if (cp + INT16SZ > eom)
{
dk_error(dk, "key record corrupted");
return DK_STAT_INTERNAL;
}
GETSHORT(n, cp);
/* XXX -- maybe deal with a partial reply rather than require it all */
if (cp + n > eom)
{
dk_error(dk, "key record corrupted");
return DK_STAT_INTERNAL;
}
dk->dk_b64 = dk_malloc(dk->dk_libhandle, dk->dk_closure, n);
if (dk->dk_b64 == NULL)
{
dk_error(dk, "unable to allocate %d byte(s)", n);
return DK_STAT_NORESOURCE;
}
dk->dk_b64len = n;
/* decode the tags, allocate memory, and copy */
state = 0;
keyidx = 0;
for (p = cp, l = 0; p <= eom && n >= 0; p++, n--, l--)
{
if (p == eom || n == 0)
{
if (state == 3)
{
state = 4;
}
else if (state == 1 || state == 2)
{
dk_error(dk,
"syntax error in key record at `%s'",
qname);
return DK_STAT_CANTVRFY;
}
else
{
break;
}
}
/* if appropriate, consume a length byte */
if (p < eom && n > 0 && l == 0)
{
l = *p + 1;
continue;
}
switch (state)
{
case 0: /* before tag */
if (isascii(*p) && isspace(*p))
continue;
memset(tag, '\0', sizeof tag);
plen = 0;
state = 1;
/* FALLTHROUGH */
case 1: /* in tag */
if (*p == '=')
{
state = 2;
continue;
}
else if (isascii(*p) && isspace(*p))
{
continue;
}
else if (plen < sizeof tag)
{
tag[plen++] = *p;
continue;
}
/* FALLTHROUGH */
case 2: /* before value */
if (isascii(*p) && isspace(*p))
continue;
memset(value, '\0', sizeof value);
plen = 0;
state = 3;
key = (strcasecmp(tag, "p") == 0);
/* FALLTHROUGH */
case 3: /* in value */
if (*p == ';')
{
state = 4;
/* FALLTHROUGH */
}
else if (isascii(*p) && isspace(*p))
{
continue;
}
else if (key && keyidx < dk->dk_b64len)
{
dk->dk_b64[keyidx] = *p;
keyidx++;
continue;
}
else if (!key && plen < sizeof value)
{
value[plen++] = *p;
continue;
}
else
{
continue;
}
/* FALLTHROUGH */
case 4: /* after value */
if (strcasecmp(tag, "g") == 0)
{
dk->dk_gran = dk_strdup(dk->dk_libhandle,
dk->dk_closure,
value);
}
else if (strcasecmp(tag, "k") == 0)
{
/* key type */
if (strcasecmp(value, "rsa") != 0)
{
dk_error(dk,
"unknown key type `%s' at `%s'",
value, qname);
return DK_STAT_CANTVRFY;
}
}
else if (strcasecmp(tag, "t") == 0)
{
if (value[0] == 'y')
dk->dk_testing = TRUE;
}
else if (strcasecmp(tag, "p") == 0)
{
/* signature; handled earlier */
dk->dk_b64len = keyidx;
/* empty == revoked */
if (value[0] == '\0')
dk->dk_revoked = TRUE;
}
else if (strcasecmp(tag, "n") == 0)
{
/* XXX -- comment */
}
else
{
/* XXX -- unknown; ignore */
}
state = 0;
break;
}
}
/* XXX -- verify valid and required values here instead of dk_eoh()? */
/* base64-decode the key into dk_key */
if (!dk_decode(dk, DK_DECODE_KEY))
return DK_STAT_NORESOURCE;
dk_mfree(dk->dk_libhandle, dk->dk_closure, dk->dk_b64);
dk->dk_b64 = NULL;
return DK_STAT_OK;
}
/*
** DK_PARAM_LOOKUP -- lookup the parameter's code
**
** Parameters:
** str -- string to evaluate
**
** Return value:
** Mnemonic code, or -1 on error.
*/
static int
dk_param_lookup(str)
char *str;
{
int c;
assert(str != NULL);
for (c = 0; params[c].str != NULL; c++)
{
if (strcasecmp(str, params[c].str) == 0)
return params[c].code;
}
return -1;
}
/*
** DK_PROCESS_VALUE -- process the value that was found
**
** Parameters:
** dk -- DomainKeys handle
** pcode -- parameter whose value is being provided
** value -- the value
**
** Return value:
** 1 -- valid
** 0 -- invalid
** -1 -- error (malloc() failure in dk_decode())
*/
static int
dk_process_value(DK *dk, int pcode, char *value)
{
char *p;
char *ctx;
assert(dk != NULL);
assert(value != NULL);
switch (pcode)
{
case DK_PARAM_SIGNALG:
if (value[0] == '\0')
{
dk->dk_signalg = DK_SIGN_DEFAULT;
return 1;
}
else if (strcasecmp(value, "rsa-sha1") == 0)
{
dk->dk_signalg = DK_SIGN_RSASHA1;
return 1;
}
return 0;
case DK_PARAM_CANONALG:
if (value[0] == '\0')
{
dk->dk_canonalg = DK_CANON_DEFAULT;
return 1;
}
else if (strcasecmp(value, "simple") == 0)
{
dk->dk_canonalg = DK_CANON_SIMPLE;
return 1;
}
else if (strcasecmp(value, "nofws") == 0)
{
dk->dk_canonalg = DK_CANON_NOFWS;
return 1;
}
return 0;
case DK_PARAM_QUERYMETHOD:
if (value[0] == '\0')
{
/* we'll be generous and allow a default here */
dk->dk_querymethod = DK_QUERY_DEFAULT;
return 1;
}
else if (strcasecmp(value, "dns") == 0)
{
dk->dk_querymethod = DK_QUERY_DNS;
return 1;
}
return 0;
case DK_PARAM_DOMAIN:
if (strlen(value) > 0)
{
dk->dk_domain = dk_strdup(dk->dk_libhandle,
dk->dk_closure, value);
return 1;
}
else
{
/* domain has no default */
return 0;
}
case DK_PARAM_SELECTOR:
if (strlen(value) > 0)
{
dk->dk_selector = dk_strdup(dk->dk_libhandle,
dk->dk_closure, value);
return 1;
}
else
{
/* selector has no default */
return 0;
}
case DK_PARAM_SIGNATURE:
/* base64-decode the signature into dk_signature */
dk->dk_b64len = strlen(dk->dk_b64);
if (!dk_decode(dk, DK_DECODE_SIGNATURE))
return -1;
dk_mfree(dk->dk_libhandle, dk->dk_closure, dk->dk_b64);
dk->dk_b64 = NULL;
return 1;
case DK_PARAM_HDRLIST:
dk->dk_shdrlist = strdup(value);
if (dk->dk_shdrlist == NULL)
return -1;
for (p = strtok_r(dk->dk_shdrlist, ":", &ctx);
p != NULL && dk->dk_hdrlidx < MAXHDRCNT;
p = strtok_r(NULL, ":", &ctx))
{
while (isascii(*p) && isspace(*p))
p++;
dk->dk_hdrlist[dk->dk_hdrlidx] = p;
dk->dk_hdrlidx++;
}
return 1;
default:
return 0;
}
}
/*
** DK_PROCESS_DKHEADER -- process a DomainKeys: header
**
** Parameters:
** dk -- DomainKeys handle
** hdr -- header contents
**
** Return value:
** -1 -- system error; check errno
** 0 -- success
** 1 -- parse error
*/
static int
dk_process_dkheader(DK *dk, const char *hdr)
{
bool comment = FALSE;
bool escaped = FALSE;
bool quoting = FALSE;
int state = 0;
int status;
int plen = 0;
int pcode = 0;
size_t hlen;
const char *p;
char param[MAXPARAMETER + 1];
char value[MAXPARAMETER + 1];
assert(dk != NULL);
assert(hdr != NULL);
hlen = strlen(hdr);
for (p = hdr; *p != '\0'; p++)
{
if (!escaped)
{
if (*p == '\\')
{
escaped = TRUE;
continue;
}
if (*p == '(')
{
comment = TRUE;
continue;
}
else if (*p == ')')
{
comment = FALSE;
continue;
}
if (*p == '"')
quoting = !quoting;
}
if (comment)
continue;
if (escaped)
escaped = FALSE;
switch (state)
{
case 0: /* before parameter */
if (isascii(*p) && isspace(*p))
continue;
state = 1;
memset(param, '\0', sizeof param);
plen = 0;
/* FALLTHROUGH */
case 1: /* in parameter */
if (*p == '=')
{
state = 3;
continue;
}
else if (!quoting && isascii(*p) && isspace(*p))
{
state = 2;
}
else if (plen < MAXPARAMETER)
{
param[plen] = *p;
plen++;
continue;
}
/* FALLTHROUGH */
case 2: /* after parameter */
if (isascii(*p) && isspace(*p))
continue;
else if (*p == '=')
state = 3;
else
return 1;
break;
case 3: /* before value */
pcode = dk_param_lookup(param);
if (pcode == -1)
return 1;
if (isascii(*p) && isspace(*p))
continue;
state = 4;
memset(value, '\0', sizeof value);
plen = 0;
if (pcode == DK_PARAM_SIGNATURE)
{
dk->dk_b64 = dk_malloc(dk->dk_libhandle,
dk->dk_closure, hlen);
if (dk->dk_b64 == NULL)
return -1;
memset(dk->dk_b64, '\0', hlen);
}
/* FALLTHROUGH */
case 4: /* in value */
if (!quoting && !escaped && *p == ';')
{
status = dk_process_value(dk, pcode, value);
if (status == 0)
return 1;
else if (status == -1)
return -1;
if (*p == ';')
state = 0;
else
state = 5;
continue;
}
switch (pcode)
{
case DK_PARAM_SIGNALG:
case DK_PARAM_DOMAIN:
case DK_PARAM_CANONALG:
case DK_PARAM_QUERYMETHOD:
case DK_PARAM_SELECTOR:
case DK_PARAM_HDRLIST:
if (plen < MAXPARAMETER &&
(quoting || escaped ||
!(isascii(*p) && isspace(*p))))
{
value[plen] = *p;
plen++;
}
break;
case DK_PARAM_SIGNATURE:
dk->dk_b64[plen] = *p;
plen++;
break;
default:
return 1;
}
break;
case 5: /* after value */
if (isascii(*p) && isspace(*p))
continue;
switch (pcode)
{
case DK_PARAM_SIGNATURE:
dk->dk_b64len = plen;
break;
default:
break;
}
status = dk_process_value(dk, pcode, value);
if (status == 0)
return 1;
else if (status == -1)
return -1;
if (*p == ';')
state = 0;
else
return 1;
break;
default: /* shouldn't happen */
assert(0);
}
}
if (state == 4)
{
status = dk_process_value(dk, pcode, value);
if (status == 0)
return 1;
else if (status == -1)
return -1;
}
/* XXX -- verify valid and required values here instead of dk_eoh()? */
return 0;
}
/*
** DK_HDRSIGNED -- see if this header is in the list of signed headers
**
** Parameters:
** dk -- DomainKeys handle
** hdr -- header being considered
** mark -- mark this header ("found")
**
** Return value:
** TRUE iff the header under evaluation was included in the signature
*/
static bool
dk_hdrsigned(DK *dk, u_char *hdr, bool mark)
{
unsigned int c;
unsigned char *colon;
assert(dk != NULL);
assert(hdr != NULL);
colon = strchr(hdr, ':');
assert(colon != NULL);
/* if verifying and no header list given, all headers were signed */
if (dk->dk_mode == DK_MODE_VERIFY && dk->dk_hdrlidx == 0)
return TRUE;
for (c = 0; c < dk->dk_hdrlidx; c++)
{
if (strncasecmp(dk->dk_hdrlist[c], hdr, colon - hdr) == 0 &&
strlen(dk->dk_hdrlist[c]) == colon - hdr)
{
if (mark)
dk->dk_hdrmark[c] = TRUE;
return TRUE;
}
}
return FALSE;
}
/*
** DK_NEW -- allocate a new DomainKeys handle
*/
static DK *
dk_new(DK_LIB *libhandle, const char *id, void *memclosure,
dk_canon_t canon_alg, dk_alg_t sign_alg, DK_STAT *statp)
{
DK *new;
/* allocate the handle */
new = dk_malloc(libhandle, memclosure, sizeof(struct dk));
if (new == NULL)
{
*statp = DK_STAT_NORESOURCE;
return NULL;
}
/* populate defaults */
memset(new, '\0', sizeof(struct dk));
new->dk_id = id;
new->dk_signalg = (sign_alg == -1 ? DK_SIGN_DEFAULT
: sign_alg);
new->dk_canonalg = (canon_alg == -1 ? DK_CANON_DEFAULT
: canon_alg);
new->dk_querymethod = DK_QUERY_DEFAULT;
new->dk_crlf = DK_CRLF_UNKNOWN;
new->dk_mode = DK_MODE_UNKNOWN;
new->dk_state = DK_STATE_INIT;
new->dk_closure = memclosure;
new->dk_libhandle = libhandle;
new->dk_tmpdir = libhandle->dkl_tmpdir;
new->dk_timeout = DEFTIMEOUT;
if ((libhandle->dkl_options & DK_OPTS_HDRLIST) != 0)
new->dk_flags |= DK_FLAG_HDRLIST;
*statp = DK_STAT_OK;
return new;
}
/*
** DK_CANON -- canonicalize some data
**
** Parameters:
** dk -- DK handle
** buf -- buffer containing data
** buflen -- number of bytes to consume
**
** Return value:
** None.
*/
static void
dk_canon(DK *dk, unsigned char *buf, size_t buflen)
{
struct dk_sha1 *sha1;
assert(dk != NULL);
sha1 = (struct dk_sha1 *) dk->dk_signinfo;
if (buf == NULL || buflen == 0)
return;
if (dk->dk_writesep)
{
if (sha1->sha1_tmpfd != -1)
BIO_write(sha1->sha1_tmpbio, CRLF, 2);
SHA1_Update(&sha1->sha1_sha1, CRLF, 2);
dk->dk_writesep = FALSE;
}
if (sha1->sha1_tmpfd != -1)
BIO_write(sha1->sha1_tmpbio, buf, buflen);
SHA1_Update(&sha1->sha1_sha1, buf, buflen);
}
/*
** DK_VERROR -- log an error into a DK handle (varargs version)
**
** Parameters:
** dk -- DK context in which this is performed
** format -- format to apply
** va -- argument list
**
** Return value:
** None.
*/
static void
dk_verror(DK *dk, const char *format, va_list va)
{
int flen;
int saverr;
char *new;
assert(dk != NULL);
assert(format != NULL);
saverr = errno;
if (dk->dk_error == NULL)
{
dk->dk_error = dk_malloc(dk->dk_libhandle, dk->dk_closure,
DEFERRLEN);
if (dk->dk_error == NULL)
{
errno = saverr;
return;
}
dk->dk_errlen = DEFERRLEN;
}
for (;;)
{
flen = vsnprintf(dk->dk_error, dk->dk_errlen, format, va);
/* compensate for broken vsnprintf() implementations */
if (flen == -1)
flen = dk->dk_errlen * 2;
if (flen >= dk->dk_errlen)
{
new = dk_malloc(dk->dk_libhandle, dk->dk_closure,
flen + 1);
if (new == NULL)
{
errno = saverr;
return;
}
dk_mfree(dk->dk_libhandle, dk->dk_closure,
dk->dk_error);
dk->dk_error = new;
dk->dk_errlen = flen + 1;
}
else
{
break;
}
}
errno = saverr;
}
/*
** DK_ERROR -- log an error into a DK handle
**
** Parameters:
** dk -- DK context in which this is performed
** format -- format to apply
** ... -- arguments
**
** Return value:
** None.
*/
static void
dk_error(DK *dk, const char *format, ...)
{
va_list va;
assert(dk != NULL);
assert(format != NULL);
va_start(va, format);
dk_verror(dk, format, va);
va_end(va);
}
/*
** ===== PUBLIC SECTION
*/
/*
** DK_INIT -- initialize the DomainKeys system
*/
DK_LIB *
dk_init(void *(*caller_mallocf)(void *closure, size_t nbytes),
void (*caller_freef)(void *closure, void *p))
{
DK_LIB *libhandle;
/* initialize the resolver */
(void) res_init();
/* initialize OpenSSL algorithms */
#if 0
OpenSSL_add_all_algorithms();
#endif /* 0 */
/* copy the parameters */
libhandle = (DK_LIB *) malloc(sizeof(struct dk_lib));
if (libhandle == NULL)
return NULL;
libhandle->dkl_malloc = caller_mallocf;
libhandle->dkl_free = caller_freef;
libhandle->dkl_options = DK_OPTS_DEFAULT;
libhandle->dkl_tmpdir = getenv("DK_TMPDIR");
if (libhandle->dkl_tmpdir == NULL || libhandle->dkl_tmpdir[0] == '\0')
libhandle->dkl_tmpdir = DK_TMPDIR;
#if USE_ARLIB
libhandle->dkl_arlib = ar_init(NULL, NULL, NULL, 0);
if (libhandle->dkl_arlib == NULL)
{
free(libhandle);
return NULL;
}
#else /* USE_ARLIB */
(void) res_init();
#endif /* USE_ARLIB */
return libhandle;
}
/*
** DK_SIGN -- allocate a handle for use in a signature operation
*/
DK *
dk_sign(DK_LIB *dklib, const char *id, void *memclosure,
const dk_sigkey_t *secretkey, dk_canon_t canonalg, dk_alg_t signalg,
DK_STAT *statp)
{
DK *new;
assert(dklib != NULL);
assert(canonalg == DK_CANON_SIMPLE || canonalg == DK_CANON_NOFWS);
assert(signalg == DK_SIGN_RSASHA1);
new = dk_new(dklib, id, memclosure, canonalg, signalg, statp);
if (new != NULL)
{
new->dk_mode = DK_MODE_SIGN;
if (secretkey != NULL)
{
new->dk_keylen = strlen((const char *) secretkey);
new->dk_key = (unsigned char *) dk_malloc(dklib,
memclosure,
new->dk_keylen + 1);
if (new->dk_key == NULL)
{
dk_free(new);
return NULL;
}
memcpy(new->dk_key, secretkey, new->dk_keylen + 1);
}
}
return new;
}
/*
** DK_VERIFY -- allocate a handle for use in a verification operation
*/
DK *
dk_verify(DK_LIB *dklib, const char *id, void *memclosure, DK_STAT *statp)
{
DK *new;
assert(dklib != NULL);
new = dk_new(dklib, id, memclosure, DK_CANON_UNKNOWN,
DK_SIGN_UNKNOWN, statp);
if (new != NULL)
new->dk_mode = DK_MODE_VERIFY;
return new;
}
/*
** DK_GETERROR -- return any stored error string from within the DKIM
** context handle
*/
const char *
dk_geterror(DK *dk)
{
assert(dk != NULL);
return dk->dk_error;
}
/*
** DK_HEADER -- process a header
*/
DK_STAT
dk_header(DK *dk, u_char *hdr, size_t len)
{
assert(dk != NULL);
assert(hdr != NULL);
assert(len > 0);
/* verify state */
if (dk->dk_state > DK_STATE_HEADER)
return DK_STAT_INTERNAL;
dk->dk_state = DK_STATE_HEADER;
/*
** If we're signing, or we found the DomainKeys: header before,
** this is a header of interest. Canonicalize and process.
*/
if (dk->dk_mode != DK_MODE_VERIFY || dk->dk_processing)
{
bool prevcr;
bool skipit = FALSE;
unsigned char *p;
unsigned char *pend;
char *q;
char *qend;
pend = hdr + len;
qend = &dk->dk_hdrbuf[sizeof(dk->dk_hdrbuf)];
prevcr = FALSE;
if (dk->dk_mode == DK_MODE_VERIFY)
{
/* is this header signed? */
if (!dk_hdrsigned(dk, hdr, TRUE))
skipit = TRUE;
}
else
{
/* XXX -- omit Received: ? */
if (!dk_hdrsigned(dk, hdr, FALSE) &&
dk->dk_hdrlidx < MAXHDRCNT)
{
dk->dk_hdrlist[dk->dk_hdrlidx] = &dk->dk_hdrbuf[dk->dk_hdrlen];
dk->dk_hdrlidx++;
}
}
if (dk->dk_hdrsidx < MAXHDRCNT)
{
dk->dk_hdrset[dk->dk_hdrsidx] = &dk->dk_hdrbuf[dk->dk_hdrlen];
dk->dk_hdrsidx++;
}
for (p = hdr, q = &dk->dk_hdrbuf[dk->dk_hdrlen];
!skipit && p < pend && q < qend;
p++)
{
if (*p == '\n' && dk->dk_canonalg != DK_CANON_NOFWS)
{
if (prevcr)
{
prevcr = FALSE;
*q = '\n';
}
else
{
*q = '\r';
if (q < qend)
{
q++;
*q = '\n';
}
}
}
else if (dk->dk_canonalg == DK_CANON_NOFWS)
{
if (*p == 0x20 ||
*p == 0x09 ||
*p == 0x0a ||
*p == 0x0D)
continue;
*q = *p;
}
else
{
*q = *p;
}
if (*p == '\r')
prevcr = TRUE;
q++;
}
if (!skipit && dk->dk_canonalg == DK_CANON_NOFWS)
{
if (q < qend)
{
*q = '\r';
q++;
}
if (q < qend)
{
*q = '\n';
q++;
}
}
*q = '\0';
q++;
dk->dk_hdrlen = q - dk->dk_hdrbuf;
}
/*
** Store the From: or Sender: value regardless of its position
** in case no DomainKey-Signature: header is found.
*/
if ((strncasecmp(hdr, "from:", 5) == 0 && dk->dk_uhdrn[0] == '\0') ||
strncasecmp(hdr, "sender:", 7) == 0)
{
int status;
char *user;
char *domain;
u_char *colon;
colon = (u_char *) strchr(hdr, ':');
status = rfc2822_mailbox_split(colon + 1, &user, &domain);
if (status != 0 || user == NULL || domain == NULL)
{
dk_error(dk, "unable to parse sender `%s'", colon + 1);
return DK_STAT_SYNTAX;
}
sm_strlcpy(dk->dk_uhdrn, hdr,
MIN(sizeof dk->dk_uhdrn, (u_int) (colon - hdr) + 1));
dk_lowercase(dk->dk_uhdrn);
snprintf(dk->dk_uhdrv, sizeof dk->dk_uhdrv, "%s@%s", user,
domain);
}
/*
** If this is a DomainKey-Signature: header, cache it and note that
** the rest is of interest.
*/
if (strncasecmp(hdr, DK_SIGNHEADER, strlen(DK_SIGNHEADER)) == 0 &&
hdr[strlen(DK_SIGNHEADER)] == ':')
{
/* first DomainKey-Signature: header? */
if (!dk->dk_processing)
{
u_char *hval;
hval = hdr + strlen(DK_SIGNHEADER) + 1;
if (dk_process_dkheader(dk, hval) != 0)
{
dk_error(dk, "syntax error in signature");
return DK_STAT_SYNTAX;
}
dk->dk_processing = TRUE;
}
/* XXX -- OPTIMIZATION
** For signed messages, at this point we know under which
** domain and selector the message was signed. We could
** start the appropriate TXT query here.
*/
}
return DK_STAT_OK;
}
/*
** DK_EOH -- note end of headers
*/
DK_STAT
dk_eoh(DK *dk)
{
int saveerr;
unsigned int c;
unsigned int d;
size_t hlen;
char *colon;
struct dk_sha1 *sha1;
char *sender;
char *from;
char *hdr;
unsigned char *p;
assert(dk != NULL);
/* verify state */
if (dk->dk_state > DK_STATE_EOH)
return DK_STAT_INTERNAL;
dk->dk_state = DK_STATE_EOH;
sender = NULL;
from = NULL;
for (c = 0; c < dk->dk_hdrsidx; c++)
{
if (strncasecmp(dk->dk_hdrset[c], "from:", 5) == 0)
{
if (from != NULL)
{
dk_error(dk, "multiple From: headers found");
return DK_STAT_SYNTAX;
}
from = dk->dk_hdrset[c];
}
if (strncasecmp(dk->dk_hdrset[c], "sender:", 7) == 0)
{
if (sender != NULL)
{
dk_error(dk, "multiple Sender: headers found");
return DK_STAT_SYNTAX;
}
sender = dk->dk_hdrset[c];
}
}
hdr = NULL;
if (sender == NULL)
hdr = from;
else
hdr = sender;
/* if verifying and no from/sender header was found, short-circuit */
if (hdr == NULL && dk->dk_mode == DK_MODE_VERIFY)
{
char *at;
at = strchr(dk->dk_uhdrv, '@');
if (at == NULL || *(at + 1) == '\0')
{
dk_error(dk, "missing or empty domain name in sender");
return DK_STAT_SYNTAX;
}
/* clean up if dk->dk_domain already allocated */
if (dk->dk_domain == NULL)
{
dk_mfree(dk->dk_libhandle, dk->dk_closure,
dk->dk_domain);
}
dk->dk_domain = dk_malloc(dk->dk_libhandle,
dk->dk_closure,
strlen(at + 1) + 1);
if (dk->dk_domain == NULL)
{
dk_error(dk, "unable to allocate %d byte(s)",
strlen(at + 1) + 1);
return DK_STAT_NORESOURCE;
}
sm_strlcpy(dk->dk_domain, at + 1, strlen(at + 1) + 1);
dk->dk_skipbody = TRUE;
(void) dk_in_use(dk);
if (dk->dk_signall || DK_DEBUG('s'))
return DK_STAT_NOSIG;
else
return DK_STAT_OK;
}
/* if we found a signature but the sender doesn't match it, say so */
if (hdr != NULL)
{
int status;
size_t len;
char *user;
char *domain;
colon = strchr(hdr, ':');
len = strlen(colon + 1) + 1;
dk->dk_sender = dk_malloc(dk->dk_libhandle,
dk->dk_closure, len);
if (dk->dk_sender == NULL)
return DK_STAT_INTERNAL;
sm_strlcpy(dk->dk_frombuf, colon + 1, sizeof dk->dk_frombuf);
status = rfc2822_mailbox_split(dk->dk_frombuf, &user, &domain);
if (status != 0 || user == NULL || domain == NULL)
{
dk_error(dk, "unable to parse sender `%s'", colon + 1);
return DK_STAT_SYNTAX;
}
snprintf(dk->dk_sender, len, "%s@%s", user, domain);
/* verify domain/subdomain match */
if (dk->dk_mode == DK_MODE_VERIFY)
{
bool ok = FALSE;
/* incorrect DomainKey-Signature: header? */
if (dk->dk_domain == NULL)
{
dk_error(dk, "can't determine signing domain");
return DK_STAT_CANTVRFY;
}
if (strcasecmp(domain, dk->dk_domain) != 0)
{
for (p = strchr(domain, '.');
p != NULL;
p = strchr(p + 1, '.'))
{
if (strcasecmp(p + 1,
dk->dk_domain) == 0)
{
ok = TRUE;
break;
}
}
}
else
{
ok = TRUE;
}
if (!ok)
{
dk_error(dk,
"signing domain and sending domain mismatch");
return DK_STAT_CANTVRFY;
}
}
dk->dk_user = dk_strdup(dk->dk_libhandle, dk->dk_closure,
user);
if (dk->dk_domain == NULL)
{
dk->dk_domain = dk_strdup(dk->dk_libhandle,
dk->dk_closure,
domain);
}
}
else
{
dk_error(dk, "no sender header found");
return DK_STAT_SYNTAX;
}
/* initialize data hash */
sha1 = dk_malloc(dk->dk_libhandle, dk->dk_closure,
sizeof(struct dk_sha1));
if (sha1 == NULL)
{
dk_error(dk, "unable to allocate %d byte(s)",
sizeof(struct dk_sha1));
return DK_STAT_NORESOURCE;
}
memset(sha1, '\0', sizeof(struct dk_sha1));
sha1->sha1_tmpfd = -1;
dk->dk_signinfo = (void *) sha1;
/* verify granularity */
if (dk->dk_gran != NULL && dk->dk_gran[0] != '\0')
{
if (strcmp(dk->dk_user, dk->dk_gran) != 0)
{
dk_error(dk, "key granularity mismatch");
return DK_STAT_CANTVRFY;
}
}
SHA1_Init(&sha1->sha1_sha1);
if (DK_DEBUG('c') ||
(dk->dk_libhandle->dkl_options & DK_OPTS_TMPFILES) != 0)
{
/* cache the stored stuff for later processing */
snprintf(sha1->sha1_tmppath, sizeof sha1->sha1_tmppath - 1,
"%s/dk.%d.XXXXXX", dk->dk_tmpdir, getpid());
sha1->sha1_tmpfd = mkstemp(sha1->sha1_tmppath);
saveerr = errno;
if (sha1->sha1_tmpfd == -1)
{
/* looks like this is a bug on OpenBSD */
(void) unlink(sha1->sha1_tmppath);
errno = saveerr;
dk_error(dk, "unable to create temporary file at %s",
sha1->sha1_tmppath);
return DK_STAT_NORESOURCE;
}
sha1->sha1_tmpbio = BIO_new_fd(sha1->sha1_tmpfd, 1);
if (!DK_DEBUG('c'))
(void) unlink(sha1->sha1_tmppath);
}
/* dk_hdrlist -- set of headers to include */
/* dk_hdrset -- set of headers on input */
/*
** I would normally be embarrassed to use an O(N^2)
** algorithm, but this is a small data set.
*/
if (dk->dk_hdrlidx > 0 && (dk->dk_flags & DK_FLAG_HDRLIST) != 0)
{
/* reset sender/from buffer to use what was actually signed */
memset(dk->dk_uhdrn, '\0', sizeof dk->dk_uhdrn);
memset(dk->dk_uhdrv, '\0', sizeof dk->dk_uhdrv);
for (c = 0; c < dk->dk_hdrlidx; c++)
{
colon = strchr(dk->dk_hdrlist[c], ':');
if (colon == NULL)
hlen = strlen(dk->dk_hdrlist[c]);
else
hlen = colon - dk->dk_hdrlist[c];
for (d = 0; d < dk->dk_hdrsidx; d++)
{
if (strncasecmp(dk->dk_hdrset[d],
dk->dk_hdrlist[c],
hlen) == 0 &&
dk->dk_hdrset[d][hlen] == ':')
{
dk_canon(dk, dk->dk_hdrset[d],
strlen(dk->dk_hdrset[d]));
colon = strchr(dk->dk_hdrset[d], ':');
/* extract correct sender/from data */
if ((strncasecmp(dk->dk_hdrlist[c],
"from",
hlen) == 0 &&
dk->dk_uhdrn[0] == '\0') ||
strncasecmp(dk->dk_hdrlist[c],
"sender", hlen) == 0)
{
strncpy(dk->dk_uhdrn,
dk->dk_hdrlist[c],
hlen);
if (colon != NULL)
{
sm_strlcpy(dk->dk_uhdrv,
colon + 1,
sizeof dk->dk_uhdrv);
}
}
}
}
}
if (dk->dk_uhdrv[0] != '\0')
{
int status;
char *user;
char *domain;
u_char *at;
char addr[MAXADDRESS + 1];
sm_strlcpy(addr, dk->dk_uhdrv, sizeof addr);
status = rfc2822_mailbox_split(addr, &user, &domain);
if (status != 0 || user == NULL || domain == NULL)
{
dk_error(dk, "unable to parse sender `%s'",
addr);
return DK_STAT_SYNTAX;
}
snprintf(dk->dk_uhdrv, sizeof dk->dk_uhdrv, "%s@%s",
user, domain);
at = strchr(dk->dk_uhdrv, '@');
dk_lowercase(at + 1);
}
}
else
{
unsigned int c;
for (c = 0; c < dk->dk_hdrsidx; c++)
{
dk_canon(dk, dk->dk_hdrset[c],
strlen(dk->dk_hdrset[c]));
}
}
/*
** The next call to dk_canon() that actually writes something
** should write an extra CRLF to separate the headers from the
** body.
*/
dk->dk_writesep = TRUE;
/*
** If no DomainKeys: header was found, determine whether or not
** there should be one, and return something appropriate.
*/
if (!dk->dk_processing)
{
int status;
if (dk->dk_domain == NULL)
{
char *at;
at = strchr(dk->dk_sender, '@');
if (at == NULL || *(at + 1) == '\0')
{
dk_error(dk, "can't find sender domain in `%s'",
dk->dk_sender);
return DK_STAT_SYNTAX;
}
dk->dk_domain = dk_malloc(dk->dk_libhandle,
dk->dk_closure,
strlen(at + 1) + 1);
if (dk->dk_domain == NULL)
{
dk_error(dk, "unable to allocate %d byte(s)",
strlen(at + 1) + 1);
return DK_STAT_NORESOURCE;
}
sm_strlcpy(dk->dk_domain, at + 1, strlen(at + 1) + 1);
}
if (dk->dk_selector == NULL && dk->dk_mode == DK_MODE_VERIFY)
{
(void) dk_in_use(dk);
if (dk->dk_signall || DK_DEBUG('s'))
return DK_STAT_NOSIG;
else
return DK_STAT_OK;
}
status = dk_get_key(dk);
if (status == DK_STAT_INTERNAL)
{
return status;
}
else if (status != DK_STAT_OK)
{
if (dk->dk_mode == DK_MODE_VERIFY)
{
if (dk->dk_key != NULL || dk->dk_signall ||
DK_DEBUG('s'))
return DK_STAT_NOSIG;
else
return DK_STAT_NOKEY;
}
else
{
if (dk->dk_key == NULL)
return DK_STAT_NOKEY;
}
}
}
/*
** If some of the supposedly-signed headers were absent,
** return an error.
*/
if (dk->dk_processing)
{
unsigned int c;
for (c = 0; c < dk->dk_hdrlidx; c++)
{
if (!dk->dk_hdrmark[c])
return DK_STAT_BADSIG;
}
}
/*
** Return DK_STAT_REVOKED if the key was revoked.
*/
if (dk->dk_revoked)
return DK_STAT_REVOKED;
/*
** Success!
*/
return DK_STAT_OK;
}
/*
** DK_BODY -- process a body chunk
*/
DK_STAT
dk_body(DK *dk, u_char *buf, size_t len)
{
size_t lidx;
size_t wlen;
u_char *p;
u_char *eob;
u_char *wrote;
unsigned char lbuf[BUFRSZ];
assert(dk != NULL);
assert(buf != NULL);
assert(len > 0);
/* verify state */
if (dk->dk_state > DK_STATE_BODY)
return DK_STAT_INTERNAL;
dk->dk_state = DK_STATE_BODY;
if (dk->dk_skipbody)
return DK_STAT_OK;
eob = buf + len - 1;
wrote = buf;
wlen = 0;
lidx = 0;
/* run it through the canonicalizing stuff */
switch (dk->dk_canonalg)
{
case DK_CANON_SIMPLE:
for (p = buf; p <= eob; p++)
{
if (*p == '\n')
{
int c;
/* adaptive */
if (dk->dk_crlf == DK_CRLF_UNKNOWN)
{
if (dk->dk_lastchar == '\r')
dk->dk_crlf = DK_CRLF_CRLF;
else
dk->dk_crlf = DK_CRLF_LF;
}
if (dk->dk_crlf == DK_CRLF_CRLF &&
dk->dk_lastchar == '\r')
{
if (wlen == 1)
{
dk->dk_blanks++;
wrote = p + 1;
wlen = 0;
}
else
{
for (c = 0;
c < dk->dk_blanks;
c++)
dk_canon(dk, CRLF, 2);
dk->dk_blanks = 0;
dk_canon(dk, wrote, wlen + 1);
wlen = 0;
wrote = p + 1;
}
dk->dk_lastchar = *p;
continue;
}
else if (dk->dk_crlf == DK_CRLF_LF)
{
if (wlen == 0)
{
dk->dk_blanks++;
wrote = p + 1;
}
else
{
for (c = 0;
c < dk->dk_blanks;
c++)
dk_canon(dk, CRLF, 2);
dk->dk_blanks = 0;
dk_canon(dk, wrote, wlen);
dk_canon(dk, CRLF, 2);
wlen = 0;
wrote = p + 1;
}
dk->dk_lastchar = *p;
continue;
}
}
dk->dk_lastchar = *p;
wlen++;
}
/* write what's left */
if (wlen > 0)
dk_canon(dk, wrote, wlen);
break;
case DK_CANON_NOFWS:
for (p = buf; p <= eob; p++)
{
if (*p == '\n')
{
int c;
/* adaptive */
if (dk->dk_crlf == DK_CRLF_UNKNOWN)
{
if (dk->dk_lastchar == '\r')
dk->dk_crlf = DK_CRLF_CRLF;
else
dk->dk_crlf = DK_CRLF_LF;
}
if ((dk->dk_lastchar == '\r' &&
dk->dk_crlf == DK_CRLF_CRLF) ||
dk->dk_crlf == DK_CRLF_LF)
{
if (lidx == 0)
{
dk->dk_blanks++;
}
else
{
for (c = 0;
c < dk->dk_blanks;
c++)
dk_canon(dk, CRLF, 2);
dk->dk_blanks = 0;
dk_canon(dk, lbuf, lidx);
dk_canon(dk, CRLF, 2);
lidx = 0;
}
}
}
else if (!dk_isfws(*p))
{
if (lidx == sizeof lbuf)
{
dk_canon(dk, lbuf, lidx);
lidx = 0;
}
lbuf[lidx] = *p;
lidx++;
}
dk->dk_lastchar = *p;
}
if (lidx > 0)
dk_canon(dk, lbuf, lidx);
break;
default:
dk_canon(dk, buf, len);
break;
}
return DK_STAT_OK;
}
/*
** DK_EOM -- note end of message
*/
DK_STAT
dk_eom(DK *dk, DK_FLAGS *dkf)
{
int ret;
BIO *key = NULL;
struct dk_sha1 *sha1;
unsigned char md[SHA_DIGEST_LENGTH];
assert(dk != NULL);
/* verify state */
if (dk->dk_state >= DK_STATE_EOM)
return DK_STAT_INTERNAL;
dk->dk_state = DK_STATE_EOM;
sha1 = (struct dk_sha1 *) dk->dk_signinfo;
ret = DK_STAT_OK;
/* if we're verifying, do the verification */
if (dk->dk_mode == DK_MODE_VERIFY)
{
int status;
/* no sender header was found below the signature */
if (dk->dk_skipbody && dk->dk_processing)
{
dk_error(dk, "no sender header found below signature");
return DK_STAT_SYNTAX;
}
if (dk->dk_key == NULL)
{
if (dk->dk_selector == NULL)
{
(void) dk_in_use(dk);
if (dk->dk_signall || DK_DEBUG('s'))
return DK_STAT_NOSIG;
else
return DK_STAT_OK;
}
status = dk_get_key(dk);
if (status != DK_STAT_OK)
return status;
}
/* load the public key */
key = BIO_new_mem_buf(dk->dk_key, dk->dk_keylen);
if (key == NULL)
{
dk_error(dk, "BIO_new_mem_buf() failed");
return DK_STAT_NORESOURCE;
}
sha1->sha1_pkey = d2i_PUBKEY_bio(key, NULL);
if (sha1->sha1_pkey == NULL)
{
dk_error(dk, "d2i_PUBKEY_bio() failed");
BIO_free(key);
return DK_STAT_NORESOURCE;
}
/* set up the RSA object */
sha1->sha1_rsa = EVP_PKEY_get1_RSA(sha1->sha1_pkey);
if (sha1->sha1_rsa == NULL)
{
dk_error(dk, "EVP_PKEY_get1_RSA() failed");
BIO_free(key);
return DK_STAT_NORESOURCE;
}
sha1->sha1_keysize = RSA_size(sha1->sha1_rsa);
sha1->sha1_pad = RSA_PKCS1_PADDING;
/* compute the SHA1 hash */
SHA1_Final(md, &sha1->sha1_sha1);
/* verify the signature */
sha1->sha1_rsain = dk->dk_signature;
sha1->sha1_rsainlen = dk->dk_signlen;
status = RSA_verify(NID_sha1, md, SHA_DIGEST_LENGTH,
sha1->sha1_rsain, sha1->sha1_rsainlen,
sha1->sha1_rsa);
if (status == 0)
ret = DK_STAT_BADSIG;
}
/* return flags */
(void) dk_in_use(dk);
if (dkf != NULL)
{
if (dk->dk_testing)
*dkf |= DK_FLAG_TESTING;
if (dk->dk_signall)
*dkf |= DK_FLAG_SIGNSALL;
}
if (key != NULL)
BIO_free(key);
return ret;
}
/*
** DK_GETSIG -- compute the signature for a message
*/
DK_STAT
dk_getsig(DK *dk, u_char *buf, size_t len)
{
int bits;
int c;
int char_count;
int status;
u_int l;
size_t b64idx;
size_t b64len;
unsigned char *b64;
BIO *key;
struct dk_sha1 *sha1;
unsigned char md[SHA_DIGEST_LENGTH];
assert(dk != NULL);
assert(buf != NULL);
assert(len > 0);
/* verify state */
if (dk->dk_state != DK_STATE_EOM)
{
dk_error(dk, "dk_getsig() called out of sequence");
return DK_STAT_INTERNAL;
}
sha1 = (struct dk_sha1 *) dk->dk_signinfo;
/* must be called only with a signing handle */
if (dk->dk_mode == DK_MODE_VERIFY)
{
dk_error(dk, "dk_getsig() called with verifying handle");
return DK_STAT_INTERNAL;
}
/* try to get the signature if it wasn't given */
if (dk->dk_key == NULL)
{
int status;
status = dk_get_key(dk);
if (status != DK_STAT_OK)
return status;
}
/* load the private key */
key = BIO_new_mem_buf(dk->dk_key, dk->dk_keylen);
if (key == NULL)
{
dk_error(dk, "BIO_new_mem_buf() failed");
return DK_STAT_NORESOURCE;
}
sha1->sha1_pkey = PEM_read_bio_PrivateKey(key, NULL, NULL, NULL);
if (sha1->sha1_pkey == NULL)
{
dk_error(dk, "PEM_read_bio_PrivateKey() failed");
BIO_free(key);
return DK_STAT_NORESOURCE;
}
/* set up the RSA object */
sha1->sha1_rsa = EVP_PKEY_get1_RSA(sha1->sha1_pkey);
if (sha1->sha1_rsa == NULL)
{
dk_error(dk, "EVP_PKEY_get1_RSA() failed");
BIO_free(key);
return DK_STAT_NORESOURCE;
}
sha1->sha1_keysize = RSA_size(sha1->sha1_rsa);
sha1->sha1_pad = RSA_PKCS1_PADDING;
sha1->sha1_rsaout = dk_malloc(dk->dk_libhandle, dk->dk_closure,
sha1->sha1_keysize);
if (sha1->sha1_rsaout == NULL)
{
dk_error(dk, "unable to allocate %d byte(s)",
sha1->sha1_keysize);
BIO_free(key);
return DK_STAT_NORESOURCE;
}
BIO_reset(sha1->sha1_tmpbio);
/* compute the sha1 hash of the message */
SHA1_Final(md, &sha1->sha1_sha1);
/* compute the signature on the hash */
sha1->sha1_rsainlen = SHA_DIGEST_LENGTH;
sha1->sha1_rsain = md;
status = RSA_sign(NID_sha1, sha1->sha1_rsain,
sha1->sha1_rsainlen, sha1->sha1_rsaout,
&l, sha1->sha1_rsa);
if (status == 0 || l == 0)
{
BIO_free(key);
dk_error(dk, "RSA_sign() returned %d, length %d", status, l);
return DK_STAT_INTERNAL;
}
sha1->sha1_rsaoutlen = l;
/* store the signature */
dk->dk_signature = sha1->sha1_rsaout;
dk->dk_signlen = sha1->sha1_rsaoutlen;
/* base64-encode the signature for validation */
b64len = sha1->sha1_rsaoutlen * 3 + 5;
b64len += (b64len / 60);
b64 = dk_malloc(dk->dk_libhandle, dk->dk_closure, b64len);
if (b64 == NULL)
{
dk_error(dk, "unable to allocate %d byte(s)", b64len);
BIO_free(key);
return DK_STAT_NORESOURCE;
}
memset(b64, '\0', b64len);
bits = 0;
char_count = 0;
b64idx = 0;
for (c = 0; c < sha1->sha1_rsaoutlen; c++)
{
bits += sha1->sha1_rsaout[c];
char_count++;
if (char_count == 3)
{
if (b64idx > b64len - 4)
{
BIO_free(key);
dk_error(dk, "base64 encoding failed");
return DK_STAT_INTERNAL;
}
b64[b64idx++] = alphabet[bits >> 18];
b64[b64idx++] = alphabet[(bits >> 12) & 0x3f];
b64[b64idx++] = alphabet[(bits >> 6) & 0x3f];
b64[b64idx++] = alphabet[bits & 0x3f];
bits = 0;
char_count = 0;
}
else
{
bits <<= 8;
}
}
if (char_count != 0)
{
if (b64idx > b64len - 4)
{
BIO_free(key);
dk_error(dk, "base64 encoding failed");
return DK_STAT_INTERNAL;
}
bits <<= 16 - (8 * char_count);
b64[b64idx++] = alphabet[bits >> 18];
b64[b64idx++] = alphabet[(bits >> 12) & 0x3f];
if (char_count == 1)
{
b64[b64idx++] = '=';
b64[b64idx++] = '=';
}
else
{
b64[b64idx++] = alphabet[(bits >> 6) & 0x3f];
b64[b64idx++] = '=';
}
}
dk->dk_b64len = b64idx;
dk->dk_b64 = b64;
/* extract the signature */
strncpy(buf, dk->dk_b64, MIN(len, dk->dk_b64len));
BIO_free(key);
return DK_STAT_OK;
}
/*
** DK_GETHDRS -- report which headers were included in calculation of
** the signature
*/
DK_STAT
dk_gethdrs(DK *dk, int *hcnt, u_char *buf, size_t buflen)
{
int cnt = 0;
unsigned int c;
char *colon;
char *end;
assert(dk != NULL);
assert(buf != NULL);
assert(buflen > 0);
/* verify state */
if (dk->dk_state != DK_STATE_EOM)
return DK_STAT_INTERNAL;
end = buf + strlen(buf);
for (c = 0; c < dk->dk_hdrlidx; c++)
{
if (c != 0)
{
if (sm_strlcat(buf, ":", buflen) >= buflen)
return DK_STAT_INTERNAL;
end++;
}
if (sm_strlcat(buf, dk->dk_hdrlist[c], buflen) >= buflen)
return DK_STAT_INTERNAL;
colon = strchr(end, ':');
if (colon == NULL)
return DK_STAT_INTERNAL;
cnt++;
*colon = '\0';
for (; *end != '\0'; end++)
if (isascii(*end) && isupper(*end))
*end = tolower(*end);
}
*hcnt = cnt;
return DK_STAT_OK;
}
/*
** DK_OPTIONS -- set/get options
**
** Parameters:
** dklib -- DK library handle
** op -- DK_OP_SETOPT to set options, DK_OP_GETOPT to get options
** opts -- pointer to either the new option set, or where to write the
** current option set
**
** Return value:
** A DK_STAT value.
*/
DK_STAT
dk_options(DK_LIB *dklib, int op, int *opts)
{
assert(dklib != NULL);
assert(op == DK_OP_SETOPT || op == DK_OP_GETOPT);
assert(opts != NULL);
switch (op)
{
case DK_OP_SETOPT:
dklib->dkl_options = *opts;
break;
case DK_OP_GETOPT:
*opts = dklib->dkl_options;
break;
}
return DK_STAT_OK;
}
/*
** DK_REPORTINFO -- get report information
*/
DK_STAT
dk_reportinfo(DK *dk, int *fd, char *addr, size_t alen)
{
struct dk_sha1 *sha1;
assert(dk != NULL);
/* verify state */
if (dk->dk_state != DK_STATE_EOM)
return DK_STAT_INTERNAL;
sha1 = (struct dk_sha1 *) dk->dk_signinfo;
if (fd != NULL)
{
if (sha1 == NULL)
*fd = -1;
else
*fd = sha1->sha1_tmpfd;
}
if (addr != NULL)
sm_strlcpy(addr, dk->dk_reportaddr, alen);
return DK_STAT_OK;
}
/*
** DK_GETIDENTITY -- retrieve apparent signer's identity
*/
DK_STAT
dk_getidentity(DK *dk, char *hname, size_t hnamelen,
char *hval, size_t hvallen)
{
assert(dk != NULL);
if (dk->dk_state < DK_STATE_EOH)
return DK_STAT_INTERNAL;
if (hname != 0 && hnamelen > 0)
sm_strlcpy(hname, dk->dk_uhdrn, hnamelen);
if (hval != 0 && hvallen > 0)
sm_strlcpy(hval, dk->dk_uhdrv, hvallen);
return DK_STAT_OK;
}
/*
** DK_TIMEOUT -- set/get the current DNS timeout
*/
DK_STAT
dk_timeout(DK *dk, int new, int *old)
{
assert(dk != NULL);
if (old != NULL)
*old = dk->dk_timeout;
if (new != -1)
dk->dk_timeout = new;
return DK_STAT_OK;
}
/*
** DK_FREE -- release resources associated with a DK handle
*/
DK_STAT
dk_free(DK *dk)
{
struct dk_sha1 *sha1;
assert(dk != NULL);
sha1 = (struct dk_sha1 *) dk->dk_signinfo;
#define CLOBBER(x) if ((x) != NULL) \
{ \
dk_mfree(dk->dk_libhandle, dk->dk_closure, (x)); \
(x) = NULL; \
}
#define BIO_CLOBBER(x) if ((x) != NULL) \
{ \
BIO_free((x)); \
(x) = NULL; \
}
#define RSA_CLOBBER(x) if ((x) != NULL) \
{ \
RSA_free((x)); \
(x) = NULL; \
}
#define EVP_CLOBBER(x) if ((x) != NULL) \
{ \
EVP_PKEY_free((x)); \
(x) = NULL; \
}
if (sha1 != NULL)
{
BIO_CLOBBER(sha1->sha1_tmpbio);
EVP_CLOBBER(sha1->sha1_pkey);
RSA_CLOBBER(sha1->sha1_rsa);
CLOBBER(sha1->sha1_rsaout);
CLOBBER(dk->dk_signinfo);
}
CLOBBER(dk->dk_sender);
CLOBBER(dk->dk_domain);
CLOBBER(dk->dk_user);
CLOBBER(dk->dk_selector);
if (dk->dk_flags & DK_FLAG_FREESIGNATURE)
CLOBBER(dk->dk_signature);
CLOBBER(dk->dk_b64);
CLOBBER(dk->dk_key);
CLOBBER(dk->dk_gran);
dk_mfree(dk->dk_libhandle, dk->dk_closure, dk);
return DK_STAT_OK;
}
syntax highlighted by Code2HTML, v. 0.9.1