/*
** Copyright (c) 2005-2007 Sendmail, Inc. and its suppliers.
** All rights reserved.
*/
#ifndef lint
static char dkim_c_id[] = "@(#)$Id: dkim.c,v 1.446 2007/12/19 17:10:00 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 <stdlib.h>
#include <ctype.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <unistd.h>
#include <resolv.h>
#include <regex.h>
#ifdef __STDC__
# include <stdarg.h>
#else /* __STDC__ */
# include <varargs.h>
#endif /* _STDC_ */
/* libsm includes */
#include <sm/string.h>
/* libar includes */
#if USE_ARLIB
# include <ar.h>
#endif /* USE_ARLIB */
/* OpenSSL includes */
#include <openssl/opensslv.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/sha.h>
/* libdkim includes */
#include "dkim.h"
#include "dkim-types.h"
#include "dkim-tables.h"
#include "dkim-keys.h"
#include "dkim-policy.h"
#include "dkim-util.h"
#include "dkim-canon.h"
#ifdef QUERY_CACHE
# include "dkim-cache.h"
#endif /* QUERY_CACHE */
#include "util.h"
#include "base64.h"
/* prototypes */
void dkim_error __P((DKIM *, const char *, ...));
/* macros */
#define DKIM_STATE_INIT 0
#define DKIM_STATE_HEADER 1
#define DKIM_STATE_EOH1 2
#define DKIM_STATE_EOH2 3
#define DKIM_STATE_BODY 4
#define DKIM_STATE_EOM1 5
#define DKIM_STATE_EOM2 6
#define DKIM_STATE_UNUSABLE 99
#ifdef _FFR_DIFFHEADERS
# define COST_INSERT 1
# define COST_DELETE 1
# define COST_SUBST 2
#endif /* _FFR_DIFFHEADERS */
#define BUFRSZ 1024
#define CRLF "\r\n"
#define SP " "
#define DEFCLOCKDRIFT 300
#define DEFTIMEOUT 10
/* 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 */
/* need fast strtoul() and strtoull()? */
#ifdef NEED_FAST_STRTOUL
# define strtoul(x,y,z) dkim_strtoul((x), (y), (z))
# define strtoull(x,y,z) dkim_strtoull((x), (y), (z))
#endif /* NEED_FAST_STRTOUL */
/* list of headers which may contain the sender */
const u_char *default_senderhdrs[] =
{
"resent-sender",
"resent-from",
"sender",
"from",
NULL
};
/* recommended list of headers to sign, from RFC4871 section 5.5 */
const u_char *should_signhdrs[] =
{
"from",
"sender",
"reply-to",
"date",
"message-id",
"to",
"cc",
"mime-version",
"content-type",
"content-transfer-encoding",
"content-id",
"content-description",
"resent-date",
"resent-from",
"resent-sender",
"resent-to",
"resent-cc",
"resent-message-id",
"in-reply-to",
"references",
"list-id",
"list-help",
"list-unsubscribe",
"list-subscribe",
"list-post",
"list-owner",
"list-archive",
NULL
};
/* recommended list of headers notto sign, from RFC4871 section 5.5 */
const u_char *should_not_signhdrs[] =
{
"return-path",
"received",
"comments",
"keywords",
"bcc",
"resent-bcc",
"dkim-signature",
NULL
};
/* required list of headers to sign */
const u_char *required_signhdrs[] =
{
"from",
NULL
};
/* ========================= PRIVATE SECTION ========================= */
/*
** DKIM_SET_FIRST -- return first set in a context
**
** Parameters:
** dkim -- DKIM context
** type -- type to find, or DKIM_SETTYPE_ANY
**
** Return value:
** Pointer to the first DKIM_SET in the context, or NULL if none.
*/
static DKIM_SET *
dkim_set_first(DKIM *dkim, dkim_set_t type)
{
DKIM_SET *set;
assert(dkim != NULL);
if (type == DKIM_SETTYPE_ANY)
return dkim->dkim_sethead;
for (set = dkim->dkim_sethead; set != NULL; set = set->set_next)
{
if (set->set_type == type)
return set;
}
return NULL;
}
/*
** DKIM_SET_NEXT -- return next set in a context
**
** Parameters:
** set -- last set reported (i.e. starting point for this search)
** type -- type to find, or DKIM_SETTYPE_ANY
**
** Return value:
** Pointer to the next DKIM_SET in the context, or NULL if none.
*/
static DKIM_SET *
dkim_set_next(DKIM_SET *cur, dkim_set_t type)
{
DKIM_SET *set;
assert(cur != NULL);
if (type == DKIM_SETTYPE_ANY)
return cur->set_next;
for (set = cur->set_next; set != NULL; set = set->set_next)
{
if (set->set_type == type)
return set;
}
return NULL;
}
/*
** DKIM_PARAM_GET -- get a parameter from a set
**
** Parameters:
** set -- set to search
** param -- parameter to find
**
** Return value:
** Pointer to the parameter requested, or NULL if it's not in the set.
*/
static u_char *
dkim_param_get(DKIM_SET *set, u_char *param)
{
DKIM_PLIST *plist;
assert(set != NULL);
assert(param != NULL);
for (plist = set->set_plist; plist != NULL; plist = plist->plist_next)
{
if (strcmp(plist->plist_param, param) == 0)
return plist->plist_value;
}
return NULL;
}
/*
** DKIM_ADD_PLIST -- add an entry to a parameter-value set
**
** Parameters:
** dkim -- DKIM context in which this is performed
** set -- set to modify
** param -- parameter
** value -- value
** force -- override existing value, if any
**
** Return value:
** 0 on success, -1 on failure.
**
** Notes:
** Data is not copied; a reference to it is stored.
*/
static int
dkim_add_plist(DKIM *dkim, DKIM_SET *set, u_char *param, u_char *value,
bool force)
{
DKIM_PLIST *plist;
assert(dkim != NULL);
assert(set != NULL);
assert(param != NULL);
assert(value != NULL);
/* see if we have one already */
for (plist = set->set_plist; plist != NULL; plist = plist->plist_next)
{
if (strcasecmp(plist->plist_param, param) == 0)
break;
}
/* nope; make one and connect it */
if (plist == NULL)
{
plist = (DKIM_PLIST *) DKIM_MALLOC(dkim, sizeof(DKIM_PLIST));
if (plist == NULL)
{
dkim_error(dkim, "unable to allocate %d byte(s)",
sizeof(DKIM_PLIST));
return -1;
}
force = TRUE;
plist->plist_next = set->set_plist;
set->set_plist = plist;
plist->plist_param = param;
}
/* set the value if "force" was set (or this was a new entry) */
if (force)
plist->plist_value = value;
return 0;
}
/*
** DKIM_PROCESS_SET -- process a parameter set, i.e. a string of the form
** param=value[; param=value]*
**
** Parameters:
** dkim -- DKIM context in which this is performed
** type -- a DKIM_SETTYPE constant
** str -- string to be scanned
** len -- number of bytes available at "str"
** udata -- arbitrary user data (not used)
**
** Return value:
** A DKIM_STAT constant.
*/
static DKIM_STAT
dkim_process_set(DKIM *dkim, dkim_set_t type, u_char *str, size_t len,
void *udata)
{
bool spaced;
int state;
int status;
u_char *p;
u_char *param;
u_char *value;
u_char *hcopy;
DKIM_SET *set;
const char *settype;
assert(dkim != NULL);
assert(str != NULL);
assert(type == DKIM_SETTYPE_SIGNATURE ||
type == DKIM_SETTYPE_KEY ||
type == DKIM_SETTYPE_POLICY);
param = NULL;
value = NULL;
state = 0;
spaced = FALSE;
hcopy = (u_char *) DKIM_MALLOC(dkim, len + 1);
if (hcopy == NULL)
{
dkim_error(dkim, "unable to allocate %d byte(s)", len + 1);
return DKIM_STAT_INTERNAL;
}
sm_strlcpy(hcopy, str, len + 1);
set = (DKIM_SET *) DKIM_MALLOC(dkim, sizeof(DKIM_SET));
if (set == NULL)
{
DKIM_FREE(dkim, hcopy);
dkim_error(dkim, "unable to allocate %d byte(s)",
sizeof(DKIM_SET));
return DKIM_STAT_INTERNAL;
}
set->set_type = type;
settype = dkim_code_to_name(settypes, type);
if (dkim->dkim_sethead == NULL)
dkim->dkim_sethead = set;
else
dkim->dkim_settail->set_next = set;
dkim->dkim_settail = set;
set->set_next = NULL;
set->set_plist = NULL;
set->set_data = hcopy;
set->set_udata = udata;
set->set_bad = FALSE;
for (p = hcopy; *p != '\0'; p++)
{
if (!isascii(*p) || (!isprint(*p) && !isspace(*p)))
{
dkim_error(dkim,
"invalid character (0x%02x) in %s data",
*p, settype);
set->set_bad = TRUE;
return DKIM_STAT_SYNTAX;
}
switch (state)
{
case 0: /* before param */
if (isspace(*p))
{
continue;
}
else if (isalnum(*p))
{
param = p;
state = 1;
}
else
{
dkim_error(dkim, "syntax error in %s data",
settype);
set->set_bad = TRUE;
return DKIM_STAT_SYNTAX;
}
break;
case 1: /* in param */
if (isspace(*p))
{
spaced = TRUE;
}
else if (*p == '=')
{
*p = '\0';
state = 2;
spaced = FALSE;
}
else if (*p == ';' || spaced)
{
dkim_error(dkim, "syntax error in %s data",
settype);
set->set_bad = TRUE;
return DKIM_STAT_SYNTAX;
}
break;
case 2: /* before value */
if (isspace(*p))
{
continue;
}
else if (*p == ';') /* empty value */
{
*p = '\0';
value = p;
/* collapse the parameter */
dkim_collapse(param);
/* create the DKIM_PLIST entry */
status = dkim_add_plist(dkim, set, param,
value, TRUE);
if (status == -1)
{
set->set_bad = TRUE;
return DKIM_STAT_INTERNAL;
}
/* reset */
param = NULL;
value = NULL;
state = 0;
}
else
{
value = p;
state = 3;
}
break;
case 3: /* in value */
if (*p == ';')
{
*p = '\0';
/* collapse the parameter and value */
dkim_collapse(param);
dkim_collapse(value);
/* create the DKIM_PLIST entry */
status = dkim_add_plist(dkim, set, param,
value, TRUE);
if (status == -1)
{
set->set_bad = TRUE;
return DKIM_STAT_INTERNAL;
}
/* reset */
param = NULL;
value = NULL;
state = 0;
}
break;
default: /* shouldn't happen */
assert(0);
}
}
switch (state)
{
case 0: /* before param */
case 3: /* in value */
/* parse the data found, if any */
if (value != NULL)
{
/* collapse the parameter and value */
dkim_collapse(param);
dkim_collapse(value);
/* create the DKIM_PLIST entry */
status = dkim_add_plist(dkim, set, param, value, TRUE);
if (status == -1)
{
set->set_bad = TRUE;
return DKIM_STAT_INTERNAL;
}
}
break;
case 1: /* after param */
case 2: /* before value */
dkim_error(dkim, "syntax error in %s data", settype);
set->set_bad = TRUE;
return DKIM_STAT_SYNTAX;
default: /* shouldn't happen */
assert(0);
}
/* load up defaults, assert requirements */
switch (set->set_type)
{
case DKIM_SETTYPE_SIGNATURE:
/* make sure required stuff is here */
if (dkim_param_get(set, "s") == NULL ||
dkim_param_get(set, "h") == NULL ||
dkim_param_get(set, "d") == NULL ||
dkim_param_get(set, "b") == NULL ||
dkim_param_get(set, "v") == NULL ||
dkim_param_get(set, "a") == NULL)
{
dkim_error(dkim, "missing parameter(s) in %s data",
settype);
set->set_bad = TRUE;
return DKIM_STAT_SYNTAX;
}
/* default for "c" */
status = dkim_add_plist(dkim, set, "c", "simple/simple", FALSE);
if (status == -1)
{
set->set_bad = TRUE;
return DKIM_STAT_INTERNAL;
}
/* default for "q" */
status = dkim_add_plist(dkim, set, "q", "dns/txt", FALSE);
if (status == -1)
{
set->set_bad = TRUE;
return DKIM_STAT_INTERNAL;
}
/* test validity of "t" and "x" */
value = dkim_param_get(set, "t");
if (value != NULL)
{
unsigned long long tmp = 0;
char *end;
errno = 0;
if (value[0] == '-')
{
errno = ERANGE;
tmp = 0;
}
else
{
tmp = strtoull(value, &end, 10);
}
if (tmp == ULLONG_MAX || errno != 0 || *end != '\0')
{
dkim_error(dkim,
"invalid \"t\" value in %s data",
settype);
set->set_bad = TRUE;
return DKIM_STAT_SYNTAX;
}
}
value = dkim_param_get(set, "x");
if (value != NULL)
{
unsigned long long tmp = 0;
char *end;
errno = 0;
if (value[0] == '-')
{
errno = ERANGE;
tmp = 0;
}
else
{
tmp = strtoull(value, &end, 10);
}
if (tmp == ULLONG_MAX || errno != 0 || *end != '\0')
{
dkim_error(dkim,
"invalid \"x\" value in %s data",
settype);
set->set_bad = TRUE;
return DKIM_STAT_SYNTAX;
}
}
break;
case DKIM_SETTYPE_POLICY:
if (dkim_param_get(set, "dkim") == NULL)
{
dkim_error(dkim, "missing parameter(s) in %s data",
settype);
set->set_bad = TRUE;
return DKIM_STAT_SYNTAX;
}
break;
case DKIM_SETTYPE_KEY:
status = dkim_add_plist(dkim, set, "g", "*", FALSE);
if (status == -1)
{
set->set_bad = TRUE;
return DKIM_STAT_INTERNAL;
}
status = dkim_add_plist(dkim, set, "k", "rsa", FALSE);
if (status == -1)
{
set->set_bad = TRUE;
return DKIM_STAT_INTERNAL;
}
status = dkim_add_plist(dkim, set, "s", "*", FALSE);
if (status == -1)
{
set->set_bad = TRUE;
return DKIM_STAT_INTERNAL;
}
status = dkim_add_plist(dkim, set, "v", DKIM_VERSION_KEY,
FALSE);
if (status == -1)
{
set->set_bad = TRUE;
return DKIM_STAT_INTERNAL;
}
break;
default:
assert(0);
}
return DKIM_STAT_OK;
}
/*
** DKIM_SET_GETUDATA -- retrieve user data associated with a set
**
** Parameters:
** set -- a DKIM_SET handle
**
** Return value:
** Stored opaque handle, if any; NULL otherwise.
*/
static void *
dkim_set_getudata(DKIM_SET *set)
{
assert(set != NULL);
return set->set_udata;
}
/*
** DKIM_GET_HEADER -- find a header in a queue of headers
**
** Parameters:
** dkim -- DKIM handle
** name -- name of the header to find
** inst -- instance to find (0 == first/any)
**
** Return value:
** Pointer to a (struct dkim_header), or NULL if not found.
*/
static struct dkim_header *
dkim_get_header(DKIM *dkim, u_char *name, size_t namelen, int inst)
{
struct dkim_header *hdr;
assert(dkim != NULL);
assert(name != NULL);
for (hdr = dkim->dkim_hhead; hdr != NULL; hdr = hdr->hdr_next)
{
if (hdr->hdr_namelen == namelen &&
strncasecmp(hdr->hdr_text, name, namelen) == 0)
{
if (inst == 0)
return hdr;
else
inst--;
}
}
return NULL;
}
/*
** DKIM_KEY_SMTP -- return TRUE iff a parameter set defines an SMTP key
**
** Parameters:
** set -- set to be checked
**
** Return value:
** TRUE iff "set" contains an "s" parameter whose value is either
** "email" or "*".
*/
static bool
dkim_key_smtp(DKIM_SET *set)
{
u_char *val;
char *last;
u_char *p;
char buf[BUFRSZ + 1];
assert(set != NULL);
assert(set->set_type == DKIM_SETTYPE_KEY);
val = dkim_param_get(set, "s");
if (val == NULL)
return TRUE;
sm_strlcpy(buf, val, sizeof buf);
for (p = strtok_r(buf, ":", &last);
p != NULL;
p = strtok_r(NULL, ":", &last))
{
if (strcmp(p, "*") == 0 ||
strcasecmp(p, "email") == 0)
return TRUE;
}
return FALSE;
}
/*
** DKIM_KEY_GRANOK -- return TRUE iff the granularity of the key is
** appropriate to the signature being evaluated
**
** Parameters:
** sig -- DKIM_SIGINFO handle
** gran -- granularity string from the retrieved key
** user -- sending userid
**
** Return value:
** TRUE iff the value of the granularity is a match for the signer.
*/
static bool
dkim_key_granok(DKIM_SIGINFO *sig, u_char *gran, char *user)
{
int status;
DKIM_SET *set;
char *at;
char *end;
u_char *p;
char *q;
char restr[MAXADDRESS + 1];
char cmp[MAXADDRESS + 1];
regex_t re;
assert(sig != NULL);
assert(gran != NULL);
assert(user != NULL);
memset(cmp, '\0', sizeof cmp);
/* if it's empty, it matches nothing */
if (gran[0] == '\0')
return FALSE;
/* if it's just "*", it matches everything */
if (gran[0] == '*' && gran[1] == '\0')
return TRUE;
/* ensure we're evaluating against a signature data set */
set = sig->sig_taglist;
assert(set != NULL);
assert(set->set_type == DKIM_SETTYPE_SIGNATURE);
/* get the value of the "i" parameter */
p = dkim_param_get(set, "i");
/* validate the "i" parameter */
if (p != NULL)
dkim_qp_decode(p, cmp, sizeof cmp);
at = strchr(cmp, '@');
if (at == NULL || at == cmp)
sm_strlcpy(cmp, user, sizeof cmp);
else
*at = '\0';
/* if it's not wildcarded, enforce an exact match */
if (strchr(gran, '*') == NULL)
return (strcmp(gran, user) == 0);
/* evaluate the wildcard */
end = restr + sizeof restr;
memset(restr, '\0', sizeof restr);
restr[0] = '^';
for (p = gran, q = restr + 1; *p != '\0' && q < end - 2; p++)
{
if (isascii(*p) && ispunct(*p))
{
if (*p == '*')
{
*q++ = '.';
*q++ = '*';
}
else
{
*q++ = '\\';
*q++ = *p;
}
}
else
{
*q++ = *p;
}
}
if (sm_strlcat(restr, "$", sizeof restr) >= sizeof restr)
return FALSE;
status = regcomp(&re, restr, 0);
if (status != 0)
return FALSE;
status = regexec(&re, user, 0, NULL, 0);
(void) regfree(&re);
return (status == 0 ? TRUE : FALSE);
}
/*
** DKIM_KEY_HASHOK -- return TRUE iff a signature's hash is in the approved
** list of hashes for a given key
**
** Parameters:
** sig -- DKIM_SIGINFO handle
** hashlist -- colon-separated approved hash list
**
** Return value:
** TRUE iff a particular hash is in the approved list of hashes.
*/
static bool
dkim_key_hashok(DKIM_SIGINFO *sig, u_char *hashlist)
{
int hashalg;
u_char *x, *y;
u_char tmp[BUFRSZ + 1];
assert(sig != NULL);
if (hashlist == NULL)
return TRUE;
x = NULL;
memset(tmp, '\0', sizeof tmp);
y = hashlist;
for (;;)
{
if (*y == ':' || *y == '\0')
{
if (x != NULL)
{
sm_strlcpy(tmp, x, sizeof tmp);
tmp[y - x] = '\0';
hashalg = dkim_name_to_code(hashes, tmp);
if (hashalg == sig->sig_hashtype)
return TRUE;
}
x = NULL;
}
else if (x == NULL)
{
x = y;
}
if (*y == '\0')
return FALSE;
y++;
}
/* NOTREACHED */
}
/*
** DKIM_KEY_HASHESOK -- return TRUE iff this key supports at least one
** hash method we know about (or doesn't specify)
**
** Parameters:
** hashlist -- colon-separated list of hashes (or NULL)
**
** Return value:
** TRUE iff this key supports at least one hash method we know about
** (or doesn't specify)
*/
static bool
dkim_key_hashesok(u_char *hashlist)
{
u_char *x, *y;
u_char tmp[BUFRSZ + 1];
if (hashlist == NULL)
return TRUE;
x = NULL;
memset(tmp, '\0', sizeof tmp);
y = hashlist;
for (;;)
{
if (*y == ':' || *y == '\0')
{
if (x != NULL)
{
sm_strlcpy(tmp, x, sizeof tmp);
tmp[y - x] = '\0';
if (dkim_name_to_code(hashes, tmp) != -1)
return TRUE;
}
x = NULL;
}
else if (x == NULL)
{
x = y;
}
if (*y == '\0')
return FALSE;
y++;
}
/* NOTREACHED */
}
#if 0
/*
** DKIM_SIG_SIGNEROK -- return TRUE iff the signer is specified in a signed
** sender header
**
** Parameters:
** dkim -- DKIM handle
** set -- signature set to be checked
** hdrs -- names of sender headers
**
** Return value:
** TRUE iff the value of the "i" parameter appears in a signed sender
** header.
**
** Note:
** This essentially detects third-party signatures. It's not of use
** yet until SSP addresses this question.
*/
static bool
dkim_sig_signerok(DKIM *dkim, DKIM_SET *set, u_char **hdrs)
{
int status;
int c;
int clen;
struct dkim_header *cur;
u_char *colon;
char *i;
char *user;
char *domain;
char buf[MAXADDRESS + 1];
char addr[MAXADDRESS + 1];
char signer[MAXADDRESS + 1];
assert(dkim != NULL);
assert(set != NULL);
assert(set->set_type == DKIM_SETTYPE_SIGNATURE);
i = dkim_param_get(set, "i");
assert(i != NULL);
dkim_qp_decode(i, signer, sizeof signer);
/* for each header in the "sender header" list */
for (c = 0; hdrs[c] != NULL; c++)
{
/* for each header in the message */
for (cur = dkim->dkim_hhead; cur != NULL; cur = cur->hdr_next)
{
/* skip unsigned headers */
if ((cur->hdr_flags & DKIM_HDR_SIGNED) == 0)
continue;
/* determine header name size */
colon = strchr(cur->hdr_text, ':');
if (colon == NULL)
clen = strlen(cur->hdr_text);
else
clen = colon - cur->hdr_text;
/* if this is a sender header */
if (strncasecmp(hdrs[c], cur->hdr_text, clen) == 0)
{
if (colon == NULL)
colon = cur->hdr_text;
else
colon += 1;
sm_strlcpy(buf, colon, sizeof buf);
status = rfc2822_mailbox_split(buf, &user,
&domain);
if (status != 0 || domain == NULL ||
user == NULL || user[0] == '\0' ||
domain[0] == '\0')
continue;
snprintf(addr, sizeof addr, "%s@%s",
user, domain);
/* see if the sender matches "i" */
if (dkim_addrcmp(addr, signer) == 0)
return TRUE;
}
}
}
return FALSE;
}
#endif /* 0 */
/*
** DKIM_SIG_DOMAINOK -- return TRUE iff a signature appears to have valid
** domain correlation; that is, "i" must be the same
** domain as or a subdomain of "d"
**
** Parameters:
** dkim -- DKIM handle
** set -- signature set to be checked
**
** Return value:
** TRUE iff the "i" parameter and the "d" parameter match up.
*/
static bool
dkim_sig_domainok(DKIM *dkim, DKIM_SET *set)
{
char *at;
char *dot;
char *i;
char *d;
char addr[MAXADDRESS + 1];
assert(dkim != NULL);
assert(set != NULL);
assert(set->set_type == DKIM_SETTYPE_SIGNATURE);
i = dkim_param_get(set, "i");
d = dkim_param_get(set, "d");
assert(d != NULL);
memset(addr, '\0', sizeof addr);
if (i == NULL)
snprintf(addr, sizeof addr, "@%s", d);
else
dkim_qp_decode(i, addr, sizeof addr);
at = strchr(addr, '@');
if (at == NULL)
return FALSE;
if (strcasecmp(at + 1, d) == 0)
return TRUE;
for (dot = strchr(at, '.'); dot != NULL; dot = strchr(dot + 1, '.'))
{
if (strcasecmp(dot + 1, d) == 0)
{
dkim->dkim_subdomain = TRUE;
return TRUE;
}
}
return FALSE;
}
/*
** DKIM_SIG_EXPIRED -- return TRUE iff a signature appears to have expired
**
** Parameters:
** set -- signature set to be checked
** drift -- seconds of drift allowed
**
** Return value:
** TRUE iff "set" contains an "x=" parameter which indicates a time
** which has passed.
**
** Notes:
** Syntax is not checked here. It's checked in dkim_process_set().
*/
static bool
dkim_sig_expired(DKIM_SET *set, time_t drift)
{
unsigned long long expire;
unsigned long long nowl;
time_t now;
u_char *val;
assert(set != NULL);
assert(set->set_type == DKIM_SETTYPE_SIGNATURE);
val = dkim_param_get(set, "x");
if (val == NULL)
return FALSE;
expire = strtoull(val, NULL, 10);
(void) time(&now);
nowl = (unsigned long long) now;
return (nowl >= expire + (unsigned long long) drift);
}
/*
** DKIM_SIG_TIMESTAMPSOK -- return TRUE iff a signature appears to have
** both a timestamp and an expiration date and they
** are properly ordered
**
** Parameters:
** set -- signature set to be checked
**
** Return value:
** TRUE: - "set" contains both a "t=" parameter and an "x=" parameter
** and the latter is greater than the former
** - "set" is missing either "t=" or "x=" (or both)
** FALSE: otherwise
**
** Notes:
** Syntax is not checked here. It's checked in dkim_process_set().
*/
static bool
dkim_sig_timestampsok(DKIM_SET *set)
{
unsigned long long signtime;
unsigned long long expire;
u_char *val;
assert(set != NULL);
assert(set->set_type == DKIM_SETTYPE_SIGNATURE);
val = dkim_param_get(set, "t");
if (val == NULL)
return TRUE;
signtime = strtoull(val, NULL, 10);
val = dkim_param_get(set, "x");
if (val == NULL)
return TRUE;
expire = strtoull(val, NULL, 10);
return (signtime < expire);
}
/*
** DKIM_SIG_FUTURE -- return TRUE iff a signature appears to have been
** generated in the future
**
** Parameters:
** set -- signature set to be checked
** drift -- seconds of drift allowed
**
** Return value:
** TRUE iff "set" contains a "t=" parameter which indicates a time
** in the future.
**
** Notes:
** Syntax is not checked here. It's checked in dkim_process_set().
*/
static bool
dkim_sig_future(DKIM_SET *set, time_t drift)
{
time_t signtime;
time_t now;
u_char *val;
assert(set != NULL);
assert(set->set_type == DKIM_SETTYPE_SIGNATURE);
val = dkim_param_get(set, "t");
if (val == NULL)
return FALSE;
signtime = strtoul(val, NULL, 10);
(void) time(&now);
return (now < signtime - drift);
}
/*
** DKIM_SIG_VERSIONOK -- return TRUE iff a signature appears to have a version
** we can accept
**
** Parameters:
** dkim -- DKIM handle
** set -- signature set to be checked
**
** Return value:
** TRUE iff "set" appears to be based on a version of DKIM that is
** supported by this API.
*/
static bool
dkim_sig_versionok(DKIM *dkim, DKIM_SET *set)
{
char *v;
assert(set != NULL);
assert(set->set_type == DKIM_SETTYPE_SIGNATURE);
v = dkim_param_get(set, "v");
assert(v != NULL);
/* check for DKIM_VERSION_SIG */
if (strcmp(v, DKIM_VERSION_SIG) == 0)
return TRUE;
/* check for DKIM_VERSION_SIGOLD if allowed */
if ((dkim->dkim_libhandle->dkiml_flags & DKIM_LIBFLAGS_ACCEPTV05) &&
strcmp(v, DKIM_VERSION_SIGOLD) == 0)
return TRUE;
return FALSE;
}
/*
** DKIM_SIGLIST_SETUP -- create a signature list and load the elements
**
** Parameters:
** dkim -- DKIM handle
**
** Return value:
** A DKIM_STAT_* constant.
*/
static DKIM_STAT
dkim_siglist_setup(DKIM *dkim)
{
int c;
int hashtype;
size_t b64siglen;
size_t len;
DKIM_STAT status;
off_t signlen = (off_t) -1;
time_t drift;
dkim_canon_t bodycanon;
dkim_canon_t hdrcanon;
dkim_alg_t signalg;
DKIM_SET *set;
DKIM_LIB *lib;
DKIM_CANON *hc;
DKIM_CANON *bc;
u_char *param;
u_char *hdrlist;
assert(dkim != NULL);
lib = dkim->dkim_libhandle;
drift = lib->dkiml_clockdrift;
len = dkim->dkim_sigcount * sizeof(DKIM_SIGINFO *);
dkim->dkim_siglist = DKIM_MALLOC(dkim, len);
if (dkim->dkim_siglist == NULL)
{
dkim_error(dkim, "unable to allocate %d byte(s)", len);
return DKIM_STAT_NORESOURCE;
}
/* allocate the siginfo elements */
for (c = 0; c < dkim->dkim_sigcount; c++)
{
dkim->dkim_siglist[c] = DKIM_MALLOC(dkim,
sizeof(DKIM_SIGINFO));
if (dkim->dkim_siglist[c] == NULL)
{
int n;
dkim_error(dkim,
"unable to allocate %d byte(s)",
sizeof(DKIM_SIGINFO));
for (n = 0; n < c; n++)
DKIM_FREE(dkim, dkim->dkim_siglist[n]);
return DKIM_STAT_NORESOURCE;
}
memset(dkim->dkim_siglist[c], '\0', sizeof(DKIM_SIGINFO));
}
/* populate the elements */
for (set = dkim_set_first(dkim, DKIM_SETTYPE_SIGNATURE), c = 0;
set != NULL && c < dkim->dkim_sigcount;
set = dkim_set_next(set, DKIM_SETTYPE_SIGNATURE), c++)
{
/* cope with bad ones */
if (set->set_bad)
{
c--;
continue;
}
/* store the set */
dkim->dkim_siglist[c]->sig_taglist = set;
/* some basic checks first */
if (!dkim_sig_versionok(dkim, set))
{
dkim->dkim_siglist[c]->sig_error = DKIM_SIGERROR_VERSION;
continue;
}
else if (!dkim_sig_domainok(dkim, set))
{
dkim->dkim_siglist[c]->sig_error = DKIM_SIGERROR_DOMAIN;
continue;
}
else if (dkim_sig_expired(set, drift))
{
dkim->dkim_siglist[c]->sig_error = DKIM_SIGERROR_EXPIRED;
continue;
}
else if (dkim_sig_future(set, drift))
{
dkim->dkim_siglist[c]->sig_error = DKIM_SIGERROR_FUTURE;
continue;
}
else if (!dkim_sig_timestampsok(set))
{
dkim->dkim_siglist[c]->sig_error = DKIM_SIGERROR_TIMESTAMPS;
continue;
}
/* determine canonicalizations */
param = dkim_param_get(set, "c");
if (param == NULL)
{
dkim->dkim_siglist[c]->sig_error = DKIM_SIGERROR_MISSING_C;
continue;
}
else
{
char *q;
char value[BUFRSZ + 1];
sm_strlcpy(value, param, sizeof value);
q = strchr(value, '/');
if (q != NULL)
*q = '\0';
hdrcanon = dkim_name_to_code(canonicalizations, value);
if (hdrcanon == -1)
{
dkim->dkim_siglist[c]->sig_error = DKIM_SIGERROR_INVALID_HC;
continue;
}
if (q == NULL)
{
bodycanon = DKIM_CANON_SIMPLE;
}
else
{
bodycanon = dkim_name_to_code(canonicalizations,
q + 1);
if (bodycanon == -1)
{
dkim->dkim_siglist[c]->sig_error = DKIM_SIGERROR_INVALID_BC;
continue;
}
}
}
/* determine hash type */
param = dkim_param_get(set, "a");
if (param == NULL)
{
dkim->dkim_siglist[c]->sig_error = DKIM_SIGERROR_MISSING_A;
continue;
}
else
{
signalg = dkim_name_to_code(algorithms, param);
if (signalg == -1)
{
dkim->dkim_siglist[c]->sig_error = DKIM_SIGERROR_INVALID_A;
continue;
}
switch (signalg)
{
case DKIM_SIGN_RSASHA1:
hashtype = DKIM_HASHTYPE_SHA1;
break;
#ifdef DKIM_SIGN_RSASHA256
case DKIM_SIGN_RSASHA256:
hashtype = DKIM_HASHTYPE_SHA256;
break;
#endif /* DKIM_SIGN_RSASHA256 */
default:
assert(0);
/* NOTREACHED */
}
dkim->dkim_siglist[c]->sig_signalg = signalg;
dkim->dkim_siglist[c]->sig_hashtype = hashtype;
}
/* determine header list */
param = dkim_param_get(set, "h");
if (param == NULL)
{
dkim->dkim_siglist[c]->sig_error = DKIM_SIGERROR_MISSING_H;
continue;
}
hdrlist = param;
/* determine signing length */
param = dkim_param_get(set, "l");
if (param != NULL)
{
char *q;
errno = 0;
if (param[0] == '-')
{
errno = ERANGE;
signlen = ULONG_MAX;
}
else
{
signlen = (off_t) strtoul(param, &q, 10);
}
if (signlen == ULONG_MAX || errno != 0 || *q != '\0')
{
dkim->dkim_siglist[c]->sig_error = DKIM_SIGERROR_INVALID_L;
continue;
}
}
/* query method */
param = dkim_param_get(set, "q");
if (param != NULL)
{
bool bad_qo = FALSE;
dkim_query_t q = (dkim_query_t) -1;
u_char *p;
char *last;
u_char *opts;
u_char tmp[BUFRSZ + 1];
u_char qtype[BUFRSZ + 1];
sm_strlcpy(qtype, param, sizeof qtype);
for (p = strtok_r(qtype, ":", &last);
p != NULL;
p = strtok_r(NULL, ":", &last))
{
opts = strchr(p, '/');
if (opts != NULL)
{
sm_strlcpy(tmp, p, sizeof tmp);
opts = strchr(tmp, '/');
*opts = '\0';
opts++;
p = tmp;
}
/* unknown type */
q = dkim_name_to_code(querytypes, p);
if (q == (dkim_query_t) -1)
continue;
if (q == DKIM_QUERY_DNS)
{
/* "txt" option required (also default) */
if (opts != NULL &&
strcmp(opts, "txt") != 0)
{
bad_qo = TRUE;
continue;
}
}
break;
}
if (q == (dkim_query_t) -1)
{
dkim->dkim_siglist[c]->sig_error = DKIM_SIGERROR_INVALID_Q;
continue;
}
else if (bad_qo)
{
dkim->dkim_siglist[c]->sig_error = DKIM_SIGERROR_INVALID_QO;
continue;
}
dkim->dkim_siglist[c]->sig_query = q;
}
/* timestamp */
param = dkim_param_get(set, "t");
if (param == NULL)
{
dkim->dkim_siglist[c]->sig_timestamp = 0;
}
else
{
dkim->dkim_siglist[c]->sig_timestamp = strtoull(param,
NULL,
10);
}
if (lib->dkiml_querymethod != DKIM_QUERY_UNKNOWN)
dkim->dkim_siglist[c]->sig_query = lib->dkiml_querymethod;
/* signing domain */
param = dkim_param_get(set, "d");
if (param == NULL)
{
dkim->dkim_siglist[c]->sig_error = DKIM_SIGERROR_MISSING_D;
continue;
}
else if (param[0] == '\0')
{
dkim->dkim_siglist[c]->sig_error = DKIM_SIGERROR_EMPTY_D;
continue;
}
dkim->dkim_siglist[c]->sig_domain = param;
/* selector */
param = dkim_param_get(set, "s");
if (param == NULL)
{
dkim->dkim_siglist[c]->sig_error = DKIM_SIGERROR_MISSING_S;
continue;
}
else if (param[0] == '\0')
{
dkim->dkim_siglist[c]->sig_error = DKIM_SIGERROR_EMPTY_S;
continue;
}
dkim->dkim_siglist[c]->sig_selector = param;
/* body hash */
param = dkim_param_get(set, "bh");
if (param == NULL)
{
dkim->dkim_siglist[c]->sig_error = DKIM_SIGERROR_MISSING_BH;
continue;
}
else if (param[0] == '\0')
{
dkim->dkim_siglist[c]->sig_error = DKIM_SIGERROR_EMPTY_BH;
continue;
}
/* signature */
param = dkim_param_get(set, "b");
if (param == NULL)
{
dkim->dkim_siglist[c]->sig_error = DKIM_SIGERROR_MISSING_B;
continue;
}
else if (param[0] == '\0')
{
dkim->dkim_siglist[c]->sig_error = DKIM_SIGERROR_EMPTY_B;
continue;
}
b64siglen = strlen(param);
dkim->dkim_siglist[c]->sig_sig = DKIM_MALLOC(dkim,
b64siglen);
if (dkim->dkim_siglist[c]->sig_sig == NULL)
{
dkim_error(dkim,
"unable to allocate %d byte(s)",
b64siglen);
return DKIM_STAT_NORESOURCE;
}
status = dkim_base64_decode(param,
dkim->dkim_siglist[c]->sig_sig,
b64siglen);
if (status < 0)
{
dkim->dkim_siglist[c]->sig_error = DKIM_SIGERROR_CORRUPT_B;
continue;
}
else
{
dkim->dkim_siglist[c]->sig_siglen = status;
}
/* canonicalization handle for the headers */
status = dkim_add_canon(dkim, TRUE, hdrcanon, hashtype,
hdrlist, dkim_set_getudata(set),
0, &hc);
if (status != DKIM_STAT_OK)
return status;
dkim->dkim_siglist[c]->sig_hdrcanon = hc;
dkim->dkim_siglist[c]->sig_hdrcanonalg = hdrcanon;
/* canonicalization handle for the body */
status = dkim_add_canon(dkim, FALSE, bodycanon,
hashtype, NULL, NULL, signlen,
&bc);
if (status != DKIM_STAT_OK)
return status;
dkim->dkim_siglist[c]->sig_bodycanon = bc;
dkim->dkim_siglist[c]->sig_bodycanonalg = bodycanon;
/* the rest */
dkim->dkim_siglist[c]->sig_bh = DKIM_SIGBH_UNTESTED;
dkim->dkim_siglist[c]->sig_flags = 0;
/* allow the user to generate its handle */
if (lib->dkiml_sig_handle != NULL)
dkim->dkim_siglist[c]->sig_context = lib->dkiml_sig_handle(dkim->dkim_closure);
/* populate the user handle */
if (lib->dkiml_sig_tagvalues != NULL)
{
dkim_param_t pcode;
struct dkim_plist *plist;
void *user;
user = dkim->dkim_siglist[c]->sig_context;
for (plist = set->set_plist;
plist != NULL;
plist = plist->plist_next)
{
pcode = dkim_name_to_code(sigparams,
plist->plist_param);
(void) lib->dkiml_sig_tagvalues(user,
pcode,
plist->plist_param,
plist->plist_value);
}
}
}
return DKIM_STAT_OK;
}
/*
** DKIM_GENSIGHDR -- generate a signature header
**
** Parameters:
** dkim -- DKIM handle
** sig -- DKIM_SIGINFO handle
** buf -- where to write
** buflen -- bytes available at "buf"
** delim -- delimiter
**
** Return value:
** Number of bytes written to "buf".
*/
static size_t
dkim_gensighdr(DKIM *dkim, DKIM_SIGINFO *sig, u_char *buf, size_t buflen,
char *delim)
{
bool firsthdr;
int n;
int status;
size_t hashlen;
u_char *hash;
u_char *colon;
struct dkim_header *hdr;
bool *always = NULL;
u_char tmp[DKIM_MAXHEADER + 1];
u_char tmphdr[DKIM_MAXHEADER + 1];
char b64hash[DKIM_MAXHEADER + 1];
assert(dkim != NULL);
assert(sig != NULL);
assert(buf != NULL);
assert(delim != NULL);
memset(tmphdr, '\0', sizeof tmphdr);
n = dkim->dkim_hdrcnt * sizeof(bool);
always = DKIM_MALLOC(dkim, n);
memset(always, '\0', n);
/*
** We need to generate a DKIM-Signature: header template
** and include it in the canonicalization.
*/
/* basic required stuff */
snprintf(tmphdr, sizeof tmphdr,
"v=%s;%sa=%s;%sc=%s/%s;%sd=%s;%ss=%s;%st=%llu",
DKIM_VERSION_SIG, delim,
dkim_code_to_name(algorithms, sig->sig_signalg),
delim,
dkim_code_to_name(canonicalizations, sig->sig_hdrcanonalg),
dkim_code_to_name(canonicalizations, sig->sig_bodycanonalg),
delim,
sig->sig_domain, delim,
sig->sig_selector, delim,
sig->sig_timestamp);
if (dkim->dkim_libhandle->dkiml_sigttl != 0)
{
unsigned long long expire;
expire = sig->sig_timestamp + (unsigned long long) dkim->dkim_libhandle->dkiml_sigttl;
snprintf(tmp, sizeof tmp, ";%sx=%llu", delim, expire);
sm_strlcat(tmphdr, tmp, sizeof tmphdr);
}
if (dkim->dkim_signer != NULL)
{
snprintf(tmp, sizeof tmp, ";%si=%s", delim, dkim->dkim_signer);
sm_strlcat(tmphdr, tmp, sizeof tmphdr);
}
memset(b64hash, '\0', sizeof b64hash);
(void) dkim_canon_closebody(dkim);
(void) dkim_canon_getfinal(sig->sig_bodycanon, &hash, &hashlen);
status = dkim_base64_encode(hash, hashlen,
b64hash, sizeof b64hash);
snprintf(tmp, sizeof tmp, ";%sbh=%s", delim,
b64hash);
sm_strlcat(tmphdr, tmp, sizeof tmphdr);
/* l= */
if (dkim->dkim_partial)
{
snprintf(tmp, sizeof tmp, ";%sl=%lu", delim,
(u_long) sig->sig_bodycanon->canon_wrote);
sm_strlcat(tmphdr, tmp, sizeof tmphdr);
}
/* h= */
for (n = 0; n < dkim->dkim_hdrcnt; n++)
always[n] = TRUE;
firsthdr = TRUE;
for (hdr = dkim->dkim_hhead; hdr != NULL; hdr = hdr->hdr_next)
{
if ((hdr->hdr_flags & DKIM_HDR_SIGNED) == 0)
continue;
memset(tmp, '\0', sizeof tmp);
strncpy(tmp, hdr->hdr_text,
MIN(DKIM_MAXHEADER, hdr->hdr_namelen));
if (!firsthdr)
{
sm_strlcat(tmphdr, ":", sizeof tmphdr);
}
else
{
sm_strlcat(tmphdr, ";", sizeof tmphdr);
sm_strlcat(tmphdr, delim, sizeof tmphdr);
sm_strlcat(tmphdr, "h=", sizeof tmphdr);
}
firsthdr = FALSE;
sm_strlcat(tmphdr, tmp, sizeof tmphdr);
if (dkim->dkim_libhandle->dkiml_alwayshdrs != NULL)
{
u_char **ah = dkim->dkim_libhandle->dkiml_alwayshdrs;
for (n = 0; ah[n] != NULL && n < dkim->dkim_hdrcnt; n++)
{
if (strcasecmp(tmp, ah[n]) == 0)
{
always[n] = FALSE;
break;
}
}
}
}
/* apply any "always sign" list */
if (dkim->dkim_libhandle->dkiml_alwayshdrs != NULL)
{
u_char **ah = dkim->dkim_libhandle->dkiml_alwayshdrs;
for (n = 0; ah[n] != NULL && n < dkim->dkim_hdrcnt; n++)
{
if (always[n])
{
if (!firsthdr)
{
sm_strlcat(tmphdr, ":", sizeof tmphdr);
}
else
{
sm_strlcat(tmphdr, ";", sizeof tmphdr);
sm_strlcat(tmphdr, delim,
sizeof tmphdr);
sm_strlcat(tmphdr, "h=",
sizeof tmphdr);
}
firsthdr = FALSE;
sm_strlcat(tmphdr, ah[n], sizeof tmphdr);
}
}
}
/* if diagnostic headers were included, include 'em */
if (dkim->dkim_libhandle->dkiml_flags & DKIM_LIBFLAGS_ZTAGS)
{
bool first;
char *p;
char *q;
char *end;
size_t len;
first = TRUE;
end = tmp + sizeof tmp - 1;
sm_strlcat(tmphdr, ";", sizeof tmphdr);
sm_strlcat(tmphdr, delim, sizeof tmphdr);
sm_strlcat(tmphdr, "z=", sizeof tmphdr);
for (hdr = dkim->dkim_hhead; hdr != NULL; hdr = hdr->hdr_next)
{
memset(tmp, '\0', sizeof tmp);
q = tmp;
len = sizeof tmp - 1;
if (!first)
{
tmp[0] = '|';
q++;
len--;
}
first = FALSE;
for (p = hdr->hdr_text; *p != '\0'; p++)
{
if (q >= end)
break;
if ((*p >= 0x21 && *p <= 0x3a) ||
*p == 0x3c ||
(*p >= 0x3e && *p <= 0x7e))
{
*q = *p;
q++;
len--;
}
else
{
snprintf(q, len, "=%02X", *p);
q += 3;
len -= 3;
}
}
sm_strlcat(tmphdr, tmp, sizeof tmphdr);
}
}
/* and finally, an empty b= */
sm_strlcat(tmphdr, ";", sizeof tmphdr);
sm_strlcat(tmphdr, delim, sizeof tmphdr);
sm_strlcat(tmphdr, "b=", sizeof tmphdr);
sm_strlcpy(buf, tmphdr, buflen);
DKIM_FREE(dkim, always);
return strlen(buf);
}
/*
** DKIM_GETSENDER -- determine sender (actually just multi-search)
**
** Parameters:
** dkim -- DKIM handle
** hdrs -- list of header names to find
**
** Return value:
** Pointer to the first such header found, or NULL if none.
*/
static struct dkim_header *
dkim_getsender(DKIM *dkim, u_char **hdrs)
{
int c;
size_t hlen;
u_char *colon;
struct dkim_header *cur;
assert(dkim != NULL);
assert(hdrs != NULL);
for (c = 0; hdrs[c] != NULL; c++)
{
hlen = strlen(hdrs[c]);
for (cur = dkim->dkim_hhead; cur != NULL; cur = cur->hdr_next)
{
if (hlen == cur->hdr_namelen &&
strncasecmp(hdrs[c], cur->hdr_text, hlen) == 0)
return cur;
}
}
return NULL;
}
/*
** DKIM_GET_POLICY -- request and parse a domain's policy record
**
** Parameters:
** dkim -- DKIM handle
** query -- string to query
** usemx -- query with an MX record rather than a TXT record
** qstatus -- query status (returned)
** policy -- policy found (returned)
** pflags -- policy flags (returned)
** hcode -- suspicious message handling code (returned)
**
** Return value:
** A DKIM_STAT_* constant.
*/
static DKIM_STAT
dkim_get_policy(DKIM *dkim, char *query, bool usemx, int *qstatus,
dkim_policy_t *policy, int *pflags, dkim_handling_t *hcode)
{
int status = 0;
int qstat = NOERROR;
unsigned int lpflags;
dkim_policy_t lpolicy;
dkim_handling_t lhandling;
DKIM_STAT pstatus;
unsigned char buf[BUFRSZ + 1];
assert(dkim != NULL);
assert(query != NULL);
assert(qstatus != NULL);
assert(policy != NULL);
assert(pflags != NULL);
assert(hcode != NULL);
if (dkim->dkim_libhandle->dkiml_policy_lookup != NULL)
{
DKIM_CBSTAT cbstatus;
cbstatus = dkim->dkim_libhandle->dkiml_policy_lookup(dkim,
query,
usemx,
buf,
sizeof buf,
&qstat);
switch (cbstatus)
{
case DKIM_CBSTAT_CONTINUE:
status = 1;
break;
case DKIM_CBSTAT_REJECT:
return DKIM_STAT_CBREJECT;
case DKIM_CBSTAT_TRYAGAIN:
return DKIM_STAT_CBTRYAGAIN;
case DKIM_CBSTAT_NOTFOUND:
break;
case DKIM_CBSTAT_ERROR:
return DKIM_STAT_CBERROR;
default:
return DKIM_STAT_CBINVALID;
}
}
else
{
dkim_query_t qtype;
DKIM_SIGINFO *sig;
sig = dkim_getsignature(dkim);
if (sig == NULL)
qtype = DKIM_QUERY_DEFAULT;
else
qtype = sig->sig_query;
switch (qtype)
{
case DKIM_QUERY_DNS:
status = dkim_get_policy_dns(dkim, query, usemx,
buf, sizeof buf, &qstat);
break;
case DKIM_QUERY_FILE:
status = dkim_get_policy_file(dkim, query,
buf, sizeof buf, &qstat);
break;
default:
assert(0);
/* just to silence -Wall */
return -1;
}
}
if (status == -1)
return DKIM_STAT_CANTVRFY;
*qstatus = qstat;
if (!usemx && qstat == NOERROR && status == 1)
{
char *p;
struct dkim_set *set;
pstatus = dkim_process_set(dkim, DKIM_SETTYPE_POLICY,
buf, strlen(buf), NULL);
if (pstatus != DKIM_STAT_OK)
return pstatus;
lpolicy = DKIM_POLICY_DEFAULT;
lhandling = DKIM_HANDLING_DEFAULT;
lpflags = 0;
set = dkim_set_first(dkim, DKIM_SETTYPE_POLICY);
p = dkim_param_get(set, "dkim");
if (p != NULL)
lpolicy = dkim_name_to_code(policies, p);
p = dkim_param_get(set, "handling");
if (p != NULL)
lhandling = dkim_name_to_code(handlings, p);
p = dkim_param_get(set, "t");
if (p != NULL)
{
u_int flag;
char *t;
char *last;
char tmp[BUFRSZ + 1];
sm_strlcpy(tmp, p, sizeof tmp);
for (t = strtok_r(tmp, ":", &last);
t != NULL;
t = strtok_r(NULL, ":", &last))
{
flag = (u_int) dkim_name_to_code(policyflags,
t);
if (flag != (u_int) -1)
lpflags |= flag;
}
}
*policy = lpolicy;
*pflags = lpflags;
*hcode = lhandling;
}
return DKIM_STAT_OK;
}
/*
** DKIM_GET_KEY -- acquire a public key used for verification
**
** Parameters:
** dkim -- DKIM handle
** sig -- DKIM_SIGINFO handle
**
** Return value:
** A DKIM_STAT_* constant.
*/
DKIM_STAT
dkim_get_key(DKIM *dkim, DKIM_SIGINFO *sig)
{
bool gotkey = FALSE; /* key stored */
bool gotset = FALSE; /* set parsed */
bool gotreply = FALSE; /* reply received */
int status;
int c;
DKIM_SIGINFO *osig;
struct dkim_set *set = NULL;
struct dkim_set *nextset;
unsigned char *p;
unsigned char buf[BUFRSZ + 1];
assert(dkim != NULL);
assert(sig != NULL);
assert(sig->sig_selector != NULL);
assert(sig->sig_domain != NULL);
memset(buf, '\0', sizeof buf);
/* see if one of the other signatures already has the key we need */
for (c = 0; c < dkim->dkim_sigcount; c++)
{
osig = dkim->dkim_siglist[c];
/* don't self-search */
if (sig == osig)
continue;
/* skip unprocessed signatures */
if ((osig->sig_flags & DKIM_SIGFLAG_PROCESSED) == 0)
continue;
/* skip unless selector and domain match */
if (strcmp(osig->sig_domain, sig->sig_domain) != 0 ||
strcmp(osig->sig_selector, sig->sig_selector) != 0)
continue;
/* we got a match! copy the key data (if any)... */
if (osig->sig_key != NULL)
{
sig->sig_key = DKIM_MALLOC(dkim, osig->sig_b64keylen);
if (sig->sig_key == NULL)
{
dkim_error(dkim,
"unable to allocate %d byte(s)",
osig->sig_b64keylen);
return DKIM_STAT_NORESOURCE;
}
memcpy(sig->sig_key, osig->sig_key,
osig->sig_b64keylen);
sig->sig_keylen = osig->sig_keylen;
gotkey = TRUE;
}
/* ...and the key tag list (if any) */
if (osig->sig_keytaglist != NULL)
{
sig->sig_keytaglist = osig->sig_keytaglist;
set = sig->sig_keytaglist;
gotset = TRUE;
gotreply = TRUE;
}
break;
}
/* try a local function if there was one defined */
if (!gotkey && dkim->dkim_libhandle->dkiml_key_lookup != NULL)
{
DKIM_CBSTAT cbstatus;
cbstatus = dkim->dkim_libhandle->dkiml_key_lookup(dkim,
sig,
buf,
sizeof buf);
switch (cbstatus)
{
case DKIM_CBSTAT_CONTINUE:
gotreply = TRUE;
break;
case DKIM_CBSTAT_REJECT:
return DKIM_STAT_CBREJECT;
case DKIM_CBSTAT_TRYAGAIN:
return DKIM_STAT_CBTRYAGAIN;
case DKIM_CBSTAT_NOTFOUND:
return DKIM_STAT_NOKEY;
case DKIM_CBSTAT_ERROR:
return DKIM_STAT_CBERROR;
default:
return DKIM_STAT_CBINVALID;
}
}
/* if no local function or it returned no result, make the query */
if (!gotreply)
{
/* use appropriate get method */
switch (sig->sig_query)
{
case DKIM_QUERY_DNS:
status = (int) dkim_get_key_dns(dkim, sig, buf,
sizeof buf);
if (status != (int) DKIM_STAT_OK)
return (DKIM_STAT) status;
break;
case DKIM_QUERY_FILE:
status = (int) dkim_get_key_file(dkim, sig, buf,
sizeof buf);
if (status != (int) DKIM_STAT_OK)
return (DKIM_STAT) status;
break;
default:
assert(0);
}
}
/* decode the payload */
if (!gotset)
{
if (buf[0] == '\0')
{
dkim_error(dkim, "empty key record");
return DKIM_STAT_SYNTAX;
}
status = dkim_process_set(dkim, DKIM_SETTYPE_KEY, buf,
strlen(buf), NULL);
if (status != DKIM_STAT_OK)
return status;
/* get the last key */
set = dkim_set_first(dkim, DKIM_SETTYPE_KEY);
assert(set != NULL);
for (;;)
{
nextset = dkim_set_next(set, DKIM_SETTYPE_KEY);
if (nextset == NULL)
break;
set = nextset;
}
assert(set != NULL);
sig->sig_keytaglist = set;
}
/* verify key version first */
p = dkim_param_get(set, "v");
if (p != NULL && strcmp(p, DKIM_VERSION_KEY) != 0)
{
dkim_error(dkim, "invalid key version `%s'", p);
return DKIM_STAT_SYNTAX;
}
/* then make sure the hash type is something we can handle */
p = dkim_param_get(set, "h");
if (!dkim_key_hashesok(p))
{
dkim_error(dkim, "unknown hash `%s'", p);
return DKIM_STAT_SYNTAX;
}
/* ...and that this key is approved for this signature's hash */
else if (!dkim_key_hashok(sig, p))
{
dkim_error(dkim, "signature-key hash mismatch");
return DKIM_STAT_CANTVRFY;
}
/* make sure it's a key designated for e-mail */
if (!dkim_key_smtp(set))
{
dkim_error(dkim, "key type mismatch");
return DKIM_STAT_CANTVRFY;
}
/* check key granularity */
p = dkim_param_get(set, "g");
if (!dkim_key_granok(sig, p, dkim->dkim_user))
{
dkim_error(dkim, "granularity mismatch");
return DKIM_STAT_CANTVRFY;
}
/* then key type */
p = dkim_param_get(set, "k");
if (p == NULL)
{
dkim_error(dkim, "key type missing");
return DKIM_STAT_SYNTAX;
}
else if (dkim_name_to_code(keytypes, p) == -1)
{
dkim_error(dkim, "invalid key type `%s'", p);
return DKIM_STAT_SYNTAX;
}
if (!gotkey)
{
/* decode the key */
sig->sig_b64key = dkim_param_get(set, "p");
if (sig->sig_b64key == NULL)
{
dkim_error(dkim, "key missing");
return DKIM_STAT_SYNTAX;
}
else if (sig->sig_b64key[0] == '\0')
{
return DKIM_STAT_REVOKED;
}
sig->sig_b64keylen = strlen(sig->sig_b64key);
sig->sig_key = DKIM_MALLOC(dkim, sig->sig_b64keylen);
if (sig->sig_key == NULL)
{
dkim_error(dkim, "unable to allocate %d byte(s)",
sig->sig_b64keylen);
return DKIM_STAT_NORESOURCE;
}
status = dkim_base64_decode(sig->sig_b64key, sig->sig_key,
sig->sig_b64keylen);
if (status < 0)
{
dkim_error(dkim, "key missing");
return DKIM_STAT_SYNTAX;
}
sig->sig_keylen = status;
}
/* store key flags */
p = dkim_param_get(set, "t");
if (p != NULL)
{
u_int flag;
char *t;
char *last;
char tmp[BUFRSZ + 1];
sm_strlcpy(tmp, p, sizeof tmp);
for (t = strtok_r(tmp, ":", &last);
t != NULL;
t = strtok_r(NULL, ":", &last))
{
flag = (u_int) dkim_name_to_code(keyflags, t);
if (flag != (u_int) -1)
sig->sig_flags |= flag;
}
}
return DKIM_STAT_OK;
}
/*
** DKIM_EOH_SIGN -- declare end-of-headers; prepare for signing
**
** Parameters:
** dkim -- DKIM handle
**
** Return value:
** A DKIM_STAT_* constant.
*/
static DKIM_STAT
dkim_eoh_sign(DKIM *dkim)
{
bool found;
bool keep;
bool tmp;
DKIM_STAT status;
int c;
int hashtype;
size_t len = 0;
DKIM_CANON *bc;
DKIM_CANON *hc;
struct dkim_header *hdr;
DKIM_LIB *lib;
assert(dkim != NULL);
if (dkim->dkim_state >= DKIM_STATE_EOH2)
return DKIM_STAT_INVALID;
if (dkim->dkim_state < DKIM_STATE_EOH2)
dkim->dkim_state = DKIM_STATE_EOH2;
lib = dkim->dkim_libhandle;
assert(lib != NULL);
tmp = ((lib->dkiml_flags & DKIM_LIBFLAGS_TMPFILES) != 0);
keep = ((lib->dkiml_flags & DKIM_LIBFLAGS_KEEPFILES) != 0);
dkim->dkim_version = lib->dkiml_version;
/*
** Verify that all the required headers are present and
** marked for signing.
*/
for (c = 0; required_signhdrs[c] != NULL; c++)
{
found = FALSE;
len = strlen(required_signhdrs[c]);
for (hdr = dkim->dkim_hhead; hdr != NULL; hdr = hdr->hdr_next)
{
if (hdr->hdr_namelen == len &&
strncasecmp(hdr->hdr_text, required_signhdrs[c],
len) == 0)
{
found = TRUE;
break;
}
}
if (!found)
{
dkim_error(dkim, "required header \"%s\" not found",
required_signhdrs[c]);
return DKIM_STAT_SYNTAX;
}
}
/* determine hash type */
switch (dkim->dkim_signalg)
{
case DKIM_SIGN_RSASHA1:
hashtype = DKIM_HASHTYPE_SHA1;
break;
#ifdef DKIM_SIGN_RSASHA256
case DKIM_SIGN_RSASHA256:
hashtype = DKIM_HASHTYPE_SHA256;
break;
#endif /* DKIM_SIGN_RSASHA256 */
default:
assert(0);
/* NOTREACHED */
}
/* initialize signature and canonicalization for signing */
dkim->dkim_siglist = DKIM_MALLOC(dkim, sizeof(DKIM_SIGINFO *));
if (dkim->dkim_siglist == NULL)
{
dkim_error(dkim, "failed to allocate %d byte(s)",
sizeof(DKIM_SIGINFO *));
return DKIM_STAT_NORESOURCE;
}
dkim->dkim_siglist[0] = DKIM_MALLOC(dkim, sizeof(struct dkim_siginfo));
if (dkim->dkim_siglist[0] == NULL)
{
dkim_error(dkim, "failed to allocate %d byte(s)",
sizeof(struct dkim_siginfo));
return DKIM_STAT_NORESOURCE;
}
dkim->dkim_sigcount = 1;
memset(dkim->dkim_siglist[0], '\0', sizeof(struct dkim_siginfo));
dkim->dkim_siglist[0]->sig_domain = dkim->dkim_domain;
dkim->dkim_siglist[0]->sig_selector = dkim->dkim_selector;
dkim->dkim_siglist[0]->sig_hashtype = hashtype;
dkim->dkim_siglist[0]->sig_signalg = dkim->dkim_signalg;
status = dkim_add_canon(dkim, TRUE, dkim->dkim_hdrcanonalg,
hashtype, NULL, NULL, 0, &hc);
if (status != DKIM_STAT_OK)
return status;
status = dkim_add_canon(dkim, FALSE, dkim->dkim_bodycanonalg,
hashtype, NULL, NULL, dkim->dkim_signlen, &bc);
if (status != DKIM_STAT_OK)
return status;
dkim->dkim_siglist[0]->sig_hdrcanon = hc;
dkim->dkim_siglist[0]->sig_hdrcanonalg = dkim->dkim_hdrcanonalg;
dkim->dkim_siglist[0]->sig_bodycanon = bc;
dkim->dkim_siglist[0]->sig_bodycanonalg = dkim->dkim_bodycanonalg;
if (dkim->dkim_libhandle->dkiml_fixedtime != 0)
{
dkim->dkim_siglist[0]->sig_timestamp = dkim->dkim_libhandle->dkiml_fixedtime;
}
else
{
time_t now;
(void) time(&now);
dkim->dkim_siglist[0]->sig_timestamp = (unsigned long long) now;
}
/* initialize all canonicalizations */
status = dkim_canon_init(dkim, tmp, keep);
if (status != DKIM_STAT_OK)
return status;
/* run the headers */
status = dkim_canon_runheaders(dkim, TRUE);
if (status != DKIM_STAT_OK)
return status;
return DKIM_STAT_OK;
}
/*
** DKIM_EOH_VERIFY -- declare end-of-headers; set up verification
**
** Parameters:
** dkim -- DKIM handle
**
** Return value:
** A DKIM_STAT_* constant.
*/
static DKIM_STAT
dkim_eoh_verify(DKIM *dkim)
{
bool keep;
bool tmp;
DKIM_STAT status;
int c;
struct dkim_header *sender;
char *user;
char *domain;
DKIM_LIB *lib;
DKIM_SET *set;
assert(dkim != NULL);
if (dkim->dkim_state >= DKIM_STATE_EOH2)
return DKIM_STAT_INVALID;
if (dkim->dkim_state < DKIM_STATE_EOH1)
dkim->dkim_state = DKIM_STATE_EOH1;
lib = dkim->dkim_libhandle;
assert(lib != NULL);
tmp = ((lib->dkiml_flags & DKIM_LIBFLAGS_TMPFILES) != 0);
keep = ((lib->dkiml_flags & DKIM_LIBFLAGS_KEEPFILES) != 0);
/* populate some stuff like dkim_sender, dkim_domain, dkim_user */
sender = dkim_getsender(dkim, dkim->dkim_libhandle->dkiml_senderhdrs);
if (sender == NULL)
{
dkim_error(dkim, "no sender headers detected");
dkim->dkim_state = DKIM_STATE_UNUSABLE;
return DKIM_STAT_SYNTAX;
}
dkim->dkim_senderhdr = sender;
if (sender->hdr_colon == NULL)
{
dkim_error(dkim, "syntax error in headers");
return DKIM_STAT_SYNTAX;
}
dkim->dkim_sender = dkim_strdup(dkim, sender->hdr_colon + 1, 0);
if (dkim->dkim_sender == NULL)
return DKIM_STAT_NORESOURCE;
status = rfc2822_mailbox_split(dkim->dkim_sender,
(char **) &user,
(char **) &domain);
if (status != 0 || domain == NULL || user == NULL ||
domain[0] == '\0' || user[0] == '\0')
{
dkim_error(dkim, "can't determine sender address");
dkim->dkim_state = DKIM_STATE_UNUSABLE;
return DKIM_STAT_SYNTAX;
}
if (dkim->dkim_domain == NULL)
{
dkim->dkim_domain = dkim_strdup(dkim, domain, 0);
if (dkim->dkim_domain == NULL)
return DKIM_STAT_NORESOURCE;
}
dkim->dkim_user = dkim_strdup(dkim, user, 0);
if (dkim->dkim_user == NULL)
return DKIM_STAT_NORESOURCE;
/* allocate the siginfo array if not already done */
if (dkim->dkim_siglist == NULL)
{
/* count the signatures */
for (set = dkim_set_first(dkim, DKIM_SETTYPE_SIGNATURE);
set != NULL;
set = dkim_set_next(set, DKIM_SETTYPE_SIGNATURE))
{
if (!set->set_bad)
dkim->dkim_sigcount++;
}
/* if no signatures, return such */
if (dkim->dkim_sigcount == 0)
{
dkim->dkim_skipbody = TRUE;
return DKIM_STAT_NOSIG;
}
status = dkim_siglist_setup(dkim);
if (status != DKIM_STAT_OK)
return status;
/* initialize all discovered canonicalizations */
status = dkim_canon_init(dkim, tmp, keep);
if (status != DKIM_STAT_OK)
return status;
}
/* call the prescreen callback, if defined */
if (lib->dkiml_prescreen != NULL)
{
status = lib->dkiml_prescreen(dkim,
dkim->dkim_siglist,
dkim->dkim_sigcount);
switch (status)
{
case DKIM_CBSTAT_CONTINUE:
break;
case DKIM_CBSTAT_REJECT:
return DKIM_STAT_CBREJECT;
case DKIM_CBSTAT_TRYAGAIN:
return DKIM_STAT_CBTRYAGAIN;
default:
return DKIM_STAT_CBINVALID;
}
}
dkim->dkim_state = DKIM_STATE_EOH2;
/* if set to ignore everything, treat message as unsigned */
set = NULL;
for (c = 0; c < dkim->dkim_sigcount; c++)
{
if (!(dkim->dkim_siglist[c]->sig_flags & DKIM_SIGFLAG_IGNORE))
{
set = dkim->dkim_siglist[c]->sig_taglist;
break;
}
}
if (set == NULL)
{
dkim->dkim_skipbody = TRUE;
return DKIM_STAT_NOSIG;
}
/* run the headers */
status = dkim_canon_runheaders(dkim, FALSE);
if (status != DKIM_STAT_OK)
return status;
/* do public key verification of all still-enabled signatures here */
if ((lib->dkiml_flags & DKIM_LIBFLAGS_DELAYSIGPROC) == 0)
{
for (c = 0; c < dkim->dkim_sigcount; c++)
{
if (!(dkim->dkim_siglist[c]->sig_flags & DKIM_SIGFLAG_PROCESSED) &&
!(dkim->dkim_siglist[c]->sig_flags & DKIM_SIGFLAG_IGNORE) &&
dkim->dkim_siglist[c]->sig_error == DKIM_SIGERROR_OK)
{
status = dkim_sig_process(dkim,
dkim->dkim_siglist[c]);
if (status != DKIM_STAT_OK)
return status;
}
}
}
/*
** Possible short-circuit here if all signatures are:
** - marked to be ignored
** - definitely invalid
** - verification attempted but failed
*/
if ((lib->dkiml_flags & DKIM_LIBFLAGS_EOHCHECK) != 0)
{
bool good = FALSE;
DKIM_SIGINFO *sig;
for (c = 0; c < dkim->dkim_sigcount; c++)
{
sig = dkim->dkim_siglist[c];
/* ignored? */
if ((sig->sig_flags & DKIM_SIGFLAG_IGNORE) != 0)
continue;
/* had a processing error? */
if (sig->sig_error != DKIM_SIGERROR_UNKNOWN &&
sig->sig_error != DKIM_SIGERROR_OK)
continue;
/* processed but didn't pass? */
if ((sig->sig_flags & DKIM_SIGFLAG_PROCESSED) != 0 &&
(sig->sig_flags & DKIM_SIGFLAG_PASSED) == 0)
continue;
/* OK we had a good one */
good = TRUE;
break;
}
/* no good ones */
if (!good)
return DKIM_STAT_CANTVRFY;
}
return DKIM_STAT_OK;
}
/*
** DKIM_EOM_SIGN -- declare end-of-body; complete signing
**
** Parameters:
** dkim -- DKIM handle
**
** Return value:
** A DKIM_STAT_* constant.
*/
static DKIM_STAT
dkim_eom_sign(DKIM *dkim)
{
int status;
u_int l;
size_t diglen;
size_t siglen;
size_t n;
size_t len;
DKIM_STAT ret;
u_char *digest;
u_char *signature;
BIO *key;
DKIM_SIGINFO *sig;
DKIM_CANON *hc;
struct dkim_header hdr;
u_char tmp[DKIM_MAXHEADER + 1];
assert(dkim != NULL);
if (dkim->dkim_state >= DKIM_STATE_EOM2)
return DKIM_STAT_INVALID;
if (dkim->dkim_state < DKIM_STATE_EOM2)
dkim->dkim_state = DKIM_STATE_EOM2;
/* finalize body canonicalizations */
(void) dkim_canon_closebody(dkim);
dkim->dkim_bodydone = TRUE;
if (dkim->dkim_libhandle->dkiml_fixedtime != 0)
dkim->dkim_timestamp = dkim->dkim_libhandle->dkiml_fixedtime;
else
(void) time(&dkim->dkim_timestamp);
/* sign with l= if requested */
if ((dkim->dkim_libhandle->dkiml_flags & DKIM_LIBFLAGS_SIGNLEN) != 0)
dkim->dkim_partial = TRUE;
n = sm_strlcpy(tmp, DKIM_SIGNHEADER ": ", sizeof tmp);
ret = dkim_getsighdr(dkim, tmp + n, sizeof tmp - n, DKIM_HDRMARGIN,
strlen(DKIM_SIGNHEADER) + 2);
if (ret != DKIM_STAT_OK)
return ret;
len = strlen(tmp);
if (len == DKIM_MAXHEADER)
{
dkim_error(dkim, "generated signature header too large");
return DKIM_STAT_NORESOURCE;
}
hdr.hdr_text = tmp;
hdr.hdr_colon = tmp + DKIM_SIGNHEADER_LEN;
hdr.hdr_namelen = n - 2;
hdr.hdr_textlen = len;
hdr.hdr_flags = 0;
hdr.hdr_next = NULL;
/* canonicalize */
dkim_canon_signature(dkim, &hdr);
key = BIO_new_mem_buf(dkim->dkim_key, dkim->dkim_keylen);
if (key == NULL)
{
dkim_error(dkim, "BIO_new_mem_buf() failed");
return DKIM_STAT_NORESOURCE;
}
assert(dkim->dkim_siglist != NULL);
assert(dkim->dkim_siglist[0] != NULL);
sig = dkim->dkim_siglist[0];
hc = sig->sig_hdrcanon;
dkim_canon_getfinal(hc, &digest, &diglen);
/* compute and store the signature */
switch (sig->sig_signalg)
{
case DKIM_SIGN_RSASHA1:
#ifdef SHA256_DIGEST_LENGTH
case DKIM_SIGN_RSASHA256:
#endif /* SHA256_DIGEST_LENGTH */
{
int nid;
struct dkim_rsa *rsa;
#ifdef SHA256_DIGEST_LENGTH
assert(sig->sig_hashtype == DKIM_HASHTYPE_SHA1 ||
sig->sig_hashtype == DKIM_HASHTYPE_SHA256);
#else /* SHA256_DIGEST_LENGTH */
assert(sig->sig_hashtype == DKIM_HASHTYPE_SHA1);
#endif /* SHA256_DIGEST_LENGTH */
rsa = DKIM_MALLOC(dkim, sizeof(struct dkim_rsa));
if (rsa == NULL)
{
dkim_error(dkim, "unable to allocate %d byte(s)",
sizeof(struct dkim_rsa));
return DKIM_STAT_NORESOURCE;
}
memset(rsa, '\0', sizeof(struct dkim_rsa));
sig->sig_signature = (void *) rsa;
sig->sig_keytype = DKIM_KEYTYPE_RSA;
rsa->rsa_pkey = PEM_read_bio_PrivateKey(key, NULL, NULL, NULL);
if (rsa->rsa_pkey == NULL)
{
dkim_error(dkim, "PEM_read_bio_PrivateKey() failed");
BIO_free(key);
return DKIM_STAT_NORESOURCE;
}
rsa->rsa_rsa = EVP_PKEY_get1_RSA(rsa->rsa_pkey);
if (rsa->rsa_rsa == NULL)
{
dkim_error(dkim, "EVP_PKEY_get1_RSA() failed");
BIO_free(key);
return DKIM_STAT_NORESOURCE;
}
rsa->rsa_keysize = RSA_size(rsa->rsa_rsa);
rsa->rsa_pad = RSA_PKCS1_PADDING;
rsa->rsa_rsaout = DKIM_MALLOC(dkim, rsa->rsa_keysize);
if (rsa->rsa_rsaout == NULL)
{
dkim_error(dkim, "unable to allocate %d byte(s)",
rsa->rsa_keysize);
RSA_free(rsa->rsa_rsa);
rsa->rsa_rsa = NULL;
BIO_free(key);
return DKIM_STAT_NORESOURCE;
}
nid = NID_sha1;
#ifdef SHA256_DIGEST_LENGTH
if (sig->sig_hashtype == DKIM_HASHTYPE_SHA256)
nid = NID_sha256;
#endif /* SHA256_DIGEST_LENGTH */
status = RSA_sign(nid, digest, diglen,
rsa->rsa_rsaout, &l, rsa->rsa_rsa);
if (status == 0 || l == 0)
{
RSA_free(rsa->rsa_rsa);
rsa->rsa_rsa = NULL;
BIO_free(key);
dkim_error(dkim,
"signature generation failed (status %d, length %d)",
status, l);
return DKIM_STAT_INTERNAL;
}
rsa->rsa_rsaoutlen = l;
signature = rsa->rsa_rsaout;
siglen = rsa->rsa_rsaoutlen;
break;
}
default:
assert(0);
}
/* base64-encode the signature */
dkim->dkim_b64siglen = siglen * 3 + 5;
dkim->dkim_b64siglen += (dkim->dkim_b64siglen / 60);
dkim->dkim_b64sig = DKIM_MALLOC(dkim, dkim->dkim_b64siglen);
if (dkim->dkim_b64sig == NULL)
{
dkim_error(dkim, "unable to allocate %d byte(s)",
dkim->dkim_b64siglen);
BIO_free(key);
return DKIM_STAT_NORESOURCE;
}
memset(dkim->dkim_b64sig, '\0', dkim->dkim_b64siglen);
status = dkim_base64_encode(signature, siglen, dkim->dkim_b64sig,
dkim->dkim_b64siglen);
BIO_free(key);
if (status == -1)
{
dkim_error(dkim,
"base64 encoding error (buffer too small)");
return DKIM_STAT_NORESOURCE;
}
dkim->dkim_signature = sig;
return DKIM_STAT_OK;
}
/*
** DKIM_EOM_VERIFY -- declare end-of-body; complete verification
**
** Parameters:
** dkim -- DKIM handle
** testkey -- TRUE iff the a matching key was found but is marked as a
** test key (returned)
**
** Return value:
** A DKIM_STAT_* constant.
*/
static DKIM_STAT
dkim_eom_verify(DKIM *dkim, bool *testkey)
{
DKIM_STAT ret;
int c;
int status;
DKIM_SIGINFO *sig = NULL;
struct dkim_header *hdr;
DKIM_LIB *lib;
assert(dkim != NULL);
if (dkim->dkim_state >= DKIM_STATE_EOM2)
return DKIM_STAT_INVALID;
if (dkim->dkim_state < DKIM_STATE_EOM1)
dkim->dkim_state = DKIM_STATE_EOM1;
/* finalize body canonicalizations */
(void) dkim_canon_closebody(dkim);
dkim->dkim_bodydone = TRUE;
if (dkim->dkim_sigcount == 0)
{ /* unsigned */
if (dkim->dkim_domain == NULL)
{
char *domain;
char *user;
hdr = dkim_get_header(dkim, DKIM_FROMHEADER,
DKIM_FROMHEADER_LEN, 0);
if (hdr == NULL)
{
dkim_error(dkim, "no %s header found",
DKIM_FROMHEADER);
return DKIM_STAT_CANTVRFY;
}
if (hdr->hdr_colon == NULL)
{
dkim_error(dkim, "%s header malformed",
DKIM_FROMHEADER);
return DKIM_STAT_CANTVRFY;
}
status = rfc2822_mailbox_split(hdr->hdr_colon + 1,
&user, &domain);
if (status != 0 || domain == NULL || domain[0] == '\0')
{
dkim_error(dkim, "%s header malformed",
DKIM_FROMHEADER);
return DKIM_STAT_CANTVRFY;
}
dkim->dkim_domain = dkim_strdup(dkim, domain, 0);
if (dkim->dkim_domain == NULL)
return DKIM_STAT_NORESOURCE;
}
return DKIM_STAT_NOSIG;
}
lib = dkim->dkim_libhandle;
/* invoke the final callback if defined */
if (lib->dkiml_final != NULL)
{
status = lib->dkiml_final(dkim, dkim->dkim_siglist,
dkim->dkim_sigcount);
switch (status)
{
case DKIM_CBSTAT_CONTINUE:
break;
case DKIM_CBSTAT_REJECT:
return DKIM_STAT_CBREJECT;
case DKIM_CBSTAT_TRYAGAIN:
return DKIM_STAT_CBTRYAGAIN;
default:
return DKIM_STAT_CBINVALID;
}
}
dkim->dkim_state = DKIM_STATE_EOM2;
/* see if we have a passing signature with bh match */
for (c = 0; c < dkim->dkim_sigcount; c++)
{
sig = dkim->dkim_siglist[c];
if ((sig->sig_flags & DKIM_SIGFLAG_PASSED) != 0 &&
(sig->sig_flags & DKIM_SIGFLAG_IGNORE) == 0 &&
sig->sig_bh == DKIM_SIGBH_MATCH)
break;
sig = NULL;
}
/* run 'em until we get one */
if (sig == NULL)
{
for (c = 0; c < dkim->dkim_sigcount; c++)
{
sig = dkim->dkim_siglist[c];
/* if not ignoring */
if ((sig->sig_flags & DKIM_SIGFLAG_IGNORE) == 0)
{
/* run this signature */
status = dkim_sig_process(dkim, sig);
if (status != DKIM_STAT_OK)
{
sig = NULL;
continue;
}
/* pass and bh match? */
if ((sig->sig_flags & DKIM_SIGFLAG_PASSED) != 0 &&
sig->sig_bh == DKIM_SIGBH_MATCH)
break;
}
sig = NULL;
}
}
/*
** If still none, we're going to fail so just use the
** first one.
*/
if (sig == NULL)
{
for (c = 0; c < dkim->dkim_sigcount; c++)
{
sig = dkim->dkim_siglist[c];
if ((sig->sig_flags & DKIM_SIGFLAG_IGNORE) == 0)
break;
sig = NULL;
}
}
/* caller marked everything with "ignore" */
if (sig == NULL)
{
dkim_error(dkim, "all signatures ignored by caller");
return DKIM_STAT_INTERNAL;
/* XXX -- DKIM_STAT_NOSIG instead? */
}
dkim->dkim_signature = sig;
if (sig->sig_error != DKIM_SIGERROR_OK &&
sig->sig_error != DKIM_SIGERROR_UNKNOWN &&
sig->sig_error != DKIM_SIGERROR_KEYFAIL &&
sig->sig_error != DKIM_SIGERROR_BADSIG &&
sig->sig_error != DKIM_SIGERROR_NOKEY)
{
if (dkim->dkim_error == NULL ||
dkim->dkim_error[0] == '\0')
{
dkim_error(dkim, dkim_code_to_name(sigerrors,
sig->sig_error));
}
return DKIM_STAT_CANTVRFY;
}
/* initialize final result */
ret = DKIM_STAT_OK;
if (sig->sig_error == DKIM_SIGERROR_NOKEY)
ret = DKIM_STAT_NOKEY;
else if (sig->sig_error == DKIM_SIGERROR_KEYFAIL)
ret = DKIM_STAT_KEYFAIL;
else if (sig->sig_error == DKIM_SIGERROR_DNSSYNTAX)
ret = DKIM_STAT_CANTVRFY;
else if ((sig->sig_flags & DKIM_SIGFLAG_PASSED) == 0)
ret = DKIM_STAT_BADSIG;
else if (sig->sig_bh == DKIM_SIGBH_MISMATCH)
ret = DKIM_STAT_BADSIG;
else if (sig->sig_error == DKIM_SIGERROR_BADSIG)
ret = DKIM_STAT_BADSIG;
/* set testkey based on the key flags */
if (testkey != NULL &&
(sig->sig_flags & DKIM_SIGFLAG_TESTKEY) != 0)
*testkey = TRUE;
return ret;
}
/*
** DKIM_NEW -- allocate a new message context
**
** Parameters:
** libhandle -- DKIM_LIB handle
** id -- transaction ID string
** memclosure -- memory closure
** hdrcanon_alg -- canonicalization algorithm to use for headers
** bodycanon_alg -- canonicalization algorithm to use for headers
** sign_alg -- signature algorithm to use
** statp -- status (returned)
**
** Return value:
** A new DKIM handle, or NULL on failure.
*/
static DKIM *
dkim_new(DKIM_LIB *libhandle, const char *id, void *memclosure,
dkim_canon_t hdrcanon_alg, dkim_canon_t bodycanon_alg,
dkim_alg_t sign_alg, DKIM_STAT *statp)
{
DKIM *new;
assert(libhandle != NULL);
/* allocate the handle */
new = (DKIM *) dkim_malloc(libhandle, memclosure,
sizeof(struct dkim));
if (new == NULL)
{
*statp = DKIM_STAT_NORESOURCE;
return NULL;
}
/* populate defaults */
memset(new, '\0', sizeof(struct dkim));
new->dkim_id = id;
new->dkim_signalg = (sign_alg == -1 ? DKIM_SIGN_DEFAULT
: sign_alg);
new->dkim_hdrcanonalg = (hdrcanon_alg == -1 ? DKIM_CANON_DEFAULT
: hdrcanon_alg);
new->dkim_bodycanonalg = (bodycanon_alg == -1 ? DKIM_CANON_DEFAULT
: bodycanon_alg);
new->dkim_querymethod = DKIM_QUERY_DEFAULT;
new->dkim_mode = DKIM_MODE_UNKNOWN;
new->dkim_state = DKIM_STATE_INIT;
new->dkim_presult = DKIM_PRESULT_NONE;
new->dkim_closure = memclosure;
new->dkim_libhandle = libhandle;
new->dkim_tmpdir = libhandle->dkiml_tmpdir;
new->dkim_timeout = libhandle->dkiml_timeout;
*statp = DKIM_STAT_OK;
#ifdef QUERY_CACHE
if ((libhandle->dkiml_flags & DKIM_LIBFLAGS_CACHE) != 0 &&
libhandle->dkiml_cache == NULL)
{
int err = 0;
libhandle->dkiml_cache = dkim_cache_init(&err,
libhandle->dkiml_tmpdir);
}
#endif /* QUERY_CACHE */
return new;
}
/* ========================= PUBLIC SECTION ========================== */
/*
** DKIM_INIT -- initialize a DKIM library context
**
** Parameters:
** caller_mallocf -- caller-provided memory allocation function
** caller_freef -- caller-provided memory release function
**
** Return value:
** A new DKIM_LIB handle suitable for use with other DKIM functions, or
** NULL on failure.
**
** Side effects:
** Crop circles near Birmingham.
*/
DKIM_LIB *
dkim_init(void *(*caller_mallocf)(void *closure, size_t nbytes),
void (*caller_freef)(void *closure, void *p))
{
u_char *td;
DKIM_LIB *libhandle;
/* initialize OpenSSL algorithms */
OpenSSL_add_all_algorithms();
/* copy the parameters */
libhandle = (DKIM_LIB *) malloc(sizeof(struct dkim_lib));
if (libhandle == NULL)
return NULL;
td = getenv("DKIM_TMPDIR");
if (td == NULL || td[0] == '\0')
td = DEFTMPDIR;
libhandle->dkiml_signre = FALSE;
libhandle->dkiml_skipre = FALSE;
libhandle->dkiml_malloc = caller_mallocf;
libhandle->dkiml_free = caller_freef;
sm_strlcpy(libhandle->dkiml_tmpdir, td,
sizeof libhandle->dkiml_tmpdir);
libhandle->dkiml_flags = DKIM_LIBFLAGS_DEFAULT;
libhandle->dkiml_timeout = DEFTIMEOUT;
libhandle->dkiml_senderhdrs = (u_char **) default_senderhdrs;
libhandle->dkiml_alwayshdrs = NULL;
libhandle->dkiml_querymethod = DKIM_QUERY_UNKNOWN;
memset(libhandle->dkiml_queryinfo, '\0',
sizeof libhandle->dkiml_queryinfo);
#ifdef QUERY_CACHE
libhandle->dkiml_cache = NULL;
#endif /* QUERY_CACHE */
libhandle->dkiml_fixedtime = 0;
libhandle->dkiml_sigttl = 0;
libhandle->dkiml_clockdrift = DEFCLOCKDRIFT;
libhandle->dkiml_key_lookup = NULL;
libhandle->dkiml_policy_lookup = NULL;
libhandle->dkiml_sig_handle = NULL;
libhandle->dkiml_sig_handle_free = NULL;
libhandle->dkiml_sig_tagvalues = NULL;
libhandle->dkiml_prescreen = NULL;
libhandle->dkiml_final = NULL;
libhandle->dkiml_dns_callback = NULL;
/* initialize the resolver */
#if USE_ARLIB
libhandle->dkiml_arlib = ar_init(NULL, NULL, NULL, 0);
if (libhandle->dkiml_arlib == NULL)
{
free(libhandle);
return NULL;
}
# ifdef _FFR_DNS_UPGRADE
libhandle->dkiml_arlibtcp = ar_init(NULL, NULL, NULL, AR_FLAG_USETCP);
if (libhandle->dkiml_arlibtcp == NULL)
{
(void) ar_shutdown(libhandle->dkiml_arlib);
free(libhandle);
return NULL;
}
# endif /* _FFR_DNS_UPGRADE */
#else /* USE_ARLIB */
(void) res_init();
#endif /* USE_ARLIB */
return libhandle;
}
/*
** DKIM_CLOSE -- shut down a DKIM library package
**
** Parameters:
** lib -- library handle to shut down
**
** Return value:
** None.
*/
void
dkim_close(DKIM_LIB *lib)
{
assert(lib != NULL);
#ifdef QUERY_CACHE
if (lib->dkiml_cache != NULL)
(void) dkim_cache_close(lib->dkiml_cache);
#endif /* QUERY_CACHE */
#ifdef USE_ARLIB
if (lib->dkiml_arlib != NULL)
(void) ar_shutdown(lib->dkiml_arlib);
#endif /* USE_ARLIB */
free((void *) lib);
EVP_cleanup();
}
/*
** DKIM_ERROR -- log an error into a DKIM handle
**
** Parameters:
** dkim -- DKIM context in which this is performed
** format -- format to apply
** ... -- arguments
**
** Return value:
** None.
*/
void
dkim_error(DKIM *dkim, const char *format, ...)
{
int flen;
int saverr;
char *new;
va_list va;
assert(dkim != NULL);
assert(format != NULL);
saverr = errno;
if (dkim->dkim_error == NULL)
{
dkim->dkim_error = DKIM_MALLOC(dkim, DEFERRLEN);
if (dkim->dkim_error == NULL)
{
errno = saverr;
return;
}
dkim->dkim_errlen = DEFERRLEN;
}
for (;;)
{
va_start(va, format);
flen = vsnprintf(dkim->dkim_error, dkim->dkim_errlen,
format, va);
va_end(va);
/* compensate for broken vsnprintf() implementations */
if (flen == -1)
flen = dkim->dkim_errlen * 2;
if (flen >= dkim->dkim_errlen)
{
new = DKIM_MALLOC(dkim, flen + 1);
if (new == NULL)
{
errno = saverr;
return;
}
DKIM_FREE(dkim, dkim->dkim_error);
dkim->dkim_error = new;
dkim->dkim_errlen = flen + 1;
}
else
{
break;
}
}
errno = saverr;
}
/*
** DKIM_OPTIONS -- get or set a library option
**
** Parameters:
** lib -- DKIM library handle
** op -- operation to perform
** opt -- option to get/set
** ptr -- pointer to its old/new value
** len -- memory available at "ptr"
**
** Return value:
** A DKIM_STAT constant.
*/
DKIM_STAT
dkim_options(DKIM_LIB *lib, int op, dkim_opts_t opt, void *ptr, size_t len)
{
assert(lib != NULL);
assert(op == DKIM_OP_SETOPT || op == DKIM_OP_GETOPT);
assert(len != 0);
switch (opt)
{
case DKIM_OPTS_TMPDIR:
if (ptr == NULL)
return DKIM_STAT_INVALID;
if (op == DKIM_OP_GETOPT)
{
sm_strlcpy((u_char *) ptr,
lib->dkiml_tmpdir, len);
}
else
{
sm_strlcpy(lib->dkiml_tmpdir, (u_char *) ptr,
sizeof lib->dkiml_tmpdir);
}
return DKIM_STAT_OK;
case DKIM_OPTS_FIXEDTIME:
if (ptr == NULL)
return DKIM_STAT_INVALID;
if (len != sizeof lib->dkiml_fixedtime)
return DKIM_STAT_INVALID;
if (op == DKIM_OP_GETOPT)
{
memcpy(ptr, &lib->dkiml_fixedtime, len);
}
else
{
memcpy(&lib->dkiml_fixedtime, ptr, len);
}
return DKIM_STAT_OK;
case DKIM_OPTS_SIGNATURETTL:
if (ptr == NULL)
return DKIM_STAT_INVALID;
if (len != sizeof lib->dkiml_sigttl)
return DKIM_STAT_INVALID;
if (op == DKIM_OP_GETOPT)
{
memcpy(ptr, &lib->dkiml_sigttl, len);
}
else
{
memcpy(&lib->dkiml_sigttl, ptr, len);
}
return DKIM_STAT_OK;
case DKIM_OPTS_CLOCKDRIFT:
if (ptr == NULL)
return DKIM_STAT_INVALID;
if (len != sizeof lib->dkiml_clockdrift)
return DKIM_STAT_INVALID;
if (op == DKIM_OP_GETOPT)
{
memcpy(ptr, &lib->dkiml_clockdrift, len);
}
else
{
memcpy(&lib->dkiml_clockdrift, ptr, len);
}
return DKIM_STAT_OK;
case DKIM_OPTS_FLAGS:
if (ptr == NULL)
return DKIM_STAT_INVALID;
if (len != sizeof lib->dkiml_flags)
return DKIM_STAT_INVALID;
if (op == DKIM_OP_GETOPT)
{
memcpy(ptr, &lib->dkiml_flags, len);
}
else
{
memcpy(&lib->dkiml_flags, ptr, len);
}
return DKIM_STAT_OK;
case DKIM_OPTS_TIMEOUT:
if (ptr == NULL)
return DKIM_STAT_INVALID;
if (len != sizeof lib->dkiml_timeout)
return DKIM_STAT_INVALID;
if (op == DKIM_OP_GETOPT)
{
memcpy(ptr, &lib->dkiml_timeout, len);
}
else
{
memcpy(&lib->dkiml_timeout, ptr, len);
}
return DKIM_STAT_OK;
case DKIM_OPTS_SENDERHDRS:
if (len != sizeof lib->dkiml_senderhdrs)
return DKIM_STAT_INVALID;
if (op == DKIM_OP_GETOPT)
{
memcpy(ptr, &lib->dkiml_senderhdrs, len);
}
else if (ptr == NULL)
{
lib->dkiml_senderhdrs = (u_char **) default_senderhdrs;
}
else
{
lib->dkiml_senderhdrs = (u_char **) ptr;
}
return DKIM_STAT_OK;
case DKIM_OPTS_ALWAYSHDRS:
if (len != sizeof lib->dkiml_alwayshdrs)
return DKIM_STAT_INVALID;
if (op == DKIM_OP_GETOPT)
{
memcpy(ptr, &lib->dkiml_alwayshdrs, len);
}
else if (ptr == NULL)
{
lib->dkiml_alwayshdrs = NULL;
}
else
{
lib->dkiml_alwayshdrs = (u_char **) ptr;
}
return DKIM_STAT_OK;
case DKIM_OPTS_SIGNHDRS:
if (len != sizeof(char **) || op == DKIM_OP_GETOPT)
{
return DKIM_STAT_INVALID;
}
else if (ptr == NULL)
{
if (lib->dkiml_signre)
{
(void) regfree(&lib->dkiml_hdrre);
lib->dkiml_signre = FALSE;
}
}
else
{
int status;
u_char **hdrs;
char buf[BUFRSZ + 1];
if (lib->dkiml_signre)
{
(void) regfree(&lib->dkiml_hdrre);
lib->dkiml_signre = FALSE;
}
memset(buf, '\0', sizeof buf);
hdrs = (u_char **) ptr;
(void) sm_strlcpy(buf, "^(", sizeof buf);
if (!dkim_hdrlist(buf, sizeof buf,
(u_char **) required_signhdrs, TRUE))
return DKIM_STAT_INVALID;
if (!dkim_hdrlist(buf, sizeof buf, hdrs, FALSE))
return DKIM_STAT_INVALID;
if (sm_strlcat(buf, ")$", sizeof buf) >= sizeof buf)
return DKIM_STAT_INVALID;
status = regcomp(&lib->dkiml_hdrre, buf,
(REG_EXTENDED|REG_ICASE));
if (status != 0)
return DKIM_STAT_INTERNAL;
lib->dkiml_signre = TRUE;
}
return DKIM_STAT_OK;
case DKIM_OPTS_SKIPHDRS:
if (len != sizeof(char **) || op == DKIM_OP_GETOPT)
{
return DKIM_STAT_INVALID;
}
else if (ptr == NULL)
{
if (lib->dkiml_skipre)
{
(void) regfree(&lib->dkiml_skiphdrre);
lib->dkiml_skipre = FALSE;
}
}
else
{
int status;
u_char **hdrs;
char buf[BUFRSZ + 1];
if (lib->dkiml_skipre)
{
(void) regfree(&lib->dkiml_skiphdrre);
lib->dkiml_skipre = FALSE;
}
memset(buf, '\0', sizeof buf);
hdrs = (u_char **) ptr;
(void) sm_strlcpy(buf, "^(", sizeof buf);
if (!dkim_hdrlist(buf, sizeof buf, hdrs, TRUE))
return DKIM_STAT_INVALID;
if (sm_strlcat(buf, ")$", sizeof buf) >= sizeof buf)
return DKIM_STAT_INVALID;
status = regcomp(&lib->dkiml_skiphdrre, buf,
(REG_EXTENDED|REG_ICASE));
if (status != 0)
return DKIM_STAT_INTERNAL;
lib->dkiml_skipre = TRUE;
}
return DKIM_STAT_OK;
case DKIM_OPTS_QUERYMETHOD:
if (ptr == NULL)
return DKIM_STAT_INVALID;
if (len != sizeof lib->dkiml_querymethod)
return DKIM_STAT_INVALID;
if (op == DKIM_OP_GETOPT)
{
memcpy(ptr, &lib->dkiml_querymethod, len);
}
else
{
memcpy(&lib->dkiml_querymethod, ptr, len);
}
return DKIM_STAT_OK;
case DKIM_OPTS_QUERYINFO:
if (ptr == NULL)
return DKIM_STAT_INVALID;
if (op == DKIM_OP_GETOPT)
{
sm_strlcpy(ptr, lib->dkiml_queryinfo, len);
}
else
{
sm_strlcpy(lib->dkiml_queryinfo, ptr,
sizeof lib->dkiml_queryinfo);
}
return DKIM_STAT_OK;
default:
return DKIM_STAT_INVALID;
}
/* to silence -Wall */
return DKIM_STAT_INTERNAL;
}
#define CLOBBER(x) if ((x) != NULL) \
{ \
dkim_mfree(dkim->dkim_libhandle, dkim->dkim_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; \
}
#define DSTRING_CLOBBER(x) if ((x) != NULL) \
{ \
dkim_dstring_free((x)); \
(x) = NULL; \
}
/*
** DKIM_FREE -- destroy a DKIM handle
**
** Parameters:
** dkim -- DKIM handle to destroy
**
** Return value:
** A DKIM_STAT constant.
*/
DKIM_STAT
dkim_free(DKIM *dkim)
{
assert(dkim != NULL);
/* blast the headers */
if (dkim->dkim_hhead != NULL)
{
struct dkim_header *next;
struct dkim_header *hdr;
for (hdr = dkim->dkim_hhead; hdr != NULL; )
{
next = hdr->hdr_next;
CLOBBER(hdr->hdr_text);
CLOBBER(hdr);
hdr = next;
}
}
/* blast the data sets */
if (dkim->dkim_sethead != NULL)
{
DKIM_SET *set;
DKIM_SET *next;
for (set = dkim->dkim_sethead; set != NULL; )
{
next = set->set_next;
if (set->set_plist != NULL)
{
DKIM_PLIST *plist;
DKIM_PLIST *pnext;
for (plist = set->set_plist;
plist != NULL; )
{
pnext = plist->plist_next;
CLOBBER(plist);
plist = pnext;
}
}
CLOBBER(set->set_data);
CLOBBER(set);
set = next;
}
}
/* trash the signature list */
if (dkim->dkim_siglist != NULL)
{
int c;
for (c = 0; c < dkim->dkim_sigcount; c++)
{
if (dkim->dkim_siglist[c]->sig_context != NULL &&
dkim->dkim_libhandle->dkiml_sig_handle_free != NULL)
{
dkim->dkim_libhandle->dkiml_sig_handle_free(dkim->dkim_closure,
dkim->dkim_siglist[c]->sig_context);
}
CLOBBER(dkim->dkim_siglist[c]->sig_key);
CLOBBER(dkim->dkim_siglist[c]->sig_sig);
if (dkim->dkim_siglist[c]->sig_keytype == DKIM_KEYTYPE_RSA)
{
struct dkim_rsa *rsa;
rsa = dkim->dkim_siglist[c]->sig_signature;
if (rsa != NULL)
{
EVP_CLOBBER(rsa->rsa_pkey);
RSA_CLOBBER(rsa->rsa_rsa);
CLOBBER(rsa->rsa_rsaout);
}
}
CLOBBER(dkim->dkim_siglist[c]->sig_signature);
CLOBBER(dkim->dkim_siglist[c]);
}
CLOBBER(dkim->dkim_siglist);
}
/* destroy canonicalizations */
if (dkim->dkim_canonhead != NULL)
{
DKIM_CANON *cur;
DKIM_CANON *next;
for (cur = dkim->dkim_canonhead; cur != NULL; )
{
next = cur->canon_next;
dkim_canon_free(dkim, cur);
cur = next;
}
}
CLOBBER(dkim->dkim_b64sig);
CLOBBER(dkim->dkim_selector);
CLOBBER(dkim->dkim_domain);
CLOBBER(dkim->dkim_user);
CLOBBER(dkim->dkim_key);
CLOBBER(dkim->dkim_sender);
CLOBBER(dkim->dkim_signer);
CLOBBER(dkim->dkim_error);
CLOBBER(dkim->dkim_zdecode);
CLOBBER(dkim->dkim_hdrlist);
DSTRING_CLOBBER(dkim->dkim_hdrbuf);
DSTRING_CLOBBER(dkim->dkim_canonbuf);
dkim_mfree(dkim->dkim_libhandle, dkim->dkim_closure, dkim);
return DKIM_STAT_OK;
}
/*
** DKIM_SIGN -- allocate a handle for use in a signature operation
**
** Parameters:
** libhandle -- DKIM_LIB handle
** id -- identification string (e.g. job ID) for logging
** memclosure -- memory closure for allocations (or NULL)
** secretkey -- secret key (PEM format)
** selector -- selector to be used when generating the signature header
** domain -- domain for which this message is being signed
** hdrcanonalg -- canonicalization algorithm to use for headers
** bodycanonalg -- canonicalization algorithm to use for body
** signalg -- signing algorithm to use
** length -- how many bytes of the body to sign (-1 for all)
** statp -- status (returned)
**
** Return value:
** A new signing handle, or NULL.
*/
DKIM *
dkim_sign(DKIM_LIB *libhandle, const char *id, void *memclosure,
const dkim_sigkey_t secretkey, const char *selector,
const char *domain, dkim_canon_t hdrcanonalg,
dkim_canon_t bodycanonalg, dkim_alg_t signalg,
off_t length, DKIM_STAT *statp)
{
DKIM *new;
assert(libhandle != NULL);
assert(secretkey != NULL);
assert(selector != NULL);
assert(domain != NULL);
assert(hdrcanonalg == DKIM_CANON_SIMPLE ||
hdrcanonalg == DKIM_CANON_RELAXED);
assert(bodycanonalg == DKIM_CANON_SIMPLE ||
bodycanonalg == DKIM_CANON_RELAXED);
#ifdef SHA256_DIGEST_LENGTH
assert(signalg == DKIM_SIGN_RSASHA1 || signalg == DKIM_SIGN_RSASHA256);
#else /* SHA256_DIGEST_LENGTH */
assert(signalg == DKIM_SIGN_RSASHA1);
#endif /* SHA256_DIGEST_LENGTH */
assert(statp != NULL);
new = dkim_new(libhandle, id, memclosure, hdrcanonalg, bodycanonalg,
signalg, statp);
if (new != NULL)
{
new->dkim_mode = DKIM_MODE_SIGN;
new->dkim_keylen = strlen((const char *) secretkey);
new->dkim_key = (unsigned char *) DKIM_MALLOC(new,
new->dkim_keylen + 1);
if (new->dkim_key == NULL)
{
*statp = DKIM_STAT_NORESOURCE;
dkim_free(new);
return NULL;
}
memcpy(new->dkim_key, (char *) secretkey,
new->dkim_keylen + 1);
}
new->dkim_selector = dkim_strdup(new, selector, 0);
new->dkim_domain = dkim_strdup(new, domain, 0);
if (length == (off_t) -1)
new->dkim_signlen = ULONG_MAX;
else
new->dkim_signlen = length;
return new;
}
/*
** DKIM_VERIFY -- allocate a handle for use in a verify operation
**
** Parameters:
** libhandle -- DKIM_LIB handle
** id -- identification string (e.g. job ID) for logging
** memclosure -- memory closure for allocations (or NULL)
** statp -- status (returned)
**
** Return value:
** A new signing handle, or NULL.
*/
DKIM *
dkim_verify(DKIM_LIB *libhandle, const char *id, void *memclosure,
DKIM_STAT *statp)
{
DKIM *new;
assert(libhandle != NULL);
assert(statp != NULL);
new = dkim_new(libhandle, id, memclosure, DKIM_CANON_UNKNOWN,
DKIM_CANON_UNKNOWN, DKIM_SIGN_UNKNOWN, statp);
if (new != NULL)
new->dkim_mode = DKIM_MODE_VERIFY;
return new;
}
/*
** DKIM_POLICY -- parse policy associated with the sender's domain
**
** Parameters:
** dkim -- DKIM handle
** test -- policy test flag (returned)
** susp -- suspicious message flag (returned)
** pcode -- discovered policy (returned)
** hcode -- recommended suspicious message handling (returned)
** pstate -- state, for re-entrancy (updated; can be NULL)
**
** Return value:
** A DKIM_STAT_* constant.
*/
DKIM_STAT
dkim_policy(DKIM *dkim, bool *test, bool *susp, dkim_policy_t *pcode,
dkim_handling_t *hcode, DKIM_PSTATE *pstate)
{
int c;
int wlen;
int qstatus = NOERROR;
unsigned int pflags;
DKIM_STAT status;
dkim_policy_t policy = DKIM_POLICY_NONE;
dkim_handling_t handling = DKIM_HANDLING_NONE;
DKIM_SIGINFO *sig;
char domain[DKIM_MAXHOSTNAMELEN + 1];
char query[DKIM_MAXHOSTNAMELEN + 1];
assert(dkim != NULL);
assert(test != NULL);
assert(susp != NULL);
/* fail for signing handles */
if (dkim->dkim_mode == DKIM_MODE_SIGN)
return DKIM_STAT_INVALID;
/* fail if no domain could be determined */
if (dkim->dkim_domain == NULL)
return DKIM_STAT_SYNTAX;
/* initialize */
*test = FALSE;
dkim->dkim_presult = DKIM_PRESULT_NONE;
if (pstate != NULL)
{
qstatus = pstate->ps_qstatus;
policy = pstate->ps_policy;
pflags = pstate->ps_pflags;
handling = pstate->ps_handling;
}
/*
** Apply draft-ietf-dkim-ssp-01 sender signing policy algorithm:
*/
/*
** 1. If a valid Originator Signature exists, the message is
** not Suspicious, and the algorithm terminates.
*/
if (pstate != NULL && pstate->ps_state < 1)
{
for (c = 0; c < dkim->dkim_sigcount; c++)
{
sig = dkim->dkim_siglist[c];
if ((sig->sig_flags & DKIM_SIGFLAG_PASSED) != 0 &&
sig->sig_bh == DKIM_SIGBH_MATCH &&
strcasecmp(dkim->dkim_domain, sig->sig_domain) == 0)
{
dkim->dkim_presult = DKIM_PRESULT_VALIDOSIG;
*susp = FALSE;
if (pcode != NULL)
*pcode = policy;
if (hcode != NULL)
*hcode = handling;
return DKIM_STAT_OK;
}
}
if (pstate != NULL)
pstate->ps_state = 1;
}
/*
** 2. The Verifier MUST query DNS for a TXT record corresponding
** to the Originator Domain prefixed by "_ssp._domainkey.". If the
** result of this query is a NOERROR response with one or more
** answers which are syntactically valid SSP responses, proceed to
** step 7.
*/
if (pstate == NULL || pstate->ps_state < 2)
{
wlen = snprintf(query, sizeof query, "%s.%s.%s",
DKIM_DNSPOLICYNAME, DKIM_DNSKEYNAME,
dkim->dkim_domain);
if (wlen >= sizeof query)
{
dkim_error(dkim, "policy query name overflow");
return DKIM_STAT_NORESOURCE;
}
status = dkim_get_policy(dkim, query, FALSE, &qstatus, &policy,
&pflags, &handling);
if (status != DKIM_STAT_OK)
{
if (status == DKIM_STAT_CBTRYAGAIN && pstate != NULL)
{
pstate->ps_qstatus = qstatus;
pstate->ps_policy = policy;
pstate->ps_pflags = pflags;
pstate->ps_handling = handling;
}
return status;
}
if (pstate != NULL)
pstate->ps_state = 2;
}
if (qstatus != NOERROR || policy == DKIM_POLICY_NONE)
{
/*
** 3. The Verifier MUST query DNS for an MX record
** corresponding to the Originator Domain (with no
** prefix). This query is made only to check the
** existence of the domain name and MAY be done in
** parallel with teh query made in step 2. If the
** result of this query is an NXDOMAIN error, the message
** is Suspicious and the algorithm terminates.
*/
if (pstate == NULL || pstate->ps_state < 3)
{
status = dkim_get_policy(dkim, dkim->dkim_domain, TRUE,
&qstatus, &policy, &pflags,
&handling);
if (status != DKIM_STAT_OK)
{
if (status == DKIM_STAT_CBTRYAGAIN &&
pstate != NULL)
{
pstate->ps_qstatus = qstatus;
pstate->ps_policy = policy;
pstate->ps_pflags = pflags;
pstate->ps_handling = handling;
}
return status;
}
if (pstate != NULL)
pstate->ps_state = 3;
}
if (qstatus == NXDOMAIN)
{
dkim->dkim_presult = DKIM_PRESULT_NXDOMAIN;
*susp = TRUE;
if (pcode != NULL)
*pcode = policy;
if (hcode != NULL)
*hcode = handling;
return DKIM_STAT_OK;
}
if (pstate == NULL || pstate->ps_state < 4)
{
int nlabels = 0;
char *p;
char *ctx;
char *label[MAXLABELS];
/*
** Build a list of labels in the domain name of
** interest.
*/
memset(label, '\0', sizeof label);
sm_strlcpy(domain, dkim->dkim_domain, sizeof domain);
for (p = strtok_r(domain, ".", &ctx);
p != NULL && nlabels < MAXLABELS;
p = strtok_r(NULL, ".", &ctx), nlabels++)
label[nlabels] = p;
if (nlabels >= MAXLABELS)
{
dkim_error(dkim,
"too many labels for policy query");
return DKIM_STAT_CANTVRFY;
}
/*
** 4. If the immediate parent of the domain part of
** the Originator Address is a top-level domain (a
** domain consisting of a single element such as
** "com", "us" or "jp"), then the message is not
** Suspicious (because no SSP record was found) and
** the algorithm terminates.
**
** The verifier MAY also compare the parent domain
** against a locally-maintained list of known
** address suffixes (e.g. ".co.uk") and terminate
** the algorithm with a "not Suspicious" result if
** the parent domain matches an entry on the list.
*/
/*
** XXX -- implement the "known address suffixes"
** stuff
*/
if (nlabels == 2)
{
dkim->dkim_presult = DKIM_PRESULT_TOPLEVEL;
*susp = FALSE;
if (pcode != NULL)
*pcode = policy;
if (hcode != NULL)
*hcode = handling;
return DKIM_STAT_OK;
}
/*
** 5. The Verifier MUST query DNS for a TXT record
** for the the immediate parent domain, prefixed with
** "_ssp._domainkey.". If the result of this query
** is a NOERROR response with one or more answers
** which are syntactically-valid SSP responses,
** proceed to step 6. Otherwise, the message is
** not Suspicious and the algorithm terminates.
*/
wlen = snprintf(query, sizeof query, "%s.%s",
DKIM_DNSPOLICYNAME, DKIM_DNSKEYNAME);
if (wlen >= sizeof query)
{
dkim_error(dkim, "policy query name overflow");
return DKIM_STAT_NORESOURCE;
}
for (c = 1; c < nlabels; c++)
{
sm_strlcat(query, ".", sizeof query);
sm_strlcat(query, label[c], sizeof query);
}
status = dkim_get_policy(dkim, query, FALSE, &qstatus,
&policy, &pflags, &handling);
if (status != DKIM_STAT_OK)
{
if (status == DKIM_STAT_CBTRYAGAIN &&
pstate != NULL)
{
pstate->ps_qstatus = qstatus;
pstate->ps_policy = policy;
pstate->ps_pflags = pflags;
pstate->ps_handling = handling;
}
return status;
}
if (pstate != NULL)
pstate->ps_state = 4;
}
if (qstatus != NOERROR || policy == DKIM_POLICY_NONE)
{
dkim->dkim_presult = DKIM_PRESULT_PARENTOK;
*susp = FALSE;
if (pcode != NULL)
*pcode = policy;
if (hcode != NULL)
*hcode = handling;
return DKIM_STAT_OK;
}
/*
** 6. If the SSP "t" tag exists in the response and any of
** the flags is "s" (indicating it should not apply to a
** subdomain), then message is not Suspicious and the
** algorithm terminates.
*/
if ((pflags & DKIM_PFLAG_NOSUBDOMAIN) != 0)
{
dkim->dkim_presult = DKIM_PRESULT_NOSUBDOMAIN;
*susp = FALSE;
if (pcode != NULL)
*pcode = policy;
if (hcode != NULL)
*hcode = handling;
return DKIM_STAT_OK;
}
}
/*
** 7. If the SSP "t" tag exists in the response and any of the flags
** is "y" (indicating testing), the message is not Suspicious and the
** algorithm terminates.
*/
if ((pflags & DKIM_PFLAG_TEST) != 0)
{
dkim->dkim_presult = DKIM_PRESULT_TESTFLAG;
*susp = FALSE;
*test = TRUE;
if (pcode != NULL)
*pcode = policy;
if (hcode != NULL)
*hcode = handling;
return DKIM_STAT_OK;
}
/*
** 8. If the value of the "dkim" SSP tag is "unknown",
** the message is not Suspicious and the algorithm
** terminates.
*/
if (policy == DKIM_POLICY_UNKNOWN)
{
dkim->dkim_presult = DKIM_PRESULT_UNKNOWN;
*susp = FALSE;
if (pcode != NULL)
*pcode = policy;
if (hcode != NULL)
*hcode = handling;
return DKIM_STAT_OK;
}
/*
** 9. If the value of the "dkim" SSP tag is "all",
** and one or more Verifier Acceptable Thurd-Party Signatures
** are present on the message, the message is not Suspicious and
** the algorithm terminates.
*/
if (policy == DKIM_POLICY_ALL)
{
for (c = 0; c < dkim->dkim_sigcount; c++)
{
sig = dkim->dkim_siglist[c];
if ((sig->sig_flags & DKIM_SIGFLAG_PASSED) != 0 &&
sig->sig_bh == DKIM_SIGBH_MATCH)
{
dkim->dkim_presult = DKIM_PRESULT_ALLVALIDSIG;
*susp = FALSE;
if (pcode != NULL)
*pcode = policy;
if (hcode != NULL)
*hcode = handling;
return DKIM_STAT_OK;
}
}
}
/*
** 10. The message is suspicious and the algorithm terminates.
*/
dkim->dkim_presult = DKIM_PRESULT_POLICYERROR;
*susp = TRUE;
if (pcode != NULL)
*pcode = policy;
if (hcode != NULL)
*hcode = handling;
return DKIM_STAT_OK;
}
/*
** DKIM_SIG_PROCESS -- process a signature
**
** Parameters:
** dkim -- DKIM handle
** sig -- DKIM_SIGINFO handle
**
** Return value:
** A DKIM_STAT_* constant.
*/
DKIM_STAT
dkim_sig_process(DKIM *dkim, DKIM_SIGINFO *sig)
{
DKIM_STAT status;
int nid;
int rsastat;
size_t diglen;
BIO *key;
u_char *digest;
struct dkim_rsa *rsa;
assert(dkim != NULL);
assert(sig != NULL);
/* skip it if we're supposed to ignore it */
if ((sig->sig_flags & DKIM_SIGFLAG_IGNORE) != 0)
return DKIM_STAT_OK;
/* skip it if there was a syntax or other error */
if (sig->sig_error != DKIM_SIGERROR_OK)
return DKIM_STAT_OK;
/* skip the DNS part if we've already done it */
if ((sig->sig_flags & DKIM_SIGFLAG_PROCESSED) == 0)
{
/* get the digest */
dkim_canon_getfinal(sig->sig_hdrcanon, &digest, &diglen);
/* retrieve the key */
status = dkim_get_key(dkim, sig);
if (status == DKIM_STAT_NOKEY)
{
sig->sig_flags |= DKIM_SIGFLAG_PROCESSED;
sig->sig_error = DKIM_SIGERROR_NOKEY;
return DKIM_STAT_OK;
}
else if (status == DKIM_STAT_KEYFAIL)
{
sig->sig_flags |= DKIM_SIGFLAG_PROCESSED;
sig->sig_error = DKIM_SIGERROR_KEYFAIL;
return DKIM_STAT_OK;
}
else if (status == DKIM_STAT_CANTVRFY ||
status == DKIM_STAT_SYNTAX)
{
sig->sig_flags |= DKIM_SIGFLAG_PROCESSED;
sig->sig_error = DKIM_SIGERROR_DNSSYNTAX;
return DKIM_STAT_OK;
}
else if (status != DKIM_STAT_OK)
{
return status;
}
/* load the public key */
key = BIO_new_mem_buf(sig->sig_key, sig->sig_keylen);
if (key == NULL)
{
dkim_error(dkim, "BIO_new_mem_buf() failed");
return DKIM_STAT_NORESOURCE;
}
/* set up to verify */
if (sig->sig_signature == NULL)
{
rsa = DKIM_MALLOC(dkim, sizeof(struct dkim_rsa));
if (rsa == NULL)
{
dkim_error(dkim,
"unable to allocate %d byte(s)",
sizeof(struct dkim_rsa));
return DKIM_STAT_NORESOURCE;
}
sig->sig_signature = rsa;
}
else
{
rsa = sig->sig_signature;
}
memset(rsa, '\0', sizeof(struct dkim_rsa));
rsa->rsa_pkey = d2i_PUBKEY_bio(key, NULL);
if (rsa->rsa_pkey == NULL)
{
dkim_error(dkim, "d2i_PUBKEY_bio() failed");
BIO_free(key);
return DKIM_STAT_NORESOURCE;
}
/* set up the RSA object */
rsa->rsa_rsa = EVP_PKEY_get1_RSA(rsa->rsa_pkey);
if (rsa->rsa_rsa == NULL)
{
dkim_error(dkim, "EVP_PKEY_get1_RSA() failed");
BIO_free(key);
return DKIM_STAT_NORESOURCE;
}
rsa->rsa_keysize = RSA_size(rsa->rsa_rsa);
rsa->rsa_pad = RSA_PKCS1_PADDING;
rsa->rsa_rsain = sig->sig_sig;
rsa->rsa_rsainlen = sig->sig_siglen;
sig->sig_keybits = 8 * rsa->rsa_keysize;
nid = NID_sha1;
#ifdef SHA256_DIGEST_LENGTH
if (sig->sig_hashtype == DKIM_HASHTYPE_SHA256)
nid = NID_sha256;
#endif /* SHA256_DIGEST_LENGTH */
rsastat = RSA_verify(nid, digest, diglen, rsa->rsa_rsain,
rsa->rsa_rsainlen, rsa->rsa_rsa);
if (rsastat == 1)
sig->sig_flags |= DKIM_SIGFLAG_PASSED;
else
sig->sig_error = DKIM_SIGERROR_BADSIG;
sig->sig_flags |= DKIM_SIGFLAG_PROCESSED;
BIO_free(key);
RSA_free(rsa->rsa_rsa);
rsa->rsa_rsa = NULL;
}
/* do the body hash check if possible */
if (dkim->dkim_bodydone && sig->sig_bh == DKIM_SIGBH_UNTESTED &&
(sig->sig_flags & DKIM_SIGFLAG_PASSED) != 0)
{
u_char *bhash;
u_char b64buf[BUFRSZ];
memset(b64buf, '\0', sizeof b64buf);
dkim_canon_getfinal(sig->sig_bodycanon, &digest, &diglen);
bhash = dkim_param_get(sig->sig_taglist, "bh");
dkim_base64_encode(digest, diglen, b64buf, sizeof b64buf);
if (strcmp(bhash, b64buf) == 0)
{
sig->sig_bh = DKIM_SIGBH_MATCH;
}
else
{
sig->sig_bh = DKIM_SIGBH_MISMATCH;
}
}
/*
** Fail if t=s was present in the key and the i= and d= domains
** don't match.
*/
if ((sig->sig_flags & DKIM_SIGFLAG_NOSUBDOMAIN) != 0)
{
char *d;
char *i;
d = dkim_param_get(sig->sig_taglist, "d");
i = dkim_param_get(sig->sig_taglist, "i");
if (i != NULL && d != NULL)
{
char *at;
at = strchr(i, '@');
if (at == NULL)
at = i;
else
at++;
if (strcasecmp(at, d) != 0)
sig->sig_error = DKIM_SIGERROR_SUBDOMAIN;
}
}
return DKIM_STAT_OK;
}
/*
** DKIM_OHDRS -- extract and decode original headers
**
** Parameters:
** dkim -- DKIM handle
** sig -- DKIM_SIGINFO handle
** ptrs -- user-provided array of pointers to header strings (updated)
** pcnt -- number of pointers available (updated)
**
** Return value:
** A DKIM_STAT_* constant.
**
** Notes:
** If the returned value of pcnt is greater that what it was originally,
** then there were more headers than there were pointers.
*/
DKIM_STAT
dkim_ohdrs(DKIM *dkim, DKIM_SIGINFO *sig, char **ptrs, int *pcnt)
{
int n = 0;
char *z;
u_char *ch;
u_char *p;
u_char *q;
char *last;
assert(dkim != NULL);
assert(ptrs != NULL);
assert(pcnt != NULL);
if (dkim->dkim_mode != DKIM_MODE_VERIFY)
return DKIM_STAT_INVALID;
/* pick the one we're going to use */
if (sig == NULL)
{
int c;
for (c = 0; c < dkim->dkim_sigcount; c++)
{
sig = dkim->dkim_siglist[c];
if ((sig->sig_flags & DKIM_SIGFLAG_PROCESSED) != 0 &&
(sig->sig_flags & DKIM_SIGFLAG_IGNORE) == 0)
break;
sig = NULL;
}
}
/* none useable; return error */
if (sig == NULL)
return DKIM_STAT_INVALID;
/* find the tag */
z = dkim_param_get(sig->sig_taglist, "z");
if (z == NULL || *z == '\0')
{
*pcnt = 0;
return DKIM_STAT_OK;
}
/* get memory for the decode */
if (dkim->dkim_zdecode == NULL)
{
dkim->dkim_zdecode = DKIM_MALLOC(dkim, MAXHEADERS);
if (dkim->dkim_zdecode == NULL)
{
dkim_error(dkim, "unable to allocate %d byte(s)",
strlen(z));
return DKIM_STAT_NORESOURCE;
}
}
/* copy it */
sm_strlcpy(dkim->dkim_zdecode, z, strlen(z));
/* decode */
for (ch = strtok_r(z, "|", &last);
ch != NULL;
ch = strtok_r(NULL, "|", &last))
{
for (p = ch, q = ch; *p != '\0'; p++)
{
if (*p == '=')
{
char c;
if (!isxdigit(*(p + 1)) || !isxdigit(*(p + 2)))
return DKIM_STAT_INVALID;
c = 16 * dkim_hexchar(*(p + 1)) + dkim_hexchar(*(p + 2));
p += 2;
*q = c;
q++;
}
else
{
if (q != p)
*q = *p;
q++;
}
}
*q = '\0';
if (n < *pcnt)
ptrs[n] = ch;
n++;
}
*pcnt = n;
return DKIM_STAT_OK;
}
#ifdef _FFR_DIFFHEADERS
# if !defined(TRE_APPROX) || (TRE_APPROX == 0)
# error _FFR_DIFFHEADERS requires approximate regular expression matching
# endif /* !defined(TRE_APPROX) || (TRE_APPROX == 0) */
/*
** DKIM_DIFFHEADERS -- compare original headers with received headers
**
** Parameters:
** dkim -- DKIM handle
** maxcost -- maximum "cost" of changes to be reported
** ohdrs -- original headers, presumably extracted from a "z" tag
** nohdrs -- number of headers at "ohdrs" available
** out -- pointer to an array of struct dkim_hdrdiff objects (updated)
** nout -- counter of handles returned (updated)
**
** Return value:
** A DKIM_STAT_* constant.
**
** Side effects:
** A series of DKIM_HDRDIFF handles is allocated and must later be
** destroyed.
*/
DKIM_STAT
dkim_diffheaders(DKIM *dkim, int maxcost, char **ohdrs, int nohdrs,
struct dkim_hdrdiff **out, int *nout)
{
int n = 0;
int a = 0;
int c;
int status;
u_char *p;
u_char *q;
u_char *end;
void *cls;
struct dkim_header *hdr;
struct dkim_hdrdiff *diffs = NULL;
DKIM_LIB *lib;
regaparams_t params;
regamatch_t matches;
regex_t re;
u_char restr[BUFRSZ + 1];
assert(dkim != NULL);
assert(out != NULL);
assert(nout != NULL);
if (dkim->dkim_mode != DKIM_MODE_VERIFY)
return DKIM_STAT_INVALID;
if (maxcost == 0)
return DKIM_STAT_INVALID;
lib = dkim->dkim_libhandle;
cls = dkim->dkim_closure;
memset(¶ms, '\0', sizeof params);
params.cost_ins = COST_INSERT;
params.cost_del = COST_DELETE;
params.cost_subst = COST_SUBST;
params.max_cost = maxcost;
params.max_ins = DKIM_MAXHEADER;
params.max_del = DKIM_MAXHEADER;
params.max_subst = DKIM_MAXHEADER;
params.max_err = maxcost;
matches.nmatch = 0;
matches.pmatch = NULL;
for (hdr = dkim->dkim_hhead; hdr != NULL; hdr = hdr->hdr_next)
{
memset(restr, '\0', sizeof restr);
end = restr + sizeof restr;
for (p = hdr->hdr_text, q = restr;
*p != '\0' && q < end - 3;
p++)
{
if (q == restr)
*q++ = '^';
if (*p == '*' ||
*p == '\\' ||
*p == '$' ||
*p == '+' ||
*p == '[' ||
*p == ']' ||
*p == '(' ||
*p == ')' ||
*p == '.' ||
*p == '|')
*q++ = '\\';
*q++ = *p;
}
*q = '$';
status = regcomp(&re, restr, REG_NOSUB);
if (status != 0)
{
char err[BUFRSZ + 1];
memset(err, '\0', sizeof err);
(void) regerror(status, &re, err, sizeof err);
dkim_error(dkim, err);
if (diffs != NULL)
dkim_mfree(lib, cls, diffs);
return DKIM_STAT_INTERNAL;
}
for (c = 0; c < nohdrs; c++)
{
if (strcmp(ohdrs[c], hdr->hdr_text) == 0)
continue;
status = regaexec(&re, ohdrs[c], &matches, params, 0);
if (status == 0)
{
if (n + 1 > a)
{
int sz;
struct dkim_hdrdiff *new;
if (a == 0)
a = 16;
else
a *= 2;
sz = a * sizeof(struct dkim_hdrdiff);
new = (struct dkim_hdrdiff *) dkim_malloc(lib,
cls,
sz);
if (new == NULL)
{
dkim_error(dkim,
"unable to allocate %d byte(s)",
sz);
if (diffs != NULL)
{
dkim_mfree(lib, cls,
diffs);
}
return DKIM_STAT_NORESOURCE;
}
dkim_mfree(lib, cls, diffs);
diffs = new;
sz = (a - n) & sizeof(struct dkim_hdrdiff);
memset(&diffs[n], '\0', sz);
}
diffs[n].hd_old = ohdrs[c];
diffs[n].hd_new = hdr->hdr_text;
n++;
}
}
regfree(&re);
}
*out = diffs;
*nout = n;
return DKIM_STAT_OK;
}
#endif /* _FFR_DIFFHEADERS */
/*
** DKIM_HEADER -- process a header
**
** Parameters:
** dkim -- DKIM handle
** hdr -- header text
** len -- bytes available at "hdr"
**
** Return value:
** A DKIM_STAT_* constant.
*/
DKIM_STAT
dkim_header(DKIM *dkim, u_char *hdr, size_t len)
{
u_char *colon;
u_char *end = NULL;
struct dkim_header *h;
assert(dkim != NULL);
assert(hdr != NULL);
assert(len != 0);
if (dkim->dkim_state > DKIM_STATE_HEADER)
return DKIM_STAT_INVALID;
dkim->dkim_state = DKIM_STATE_HEADER;
colon = memchr(hdr, ':', len);
if (colon != NULL)
{
end = colon;
while (end > hdr && isascii(*(end - 1)) && isspace(*(end - 1)))
end--;
}
/* see if this is one we should skip */
if (dkim->dkim_mode == DKIM_MODE_SIGN &&
dkim->dkim_libhandle->dkiml_skipre)
{
int status;
char name[DKIM_MAXHEADER + 1];
sm_strlcpy(name, hdr, sizeof name);
if (end != NULL)
name[end - hdr] = '\0';
status = regexec(&dkim->dkim_libhandle->dkiml_skiphdrre,
name, 0, NULL, 0);
if (status == 0)
return DKIM_STAT_OK;
else
assert(status == REG_NOMATCH);
}
h = DKIM_MALLOC(dkim, sizeof(struct dkim_header));
if (h == NULL)
{
dkim_error(dkim, "unable to allocate %d byte(s)",
sizeof(struct dkim_header));
return DKIM_STAT_NORESOURCE;
}
h->hdr_text = dkim_strdup(dkim, hdr, len);
if (h->hdr_text == NULL)
return DKIM_STAT_NORESOURCE;
h->hdr_namelen = end != NULL ? end - hdr : len;
h->hdr_textlen = len;
if (colon == NULL)
h->hdr_colon = NULL;
else
h->hdr_colon = h->hdr_text + (colon - hdr);
h->hdr_flags = 0;
h->hdr_next = NULL;
if (dkim->dkim_hhead == NULL)
{
dkim->dkim_hhead = h;
dkim->dkim_htail = h;
}
else
{
dkim->dkim_htail->hdr_next = h;
dkim->dkim_htail = h;
}
dkim->dkim_hdrcnt++;
if (h->hdr_colon != NULL)
{
if (h->hdr_namelen == DKIM_SIGNHEADER_LEN &&
strncasecmp(hdr, DKIM_SIGNHEADER,
DKIM_SIGNHEADER_LEN) == 0)
{
DKIM_STAT status;
size_t plen;
plen = len - (h->hdr_colon - h->hdr_text) - 1;
status = dkim_process_set(dkim, DKIM_SETTYPE_SIGNATURE,
h->hdr_colon + 1, plen, h);
if (status != DKIM_STAT_OK)
return status;
}
}
return DKIM_STAT_OK;
}
/*
** DKIM_EOH -- declare end-of-headers
**
** Parameters:
** dkim -- DKIM handle
**
** Return value:
** A DKIM_STAT_* constant.
*/
DKIM_STAT
dkim_eoh(DKIM *dkim)
{
#ifdef _FFR_PARSE_TIME
struct dkim_header *hdr;
#endif /* _FFR_PARSE_TIME */
assert(dkim != NULL);
#ifdef _FFR_PARSE_TIME
#define RFC2822DATE "%a, %d %b %Y %H:%M:%S %z"
/* # define RFC2822DATE "%a" */
/* store the Date: value for possible later scrutiny */
hdr = dkim_get_header(dkim, DKIM_DATEHEADER, DKIM_DATEHEADER_LEN, 0);
if (hdr != NULL)
{
char *colon;
colon = hdr->hdr_colon;
if (colon != NULL)
{
char *p;
struct tm tm;
colon++;
while (isascii(*colon) && isspace(*colon))
colon++;
p = strptime(colon, RFC2822DATE, &tm);
if (p != NULL)
dkim->dkim_msgdate = mktime(&tm);
}
}
#endif /* _FFR_PARSE_TIME */
if (dkim->dkim_mode == DKIM_MODE_VERIFY)
return dkim_eoh_verify(dkim);
else
return dkim_eoh_sign(dkim);
}
/*
** DKIM_BODY -- pass a body chunk in for processing
**
** Parameters:
** dkim -- DKIM handle
** buf -- body chunk
** buflen -- number of bytes at "buf"
**
** Return value:
** A DKIM_STAT_* constant.
*/
DKIM_STAT
dkim_body(DKIM *dkim, u_char *buf, size_t buflen)
{
assert(dkim != NULL);
assert(buf != NULL);
if (dkim->dkim_state > DKIM_STATE_BODY)
return DKIM_STAT_INVALID;
dkim->dkim_state = DKIM_STATE_BODY;
if (dkim->dkim_skipbody)
return DKIM_STAT_OK;
return dkim_canon_bodychunk(dkim, buf, buflen);
}
/*
** DKIM_EOM -- declare end-of-body; conduct verification or signing
**
** Parameters:
** dkim -- DKIM handle
** testkey -- TRUE iff the a matching key was found but is marked as a
** test key (returned)
**
** Return value:
** A DKIM_STAT_* constant.
*/
DKIM_STAT
dkim_eom(DKIM *dkim, bool *testkey)
{
assert(dkim != NULL);
if (dkim->dkim_mode == DKIM_MODE_SIGN)
return dkim_eom_sign(dkim);
else
return dkim_eom_verify(dkim, testkey);
}
/*
** DKIM_MINBODY -- return number of bytes still expected
**
** Parameters:
** dkim -- DKIM handle
**
** Return value:
** 0 -- all canonicalizations satisfied
** ULONG_MAX -- at least one canonicalization wants the whole message
** other -- bytes required to satisfy all canonicalizations
*/
u_long
dkim_minbody(DKIM *dkim)
{
assert(dkim != NULL);
return dkim_canon_minbody(dkim);
}
/*
** DKIM_GETSIGLIST -- retrieve the list of signatures
**
** Parameters:
** dkim -- DKIM handle
** sigs -- pointer to a vector of DKIM_SIGINFO pointers (updated)
** nsigs -- pointer to an integer to receive the pointer count (updated)
**
** Return value:
** A DKIM_STAT_* constant.
*/
DKIM_STAT
dkim_getsiglist(DKIM *dkim, DKIM_SIGINFO ***sigs, int *nsigs)
{
assert(dkim != NULL);
assert(sigs != NULL);
assert(nsigs != NULL);
if (dkim->dkim_state < DKIM_STATE_EOH2)
return DKIM_STAT_INVALID;
*sigs = dkim->dkim_siglist;
*nsigs = dkim->dkim_sigcount;
return DKIM_STAT_OK;
}
/*
** DKIM_GETSIGNATURE -- retrieve the "final" signature
**
** Parameters:
** dkim -- DKIM handle
**
** Return value:
** Pointer to a DKIM_SIGINFO handle which is the one libdkim will
** use to return a "final" result; NULL if none could be determined.
*/
DKIM_SIGINFO *
dkim_getsignature(DKIM *dkim)
{
assert(dkim != NULL);
return dkim->dkim_signature;
}
/*
** DKIM_GETSIGHDR -- for signing operations, retrieve the complete signature
** header
**
** Parameters:
** dkim -- DKIM handle
** buf -- buffer for the signature
** buflen -- bytes available at "buf"
** margin -- attempt to cut at specified line width; 0 == don't bother
** initial -- initial line width
**
** Return value:
** A DKIM_STAT_* constant.
**
** Notes:
** Per RFC4871 section 3.7, the signature header returned here does
** not contain a trailing CRLF.
*/
DKIM_STAT
dkim_getsighdr(DKIM *dkim, u_char *buf, size_t buflen, size_t margin,
size_t initial)
{
size_t len;
char *ctx;
char *pv;
DKIM_SIGINFO *sig;
u_char hdr[DKIM_MAXHEADER + 1];
assert(dkim != NULL);
assert(buf != NULL);
assert(buflen != 0);
if (dkim->dkim_state != DKIM_STATE_EOM2 ||
dkim->dkim_mode != DKIM_MODE_SIGN)
return DKIM_STAT_INVALID;
#define DELIMITER "\001"
sig = dkim->dkim_signature;
if (sig == NULL)
sig = dkim->dkim_siglist[0];
/* compute and extract the signature header */
memset(hdr, '\0', sizeof hdr);
len = dkim_gensighdr(dkim, sig, hdr, sizeof hdr, DELIMITER);
if (len == DKIM_MAXHEADER)
{
dkim_error(dkim,
"generated signature header too large (max %d)",
DKIM_MAXHEADER);
return DKIM_STAT_NORESOURCE;
}
if (dkim->dkim_b64sig != NULL)
{
len = sm_strlcat(hdr, dkim->dkim_b64sig, sizeof hdr);
if (len >= sizeof hdr || len >= buflen)
{
dkim_error(dkim,
"generated signature header too large (max %d)",
DKIM_MAXHEADER);
return DKIM_STAT_NORESOURCE;
}
}
if (margin == 0)
{
bool first = TRUE;
for (pv = strtok_r(hdr, DELIMITER, &ctx);
pv != NULL;
pv = strtok_r(NULL, DELIMITER, &ctx))
{
if (!first)
sm_strlcat(buf, " ", buflen);
sm_strlcat(buf, pv, buflen);
first = FALSE;
}
}
else
{
bool first = TRUE;
bool forcewrap;
char *p;
char *q;
char *end;
char which[MAXTAGNAME + 1];
len = initial;
end = which + MAXTAGNAME;
for (pv = strtok_r(hdr, DELIMITER, &ctx);
pv != NULL;
pv = strtok_r(NULL, DELIMITER, &ctx))
{
for (p = pv, q = which; *p != '=' && q <= end; p++, q++)
{
*q = *p;
*(q + 1) = '\0';
}
/* force wrapping of "b=" ? */
forcewrap = FALSE;
if (strcmp(which, "b") == 0 &&
len + strlen(which) + 2 >= margin)
forcewrap = TRUE;
if (len == 0 || first)
{
sm_strlcat(buf, pv, buflen);
len += strlen(pv);
first = FALSE;
}
else if (forcewrap || len + strlen(pv) > margin)
{
if (strcmp(which, "h") == 0)
{ /* break at colons */
bool ifirst = TRUE;
char *tmp;
char *ctx2;
for (tmp = strtok_r(pv, ":", &ctx2);
tmp != NULL;
tmp = strtok_r(NULL, ":", &ctx2))
{
if (ifirst)
{
if (len + strlen(pv) + 2 > margin)
{
sm_strlcat(buf,
"\r\n\t",
buflen);
len = 8;
}
else
{
sm_strlcat(buf,
" ",
buflen);
len += 1;
}
sm_strlcat(buf, pv,
buflen);
len += strlen(pv);
ifirst = FALSE;
}
else if (len + strlen(tmp) + 1 > margin)
{
sm_strlcat(buf, ":",
buflen);
len += 1;
sm_strlcat(buf,
"\r\n\t ",
buflen);
len = 9;
sm_strlcat(buf, tmp,
buflen);
len += strlen(tmp);
}
else
{
sm_strlcat(buf, ":",
buflen);
len += 1;
sm_strlcat(buf, tmp,
buflen);
len += strlen(tmp);
}
}
}
else if (strcmp(which, "b") == 0 ||
strcmp(which, "bh") == 0 ||
strcmp(which, "z") == 0)
{ /* break at margins */
bool more;
int idx;
int offset;
char *x;
offset = strlen(which) + 1;
if (forcewrap ||
len + offset + 1 >= margin)
{
sm_strlcat(buf, "\r\n\t",
buflen);
len = 8;
idx += 3;
}
else
{
sm_strlcat(buf, " ", buflen);
len++;
idx++;
}
sm_strlcat(buf, which, buflen);
sm_strlcat(buf, "=", buflen);
len += offset;
idx += offset;
idx = strlen(buf);
for (x = pv + offset;
*x != '\0' && idx < buflen - 2;
x++)
{
more = (*(x + 1) != '\0');
buf[idx] = *x;
idx++;
buf[idx] = '\0';
len++;
if (len >= margin && more)
{
sm_strlcat(buf,
"\r\n\t",
buflen);
len = 8;
idx += 3;
}
}
}
else
{ /* break at delimiter */
sm_strlcat(buf, "\r\n\t", buflen);
len = 8;
sm_strlcat(buf, pv, buflen);
len += strlen(pv);
}
}
else
{
if (!first)
sm_strlcat(buf, " ", buflen);
first = FALSE;
len += 1;
sm_strlcat(buf, pv, buflen);
len += strlen(pv);
}
}
}
return DKIM_STAT_OK;
}
/*
** DKIM_REPORTINFO -- return the address to which failure reports are sent
**
** Parameters:
** dkim -- DKIM handle
** sig -- DKIM_SIGINFO handle
** fd -- descriptor for canonicalized message (returned)
** bfd -- descriptor for canonicalized body (returned)
** buf -- buffer for the address
** buflen -- bytes available at "buf"
**
** Return value:
** A DKIM_STAT_* constant.
**
** Notes:
** DKIM_STAT_CANTVRFY returned by this function means no policy record
** was found or "r" was not part of the policy record found.
**
** If the IETF "base-01" draft is being applied, the headers and
** body are hashed separately. Thus, there are two descriptors of
** interest. In that case, the body comes back in "bfd". Otherwise,
** "bfd" will be unaltered.
*/
DKIM_STAT
dkim_reportinfo(DKIM *dkim, DKIM_SIGINFO *sig, int *fd, int *bfd,
u_char *buf, size_t buflen)
{
DKIM_SET *set;
assert(dkim != NULL);
assert(sig != NULL);
if (dkim->dkim_state != DKIM_STATE_EOM2 ||
dkim->dkim_mode != DKIM_MODE_VERIFY)
return DKIM_STAT_INVALID;
if (sig->sig_hdrcanon != NULL)
{
switch (sig->sig_hashtype)
{
case DKIM_HASHTYPE_SHA1:
{
struct dkim_sha1 *sha1;
sha1 = (struct dkim_sha1 *) sig->sig_hdrcanon->canon_hash;
if (fd != NULL)
*fd = sha1->sha1_tmpfd;
if (bfd != NULL)
{
sha1 = (struct dkim_sha1 *) sig->sig_bodycanon->canon_hash;
*bfd = sha1->sha1_tmpfd;
}
break;
}
#ifdef SHA256_DIGEST_LENGTH
case DKIM_HASHTYPE_SHA256:
{
struct dkim_sha256 *sha256;
sha256 = (struct dkim_sha256 *) sig->sig_hdrcanon->canon_hash;
if (fd != NULL)
*fd = sha256->sha256_tmpfd;
if (bfd != NULL)
{
sha256 = (struct dkim_sha256 *) sig->sig_bodycanon->canon_hash;
*bfd = sha256->sha256_tmpfd;
}
break;
}
#endif /* SHA256_DIGEST_LENGTH */
}
}
/* extract the information */
set = dkim_set_first(dkim, DKIM_SETTYPE_POLICY);
if (set == NULL)
{
dkim_error(dkim, "no policy record found");
return DKIM_STAT_CANTVRFY;
}
dkim->dkim_reportaddr = dkim_param_get(set, "r");
if (dkim->dkim_reportaddr == NULL)
{
dkim_error(dkim, "no reporting address found in policy record");
return DKIM_STAT_CANTVRFY;
}
memset(buf, '\0', buflen);
sm_strlcpy(buf, dkim->dkim_reportaddr, buflen);
return DKIM_STAT_OK;
}
/*
** DKIM_SIG_GETIDENTITY -- retrieve identity of the signer
**
** Parameters:
** dkim -- DKIM handle
** sig -- DKIM_SIGINFO handle (or NULL to choose final one)
** val -- destination buffer
** vallen -- size of destination buffer
**
** Return value:
** A DKIM_STAT_* constant.
*/
DKIM_STAT
dkim_sig_getidentity(DKIM *dkim, DKIM_SIGINFO *sig, char *val, size_t vallen)
{
int len;
char *param;
struct dkim_set *set;
assert(dkim != NULL);
assert(val != NULL);
assert(vallen != 0);
if (dkim->dkim_mode == DKIM_MODE_SIGN)
return DKIM_STAT_INVALID;
if (sig == NULL)
{
sig = dkim->dkim_signature;
if (sig == NULL)
return DKIM_STAT_INVALID;
}
set = sig->sig_taglist;
param = dkim_param_get(set, "i");
if (param == NULL)
{
param = dkim_param_get(set, "d");
if (param == NULL)
return DKIM_STAT_INTERNAL;
len = snprintf(val, vallen, "@%s", param);
return (len < vallen ? DKIM_STAT_OK : DKIM_STAT_NORESOURCE);
}
else
{
len = dkim_qp_decode(param, val, vallen);
return (len < vallen ? DKIM_STAT_OK : DKIM_STAT_NORESOURCE);
}
}
/*
** DKIM_SIG_GETCANONLEN -- return canonicalized and total body lengths
**
** Parameters:
** dkim -- DKIM handle
** sig -- DKIM_SIGINFO handle
** msglen -- total body length (returned)
** canonlen -- total canonicalized length (returned)
** signlen -- maximum signed length (returned)
**
** Return value:
** A DKIM_STAT_* constant.
*/
DKIM_STAT
dkim_sig_getcanonlen(DKIM *dkim, DKIM_SIGINFO *sig, off_t *msglen,
off_t *canonlen, off_t *signlen)
{
assert(dkim != NULL);
assert(sig != NULL);
if (msglen != NULL)
*msglen = dkim->dkim_bodylen;
if (canonlen != NULL)
*canonlen = sig->sig_bodycanon->canon_wrote;
if (signlen != NULL)
*signlen = sig->sig_bodycanon->canon_length;
return DKIM_STAT_OK;
}
/*
** DKIM_SIG_GETFLAGS -- retreive signature handle flags
**
** Parameters:
** sig -- DKIM_SIGINFO handle
**
** Return value:
** An unsigned integer which is a bitwise-OR of the DKIM_SIGFLAG_*
** constants currently set in the provided handle.
*/
unsigned int
dkim_sig_getflags(DKIM_SIGINFO *sig)
{
assert(sig != NULL);
return sig->sig_flags;
}
/*
** DKIM_SIG_GETBH -- retreive signature handle "bh" test state
**
** Parameters:
** sig -- DKIM_SIGINFO handle
**
** Return value:
** An unsigned integer which is one of the DKIM_SIGBH_* constants
** indicating the current state of "bh" evaluation of the signature.
*/
unsigned int
dkim_sig_getbh(DKIM_SIGINFO *sig)
{
assert(sig != NULL);
return sig->sig_bh;
}
/*
** DKIM_SIG_GETKEYSIZE -- retrieve key size (in bits) when verifying
**
** Parameters:
** sig -- DKIM_SIGINFO handle
** bits -- number of bits in the key (returned)
**
** Return value:
** A DKIM_STAT_* constant.
*/
DKIM_STAT
dkim_sig_getkeysize(DKIM_SIGINFO *sig, unsigned int *bits)
{
assert(sig != NULL);
assert(bits != NULL);
if (sig->sig_keybits == 0)
return DKIM_STAT_INVALID;
*bits = sig->sig_keybits;
return DKIM_STAT_OK;
}
/*
** DKIM_SIG_GETSIGNALG -- retrieve signature algorithm when verifying
**
** Parameters:
** sig -- DKIM_SIGINFO handle
** alg -- signature algorithm used (returned)
**
** Return value:
** A DKIM_STAT_* constant.
*/
DKIM_STAT
dkim_sig_getsignalg(DKIM_SIGINFO *sig, dkim_alg_t *alg)
{
assert(sig != NULL);
assert(alg != NULL);
*alg = sig->sig_signalg;
return DKIM_STAT_OK;
}
/*
** DKIM_SIG_GETSIGNTIME -- retrieve signature timestamp
**
** Parameters:
** sig -- DKIM_SIGINFO handle
** when -- signature timestamp (returned)
**
** Return value:
** A DKIM_STAT_* constant.
*/
DKIM_STAT
dkim_sig_getsigntime(DKIM_SIGINFO *sig, time_t *when)
{
assert(sig != NULL);
assert(when != NULL);
if (sig->sig_timestamp == 0)
return DKIM_STAT_INVALID;
*when = (time_t) sig->sig_timestamp;
return DKIM_STAT_OK;
}
#ifdef _FFR_STATS
/*
** DKIM_SIG_GETCANONS -- retrieve canonicalizations used when signing
**
** Parameters:
** sig -- DKIM_SIGINFO handle from which to retrieve canonicalizations
** hdr -- Pointer to a dkim_canon_t where the header canonicalization
** should be stored
** body -- Pointer to a dkim_canon_t where the body canonicalization
** should be stored
**
** Return value:
** A DKIM_STAT_* constant.
*/
DKIM_STAT
dkim_sig_getcanons(DKIM_SIGINFO *sig, dkim_canon_t *hdr, dkim_canon_t *body)
{
assert(sig != NULL);
if (hdr != NULL)
*hdr = sig->sig_hdrcanonalg;
if (body != NULL)
*body = sig->sig_bodycanonalg;
return DKIM_STAT_OK;
}
#endif /* _FFR_STATS */
/*
** DKIM_SET_SIGNER -- set DKIM signature's signer
**
** Parameters:
** dkim -- DKIM signing handle
** signer -- signer to store
**
** Parameters:
** A DKIM_STAT_* constant.
*/
DKIM_STAT
dkim_set_signer(DKIM *dkim, const char *signer)
{
assert(dkim != NULL);
assert(signer != NULL);
if (dkim->dkim_mode != DKIM_MODE_SIGN)
return DKIM_STAT_INVALID;
if (dkim->dkim_signer == NULL)
{
dkim->dkim_signer = DKIM_MALLOC(dkim, MAXADDRESS + 1);
if (dkim->dkim_signer == NULL)
{
dkim_error(dkim, "unable to allocate %d byte(s)",
MAXADDRESS + 1);
return DKIM_STAT_NORESOURCE;
}
}
sm_strlcpy(dkim->dkim_signer, signer, MAXADDRESS + 1);
return DKIM_STAT_OK;
}
/*
** DKIM_GETERROR -- return any stored error string from within the DKIM
** context handle
**
** Parameters:
** dkim -- DKIM handle from which to retrieve an error string
**
** Return value:
** A pointer to the stored string, or NULL if none was stored.
*/
const char *
dkim_geterror(DKIM *dkim)
{
assert(dkim != NULL);
return dkim->dkim_error;
}
/*
** DKIM_GETRESULTSTR -- translate a DKIM_STAT_* constant to a string
**
** Parameters:
** result -- DKIM_STAT_* constant to translate
**
** Return value:
** Pointer to a text describing "result", or NULL if none exists
*/
const char *
dkim_getresultstr(DKIM_STAT result)
{
return dkim_code_to_name(results, result);
}
/*
** DKIM_GETPRESULT -- retrieve policy result
**
** Parameters:
** dkim -- DKIM handle from which to get policy result
**
** Return value:
** DKIM policy check result.
*/
int
dkim_getpresult(DKIM *dkim)
{
assert(dkim != NULL);
return dkim->dkim_presult;
}
/*
** DKIM_GETPRESULTSTR -- retrieve policy result string
**
** Parameters:
** presult -- policy result code to translate
**
** Return value:
** Pointer to text that describes "presult".
*/
const char *
dkim_getpresultstr(int presult)
{
return dkim_code_to_name(policyresults, presult);
}
/*
** DKIM_GETHANDLINGSTR -- retrieve handling result string
**
** Parameters:
** hresult -- policy handling code to translate
**
** Return value:
** Pointer to text that describes "hresult".
*/
const char *
dkim_gethandlingstr(int hresult)
{
return dkim_code_to_name(handlings, hresult);
}
/*
** DKIM_GETPOLICYSTR -- retrieve policy string
**
** Parameters:
** policy -- policy code to translate
**
** Return value:
** Pointer to text that describes "policy".
*/
const char *
dkim_getpolicystr(int policy)
{
return dkim_code_to_name(policies, policy);
}
/*
** DKIM_SET_DNS_CALLBACK -- set the DNS wait callback
**
** Parameters:
** libdkim -- DKIM library handle
** func -- function to call; should take an opaque context pointer
** interval -- how often to call back
**
** Return value:
** DKIM_STAT_OK -- success
** DKIM_STAT_INVALID -- invalid use
*/
DKIM_STAT
dkim_set_dns_callback(DKIM_LIB *libdkim, void (*func)(const void *context),
unsigned int interval)
{
assert(libdkim != NULL);
#if USE_ARLIB
if (func != NULL && interval == 0)
return DKIM_STAT_INVALID;
libdkim->dkiml_dns_callback = func;
libdkim->dkiml_callback_int = interval;
return DKIM_STAT_OK;
#else /* USE_ARLIB */
return DKIM_STAT_INVALID;
#endif /* USE_ARLIB */
}
/*
** DKIM_SET_USER_CONTEXT -- set user context pointer
**
** Parameters:
** dkim -- DKIM handle
** ctx -- opaque context pointer
**
** Return value:
** DKIM_STAT_OK
*/
DKIM_STAT
dkim_set_user_context(DKIM *dkim, const void *ctx)
{
assert(dkim != NULL);
dkim->dkim_user_context = ctx;
return DKIM_STAT_OK;
}
/*
** DKIM_GET_USER_CONTEXT -- get user context pointer
**
** Parameters:
** dkim -- DKIM handle
**
** Return value:
** User context associated with a DKIM handle
*/
const void *
dkim_get_user_context(DKIM *dkim)
{
assert(dkim != NULL);
return dkim->dkim_user_context;
}
#ifdef _FFR_PARSE_TIME
/*
** DKIM_GET_MSGDATE -- retrieve value extracted from the Date: header
**
** Parameters:
** dkim -- DKIM handle
**
** Return value:
** time_t representing the value in the Date: header of the message,
** or 0 if no such header was found or the value in it was unusable
*/
time_t
dkim_get_msgdate(DKIM *dkim)
{
assert(dkim != NULL);
return dkim->dkim_msgdate;
}
#endif /* _FFR_PARSE_TIME */
/*
** DKIM_GETMODE -- return the mode (signing, verifying, etc.) of a handle
**
** Parameters:
** dkim -- DKIM handle
**
** Return value:
** A DKIM_MODE_* constant.
*/
int
dkim_getmode(DKIM *dkim)
{
assert(dkim != NULL);
return dkim->dkim_mode;
}
/*
** DKIM_GETDOMAIN -- retrieve policy domain from a DKIM context
**
** Parameters:
** dkim -- DKIM handle
**
** Return value:
** Pointer to the domain used for policy checking or NULL if no domain
** could be determined.
*/
u_char *
dkim_getdomain(DKIM *dkim)
{
assert(dkim != NULL);
return dkim->dkim_domain;
}
/*
** DKIM_SET_KEY_LOOKUP -- set the key lookup function
**
** Parameters:
** libdkim -- DKIM library handle
** func -- function to call
**
** Return value:
** DKIM_STAT_OK
*/
DKIM_STAT
dkim_set_key_lookup(DKIM_LIB *libdkim,
DKIM_STAT (*func)(DKIM *dkim, DKIM_SIGINFO *sig,
u_char *buf, size_t buflen))
{
assert(libdkim != NULL);
libdkim->dkiml_key_lookup = func;
return DKIM_STAT_OK;
}
/*
** DKIM_SET_POLICY_LOOKUP -- set the policy lookup function
**
** Parameters:
** libdkim -- DKIM library handle
** func -- function to call
**
** Return value:
** DKIM_STAT_OK
*/
DKIM_STAT
dkim_set_policy_lookup(DKIM_LIB *libdkim,
int (*func)(DKIM *dkim, u_char *query, bool usemx,
u_char *buf, size_t buflen, int *qstat))
{
assert(libdkim != NULL);
libdkim->dkiml_policy_lookup = func;
return DKIM_STAT_OK;
}
/*
** DKIM_SET_SIGNATURE_HANDLE -- set the user handle allocation function
**
** Parameters:
** libdkim -- DKIM library handle
** func -- function to call
**
** Return value:
** DKIM_STAT_OK -- success
** DKIM_STAT_INVALID -- called against a signing handle or too late
** (i.e. after dkim_eoh() was called)
*/
DKIM_STAT
dkim_set_signature_handle(DKIM_LIB *libdkim, void * (*func)(void *closure))
{
assert(libdkim != NULL);
libdkim->dkiml_sig_handle = func;
return DKIM_STAT_OK;
}
/*
** DKIM_SET_SIGNATURE_HANDLE_FREE -- set the user handle deallocation function
**
** Parameters:
** libdkim -- DKIM library handle
** func -- function to call
**
** Return value:
** DKIM_STAT_OK
*/
DKIM_STAT
dkim_set_signature_handle_free(DKIM_LIB *libdkim,
void (*func)(void *closure, void *user))
{
assert(libdkim != NULL);
libdkim->dkiml_sig_handle_free = func;
return DKIM_STAT_OK;
}
/*
** DKIM_SET_SIGNATURE_TAGVALUES -- set the user handle population function
**
** Parameters:
** libdkim -- DKIM library handle
** func -- function to call
**
** Return value:
** DKIM_STAT_OK
*/
DKIM_STAT
dkim_set_signature_tagvalues(DKIM_LIB *libdkim, void (*func)(void *user,
dkim_param_t pcode,
const u_char *param,
const u_char *value))
{
assert(libdkim != NULL);
libdkim->dkiml_sig_tagvalues = func;
return DKIM_STAT_OK;
}
/*
** DKIM_SET_PRESCREEN -- set the user prescreen function
**
** Parameters:
** libdkim -- DKIM library handle
** func -- function to call
**
** Return value:
** DKIM_STAT_OK
*/
DKIM_STAT
dkim_set_prescreen(DKIM_LIB *libdkim, DKIM_CBSTAT (*func)(DKIM *dkim,
DKIM_SIGINFO **sigs,
int nsigs))
{
assert(libdkim != NULL);
libdkim->dkiml_prescreen = func;
return DKIM_STAT_OK;
}
/*
** DKIM_SET_FINAL -- set the user final scan function
**
** Parameters:
** libdkim -- DKIM library handle
** func -- function to call
**
** Return value:
** DKIM_STAT_OK
*/
DKIM_STAT
dkim_set_final(DKIM_LIB *libdkim, DKIM_CBSTAT (*func)(DKIM *dkim,
DKIM_SIGINFO **sigs,
int nsigs))
{
assert(libdkim != NULL);
libdkim->dkiml_final = func;
return DKIM_STAT_OK;
}
/*
** DKIM_SIG_GETCONTEXT -- retrieve user-provided context from a DKIM_SIGINFO
**
** Parameters:
** siginfo -- pointer to a DKIM_SIGINFO from which to extract context
**
** Return value:
** Pointer to the user context provided by an earlier call to the
** handle allocator (see above), or NULL if none was ever set.
*/
void *
dkim_sig_getcontext(DKIM_SIGINFO *siginfo)
{
assert(siginfo != NULL);
return siginfo->sig_context;
}
/*
** DKIM_SIG_GETSELECTOR -- retrieve selector from a DKIM_SIGINFO
**
** Parameters:
** siginfo -- pointer to a DKIM_SIGINFO from which to extract the selector
**
** Return value:
** Pointer to the selector associated with the DKIM_SIGINFO.
*/
char *
dkim_sig_getselector(DKIM_SIGINFO *siginfo)
{
assert(siginfo != NULL);
return siginfo->sig_selector;
}
/*
** DKIM_SIG_GETDOMAIN -- retrieve domain from a DKIM_SIGINFO
**
** Parameters:
** siginfo -- pointer to a DKIM_SIGINFO from which to extract the domain
**
** Return value:
** Pointer to the domain associated with the DKIM_SIGINFO.
*/
char *
dkim_sig_getdomain(DKIM_SIGINFO *siginfo)
{
assert(siginfo != NULL);
return siginfo->sig_domain;
}
/*
** DKIM_SIG_GETERROR -- retrieve an error code from a DKIM_SIGINFO
**
** Parameters:
** siginfo -- pointer to a DKIM_SIGINFO from which to extract context
**
** Return value:
** A DKIM_SIGERROR_* constant.
*/
int
dkim_sig_geterror(DKIM_SIGINFO *siginfo)
{
assert(siginfo != NULL);
return siginfo->sig_error;
}
/*
** DKIM_SIG_GETERRORSTR -- translate a DKIM_SIGERROR_* constant to a string
**
** Parameters:
** sigerr -- DKIM_SIGERROR_* constant to translate
**
** Return value:
** A pointer to a human-readable expression of "sigerr", or NULL if none
** exists.
*/
const char *
dkim_sig_geterrorstr(DKIM_SIGERROR sigerr)
{
return dkim_code_to_name(sigerrors, sigerr);
}
/*
** DKIM_SIG_IGNORE -- mark a signature referenced by a DKIM_SIGINFO with
** an "ignore" flag
**
** Parameters:
** siginfo -- pointer to a DKIM_SIGINFO to update
**
** Return value:
** None.
*/
void
dkim_sig_ignore(DKIM_SIGINFO *siginfo)
{
assert(siginfo != NULL);
siginfo->sig_flags |= DKIM_SIGFLAG_IGNORE;
}
/*
** DKIM_SSL_VERSION -- return version of OpenSSL that was used to build
** the library
**
** Parameters:
** None.
**
** Return value:
** The constant OPENSSL_VERSION_NUMBER as defined by OpenSSL.
*/
unsigned long
dkim_ssl_version(void)
{
return OPENSSL_VERSION_NUMBER;
}
#ifdef QUERY_CACHE
/*
** DKIM_FLUSH_CACHE -- purge expired records from the cache
**
** Parameters:
** lib -- DKIM library handle, returned by dkim_init()
**
** Return value:
** -1 -- caching is not in effect
** >= 0 -- number of purged records
*/
int
dkim_flush_cache(DKIM_LIB *lib)
{
int err;
assert(lib != NULL);
if (lib->dkiml_cache == NULL)
return -1;
return dkim_cache_expire(lib->dkiml_cache, 0, &err);
}
/*
** DKIM_GETCACHESTATS -- retrieve cache statistics
**
** Parameters:
** queries -- number of queries handled (returned)
** hits -- number of cache hits (returned)
** expired -- number of expired hits (returned)
**
** Return value:
** None.
**
** Notes:
** Any of the parameters may be NULL if the corresponding datum
** is not of interest.
*/
void
dkim_getcachestats(u_int *queries, u_int *hits, u_int *expired)
{
return dkim_cache_stats(queries, hits, expired);
}
#endif /* QUERY_CACHE */
syntax highlighted by Code2HTML, v. 0.9.1