Skip to content
Permalink
2aafb015a5
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
2050 lines (1880 sloc) 55.5 KB
/*************************************************************************
** The user interface to the rest of this library.
**
** Copyright (c) 2012-2016, 2018, 2021, The Trusted Domain Project.
** All rights reserved.
**************************************************************************/
#include <ctype.h>
#include "opendmarc_internal.h"
#include "dmarc.h"
/* libbsd if found */
#ifdef USE_BSD_H
# include <bsd/string.h>
#endif /* USE_BSD_H */
/* libstrl if needed */
#ifdef USE_STRL_H
# include <strl.h>
#endif /* USE_STRL_H */
/* opendmarc_strl if needed */
#ifdef USE_DMARCSTRL_H
# include <opendmarc_strl.h>
#endif /* USE_DMARCSTRL_H */
/*
** CHECK_DOMAIN -- check for syntactical validity of a domain name
**
** Parameters:
** domain -- domain name to check
**
** Return value:
** TRUE if the syntax was fine, FALSE otherwise.
*/
bool check_domain(u_char *domain)
{
u_char *dp;
for (dp = domain; *dp != '\0'; dp++)
{
if (!(isalpha(*dp) ||
isdigit(*dp) ||
*dp == '.' ||
*dp == '-' ||
*dp == '_'))
return FALSE;
}
return TRUE;
}
/**************************************************************************
** OPENDMARC_POLICY_LIBRARY_INIT -- Initialize The Library
** Parameters:
** lib_init -- Address of a filled in DMARC_LIB_T structure
** Returns:
** DMARC_PARSE_OKAY -- on success
** DMARC_PARSE_ERROR_NULL_CTX -- if lib_init is null
** DMARC_TLD_ERROR_UNKNOWN -- If lip_init->tld_type is undefined
** Side Effects:
** Sets a global pointer
** Warning:
** This function is not thread safe so only call once on
** startup.
***************************************************************************/
static OPENDMARC_LIB_T *Opendmarc_Libp = NULL;
static OPENDMARC_LIB_T Opendmarc_Lib;
OPENDMARC_STATUS_T
opendmarc_policy_library_init(OPENDMARC_LIB_T *lib_init)
{
int ret = DMARC_PARSE_OKAY;
if (lib_init == NULL)
return DMARC_PARSE_ERROR_NULL_CTX;
(void) memcpy(&Opendmarc_Lib, lib_init, sizeof(OPENDMARC_LIB_T));
Opendmarc_Libp = &Opendmarc_Lib;
errno = 0;
if ((Opendmarc_Libp->tld_source_file)[0] != '\0')
{
switch (Opendmarc_Libp->tld_type)
{
case OPENDMARC_TLD_TYPE_MOZILLA:
ret = opendmarc_tld_read_file(Opendmarc_Libp->tld_source_file,
"//", "*.", "!");
if (ret != 0)
ret = errno;
break;
default:
return DMARC_TLD_ERROR_UNKNOWN;
}
}
return ret;
}
/**************************************************************************
** OPENDMARC_POLICY_LIBRARY_SHUTDOWN -- Shutdown The Libarary
** Parameters:
** lib_init -- The prior DMARC_LIB_T strucgture
** Returns:
** DMARC_PARSE_OKAY -- always
** Side Effects:
** May free memory
** Warning:
***************************************************************************/
OPENDMARC_STATUS_T
opendmarc_policy_library_shutdown(OPENDMARC_LIB_T *lib_init)
{
(void) opendmarc_tld_shutdown();
return DMARC_PARSE_OKAY;
}
/**************************************************************************
** OPENDMARC_POLICY_CONNECT_INIT -- Get policy context for connection
** Parameters:
** ip_addr -- An IP addresss in string form.
** is_ipv6 -- Zero for IPv4, non-zero for IPv6
** Returns:
** pctx -- An allocated and initialized context pointer.
** NULL -- On failure and sets errno
** Side Effects:
** Allocates memory.
***************************************************************************/
DMARC_POLICY_T *
opendmarc_policy_connect_init(u_char *ip_addr, int is_ipv6)
{
DMARC_POLICY_T *pctx;
int xerrno;
if (ip_addr == NULL)
{
errno = EINVAL;
return NULL;
}
pctx = malloc(sizeof(DMARC_POLICY_T));
if (pctx == NULL)
{
return NULL;
}
(void) memset(pctx, '\0', sizeof(DMARC_POLICY_T));
pctx->p = DMARC_RECORD_P_UNSPECIFIED;
pctx->sp = DMARC_RECORD_P_UNSPECIFIED;
pctx->ip_addr = (u_char *)strdup((char *)ip_addr);
if (pctx->ip_addr == NULL)
{
xerrno = errno;
(void) free(pctx);
errno = xerrno;
return NULL;
}
if (is_ipv6 == 0)
pctx->ip_type = DMARC_POLICY_IP_TYPE_IPV4;
else
pctx->ip_type = DMARC_POLICY_IP_TYPE_IPV6;
return pctx;
}
/**************************************************************************
** OPENDMARC_POLICY_CONNECT_CLEAR -- Zero the policy context but doesn't
** free it
**
** Parameters:
** pctx -- The context to zero.
** Returns:
** pctx -- Zeroed but still allocated context
** NULL -- On failure and sets errno
** Side Effects:
** Frees memory.
***************************************************************************/
DMARC_POLICY_T *
opendmarc_policy_connect_clear(DMARC_POLICY_T *pctx)
{
if (pctx == NULL)
{
errno = EINVAL;
return NULL;
}
if (pctx->ip_addr != NULL)
(void) free(pctx->ip_addr);
if (pctx->from_domain != NULL)
(void) free(pctx->from_domain);
if (pctx->spf_domain != NULL)
(void) free(pctx->spf_domain);
if (pctx->dkim_domain != NULL)
(void) free(pctx->dkim_domain);
if (pctx->dkim_selector != NULL)
(void) free(pctx->dkim_selector);
if (pctx->spf_human_outcome != NULL)
(void) free(pctx->spf_human_outcome);
if (pctx->dkim_human_outcome != NULL)
(void) free(pctx->dkim_human_outcome);
if (pctx->organizational_domain != NULL)
(void) free(pctx->organizational_domain);
pctx->rua_list = opendmarc_util_clearargv(pctx->rua_list);
pctx->rua_cnt = 0;
pctx->ruf_list = opendmarc_util_clearargv(pctx->ruf_list);
pctx->ruf_cnt = 0;
pctx->fo = 0;
(void) memset(pctx, '\0', sizeof(DMARC_POLICY_T));
pctx->p = DMARC_RECORD_P_UNSPECIFIED;
return pctx;
}
/**************************************************************************
** OPENDMARC_POLICY_CONNECT_RSET -- Rset for another message
** Usefull if there is more than a single envelope per connection.
** Usefull during an SMTP RSET
**
** Parameters:
** pctx -- The context to rset.
** Returns:
** pctx -- RSET context
** NULL -- On failure and sets errno
** Side Effects:
** Frees memory.
** Preserves the IP address and type
***************************************************************************/
DMARC_POLICY_T *
opendmarc_policy_connect_rset(DMARC_POLICY_T *pctx)
{
u_char *ip_save;
int ip_type;
if (pctx == NULL)
{
errno = EINVAL;
return NULL;
}
ip_save = pctx->ip_addr;
pctx->ip_addr = NULL;
ip_type = pctx->ip_type;
pctx->ip_type = -1;
pctx = opendmarc_policy_connect_clear(pctx);
if (pctx == NULL)
return NULL;
pctx->ip_addr = ip_save;
pctx->ip_type = ip_type;
return pctx;
}
/**************************************************************************
** OPENDMARC_POLICY_CONNECT_SHUTDOWN -- Free the policy context
** Frees and deallocates the context
**
** Parameters:
** pctx -- The context to free and deallocate.
** Returns:
** NULL -- Always
** Side Effects:
** Frees memory.
***************************************************************************/
DMARC_POLICY_T *
opendmarc_policy_connect_shutdown(DMARC_POLICY_T *pctx)
{
if (pctx != NULL)
{
pctx = opendmarc_policy_connect_clear(pctx);
(void) free(pctx);
pctx = NULL;
}
return pctx;
}
int
opendmarc_policy_check_alignment(u_char *subdomain, u_char *tld, int mode)
{
u_char rev_sub[512];
u_char rev_tld[512];
u_char tld_buf[512];
u_char *ep;
int ret;
if (subdomain == NULL)
return EINVAL;
if (tld == NULL)
return EINVAL;
if (mode== DMARC_RECORD_A_UNSPECIFIED)
mode= DMARC_RECORD_A_RELAXED;
(void) memset(tld_buf, '\0', sizeof tld_buf);
(void) strlcpy(tld_buf, tld, sizeof tld_buf);
(void) memset(rev_sub, '\0', sizeof rev_sub);
(void) opendmarc_reverse_domain(subdomain, rev_sub, sizeof rev_sub);
ep = rev_sub + strlen(rev_sub) -1;
if (*ep != '.')
(void) strlcat((char *)rev_sub, ".", sizeof rev_sub);
(void) memset(rev_tld, '\0', sizeof rev_tld);
(void) opendmarc_reverse_domain(tld_buf, rev_tld, sizeof rev_tld);
ep = rev_tld + strlen(rev_tld) -1;
if (*ep != '.')
(void) strlcat((char *)rev_tld, ".", sizeof rev_tld);
/*
* Perfect match is aligned irrespective of relaxed or strict.
*/
if (strcasecmp(rev_tld, rev_sub) == 0)
return 0;
ret = strncasecmp(rev_tld, rev_sub, strlen(rev_tld));
if (ret == 0 && mode == DMARC_RECORD_A_RELAXED)
return 0;
ret = strncasecmp(rev_sub, rev_tld, strlen(rev_sub));
if (ret == 0 && mode == DMARC_RECORD_A_RELAXED)
return 0;
ret = opendmarc_get_tld(tld, tld_buf, sizeof tld_buf);
if (ret != 0)
return -1;
(void) memset(rev_tld, '\0', sizeof rev_tld);
(void) opendmarc_reverse_domain(tld_buf, rev_tld, sizeof rev_tld);
ep = rev_tld + strlen(rev_tld) -1;
if (*ep != '.')
(void) strlcat((char *)rev_tld, ".", sizeof rev_tld);
/*
* Perfect match is aligned irrespective of relaxed or strict.
*/
if (strcasecmp(rev_tld, rev_sub) == 0)
return 0;
ret = strncasecmp(rev_tld, rev_sub, strlen(rev_tld));
if (ret == 0 && mode == DMARC_RECORD_A_RELAXED)
return 0;
ret = strncasecmp(rev_sub, rev_tld, strlen(rev_sub));
if (ret == 0 && mode == DMARC_RECORD_A_RELAXED)
return 0;
return -1;
}
/**************************************************************************
** OPENDMARC_POLICY_STORE_FROM_DOMAIN -- Store domain from the From: header.
** If the domain is an address parse the domain from it.
** The domain is needed to perform alignment checks.
** Parameters:
** pctx -- The context to uptdate
** from_domain -- A string
** Returns:
** DMARC_PARSE_OKAY -- On success
** DMARC_PARSE_ERROR_NULL_CTX -- If pctx was NULL
** DMARC_PARSE_ERROR_EMPTY -- if from_domain NULL or zero
** DMARC_PARSE_ERROR_NO_DOMAIN -- No domain in from_domain
** Side Effects:
** Allocates memory.
** Note:
** Does not check to insure that the found domain is a
** syntactically valid domain. It is okay for domain to
** puney decoded into 8-bit data.
***************************************************************************/
OPENDMARC_STATUS_T
opendmarc_policy_store_from_domain(DMARC_POLICY_T *pctx, u_char *from_domain)
{
char domain_buf[256];
char *dp;
if (pctx == NULL)
return DMARC_PARSE_ERROR_NULL_CTX;
if (from_domain == NULL || strlen((char *)from_domain) == 0)
return DMARC_PARSE_ERROR_EMPTY;
dp = opendmarc_util_finddomain(from_domain, domain_buf, sizeof domain_buf);
if (dp == NULL)
return DMARC_PARSE_ERROR_NO_DOMAIN;
pctx->from_domain = strdup((char *)dp);
if (pctx->from_domain == NULL)
return DMARC_PARSE_ERROR_NO_ALLOC;
return DMARC_PARSE_OKAY;
}
/**************************************************************************
** OPENDMARC_POLICY_STORE_SPF -- Store spf results
** Okay to supply the raw MAIL From: data
**
** Parameters:
** pctx -- The context to uptdate
** domain -- The domain used to verify SPF
** result -- DMARC_POLICY_SPF_OUTCOME_NONE
** or DMARC_POLICY_SPF_OUTCOME_PASS
** or DMARC_POLICY_SPF_OUTCOME_FAIL
** or DMARC_POLICY_SPF_OUTCOME_TMPFAIL
** origin -- DMARC_POLICY_SPF_ORIGIN_MAILFROM
** or DMARC_POLICY_SPF_ORIGIN_HELO
** human_readable -- A human readable reason for failure
** Returns:
** DMARC_PARSE_OKAY -- On success
** DMARC_PARSE_ERROR_NULL_CTX -- If pctx was NULL
** DMARC_PARSE_ERROR_EMPTY -- if domain NULL or zero
** DMARC_PARSE_ERROR_NO_DOMAIN -- No domain in domain
** Side Effects:
** Allocates memory.
** Note:
** Does not check to insure that the domain is a
** syntactically valid domain. It is okay for domain to
** puney decoded into 8-bit data.
***************************************************************************/
OPENDMARC_STATUS_T
opendmarc_policy_store_spf(DMARC_POLICY_T *pctx, u_char *domain, int result, int origin, u_char *human_readable)
{
char domain_buf[256];
char *dp;
if (pctx == NULL)
return DMARC_PARSE_ERROR_NULL_CTX;
if (domain == NULL || strlen((char *)domain) == 0)
return DMARC_PARSE_ERROR_EMPTY;
dp = opendmarc_util_finddomain(domain, domain_buf, sizeof domain_buf);
if (dp == NULL)
return DMARC_PARSE_ERROR_NO_DOMAIN;
if (!check_domain(dp))
return DMARC_PARSE_ERROR_BAD_VALUE;
if (human_readable != NULL)
pctx->spf_human_outcome = strdup((char *)human_readable);
pctx->spf_domain = strdup((char *)dp);
if (pctx->spf_domain == NULL)
return DMARC_PARSE_ERROR_NO_ALLOC;
switch (result)
{
case DMARC_POLICY_SPF_OUTCOME_NONE:
case DMARC_POLICY_SPF_OUTCOME_PASS:
case DMARC_POLICY_SPF_OUTCOME_FAIL:
case DMARC_POLICY_SPF_OUTCOME_TMPFAIL:
pctx->spf_outcome = result;
break;
default:
return DMARC_PARSE_ERROR_BAD_SPF_MACRO;
}
switch (origin)
{
case DMARC_POLICY_SPF_ORIGIN_MAILFROM:
case DMARC_POLICY_SPF_ORIGIN_HELO:
pctx->spf_origin = origin;
break;
default:
return DMARC_PARSE_ERROR_BAD_SPF_MACRO;
}
return DMARC_PARSE_OKAY;
}
/**************************************************************************
** OPENDMARC_POLICY_STORE_DKIM -- Store dkim results
**
** Parameters:
** pctx -- The context to update
** d_equal_domain -- The the domain from the d=
** s_equal_selector -- The selector from the s=
** dkim_result -- DMARC_POLICY_DKIM_OUTCOME_NONE
** or DMARC_POLICY_DKIM_OUTCOME_PASS
** or DMARC_POLICY_DKIM_OUTCOME_FAIL
** or DMARC_POLICY_DKIM_OUTCOME_TMPFAIL
** human_result -- A human readable reason for failure
** Returns:
** DMARC_PARSE_OKAY -- On success
** DMARC_PARSE_ERROR_NULL_CTX -- If pctx was NULL
** DMARC_PARSE_ERROR_EMPTY -- if domain NULL or zero
** DMARC_PARSE_ERROR_NO_DOMAIN -- No domain in domain
** DMARC_PARSE_ERROR_NO_ALLOC -- Memory allocation failed
** DMARC_FROM_DOMAIN_ABSENT -- No From: domain
** Side Effects:
** Allocates memory.
** Note:
** Does not check to insure that the domain is a
** syntactically valid domain. It is okay for domain to
** puney decoded into 8-bit data.
***************************************************************************/
OPENDMARC_STATUS_T
opendmarc_policy_store_dkim(DMARC_POLICY_T *pctx, u_char *d_equal_domain,
u_char *s_equal_selector, int dkim_result, u_char *human_result)
{
char domain_buf[256];
u_char *dp;
int result = DMARC_POLICY_DKIM_OUTCOME_NONE;
if (pctx == NULL)
return DMARC_PARSE_ERROR_NULL_CTX;
if (d_equal_domain == NULL || strlen((char *)d_equal_domain) == 0)
return DMARC_PARSE_ERROR_EMPTY;
if (pctx->from_domain == NULL)
return DMARC_FROM_DOMAIN_ABSENT;
if (!check_domain(d_equal_domain))
return DMARC_PARSE_ERROR_BAD_VALUE;
switch (dkim_result)
{
case DMARC_POLICY_DKIM_OUTCOME_NONE:
case DMARC_POLICY_DKIM_OUTCOME_PASS:
case DMARC_POLICY_DKIM_OUTCOME_FAIL:
case DMARC_POLICY_DKIM_OUTCOME_TMPFAIL:
result = dkim_result;
break;
default:
return DMARC_PARSE_ERROR_BAD_DKIM_MACRO;
}
if (pctx->dkim_final == TRUE)
return DMARC_PARSE_OKAY;
dp = opendmarc_util_finddomain(d_equal_domain, domain_buf, sizeof domain_buf);
if (dp == NULL || strlen(dp) == 0)
return DMARC_PARSE_ERROR_NO_DOMAIN;
/*
* If the d= domain is an exact match to the from_domain
* select this one as the domain of choice.
* If the outcome is pass, make this the final choice.
*/
if (strcasecmp((char *)dp, pctx->from_domain) == 0)
{
if (pctx->dkim_domain != NULL)
{
(void) free(pctx->dkim_domain);
pctx->dkim_domain = NULL;
}
if (pctx->dkim_selector != NULL)
{
(void) free(pctx->dkim_selector);
pctx->dkim_selector = NULL;
}
if (result == DMARC_POLICY_DKIM_OUTCOME_PASS)
{
pctx->dkim_final = TRUE;
goto set_final;
}
if (pctx->dkim_outcome == DMARC_POLICY_DKIM_OUTCOME_PASS)
return DMARC_PARSE_OKAY;
goto set_final;
}
/*
* See if the d= is a superset of the from domain.
* If so and if we have not already found
* a best match, make this the temporary best match.
*/
if (opendmarc_policy_check_alignment(dp, pctx->from_domain,
pctx->adkim) == 0)
{
if (pctx->dkim_domain != NULL)
{
(void) free(pctx->dkim_domain);
pctx->dkim_domain = NULL;
}
if (pctx->dkim_selector != NULL)
{
(void) free(pctx->dkim_selector);
pctx->dkim_selector = NULL;
}
if (result == DMARC_POLICY_DKIM_OUTCOME_PASS)
goto set_final;
}
/*
* If we found any record so far that passed, preserve it; if the new entry
* is not aligned, only replace an existing one by an unaligned one if it was
* not a pass, but make sure to update the domain in that case!
*/
if (pctx->dkim_outcome == DMARC_POLICY_DKIM_OUTCOME_PASS) {
return DMARC_PARSE_OKAY;
} else {
(void) free(pctx->dkim_domain);
pctx->dkim_domain = NULL;
}
set_final:
if (pctx->dkim_domain == NULL)
pctx->dkim_domain = strdup((char *)dp);
if (pctx->dkim_domain == NULL)
return DMARC_PARSE_ERROR_NO_ALLOC;
if (pctx->dkim_selector == NULL && s_equal_selector != NULL)
pctx->dkim_selector = strdup((char *)s_equal_selector);
if (human_result != NULL)
{
if (pctx->dkim_human_outcome != NULL)
(void) free(pctx->dkim_human_outcome);
pctx->dkim_human_outcome = strdup((char *)human_result);
}
pctx->dkim_outcome = result;
return DMARC_PARSE_OKAY;
}
/**************************************************************************
** OPENDMARC_POLICY_QUERY_DMARC_XDOMAIN -- Verify that we have permission
** to send to domain
** Parameters:
** pctx -- The context to uptdate
** uri -- URI listed in DMARC record
** Returns:
** DMARC_PARSE_OKAY -- On success, and fills pctx
** DMARC_PARSE_ERROR_NULL_CTX -- If pctx was NULL
** DMARC_PARSE_ERROR_EMPTY -- if domain NULL or zero
** DMARC_PARSE_ERROR_NO_DOMAIN -- No domain in domain
** DMARC_DNS_ERROR_TMPERR -- No domain, try again later
** DMARC_DNS_ERROR_NO_RECORD -- No DMARC record found.
** Side Effects:
** Performs one or more DNS lookups
**
***************************************************************************/
OPENDMARC_STATUS_T
opendmarc_policy_query_dmarc_xdomain(DMARC_POLICY_T *pctx, u_char *uri)
{
u_char buf[BUFSIZ];
u_char copy[256];
u_char domain[256];
u_char domain_tld[256];
u_char uri_tld[256];
u_char *ret = NULL;
int dns_reply = 0;
int i = 0;
int err = 0;
if (pctx == NULL || pctx->from_domain == NULL)
return DMARC_PARSE_ERROR_NULL_CTX;
if (uri == NULL)
return DMARC_PARSE_ERROR_EMPTY;
memset(buf, '\0', sizeof buf);
memset(copy, '\0', sizeof copy);
memset(domain, '\0', sizeof domain);
memset(domain_tld, '\0', sizeof domain_tld);
memset(uri_tld, '\0', sizeof uri_tld);
/* Get out domain from our URI */
if (strncasecmp(uri, "mailto:", 7) == 0)
uri += 7;
if (opendmarc_util_finddomain(uri, domain, sizeof domain) == NULL)
return DMARC_PARSE_ERROR_NO_DOMAIN;
/* Ensure that we're not doing a cross-domain check */
err = 0;
err = opendmarc_get_tld(domain, uri_tld, sizeof uri_tld);
err += opendmarc_get_tld(pctx->from_domain, domain_tld, sizeof domain_tld);
if (err != 0)
return DMARC_DNS_ERROR_NO_RECORD;
if (strncasecmp((char *) uri_tld, (char *) domain_tld,
sizeof uri_tld) == 0)
return DMARC_PARSE_OKAY;
strlcpy((char *) copy, (char *) pctx->from_domain, sizeof copy);
strlcat((char *) copy, "._report._dmarc.", sizeof copy);
strlcat((char *) copy, (char *) domain, sizeof copy);
/* Query DNS */
for (i = 0; i < DNS_MAX_RETRIES && ret == NULL; i++)
{
ret = (u_char *) dmarc_dns_get_record((char *) copy, &dns_reply,
(char *) buf, sizeof buf);
if (ret != 0 || dns_reply == HOST_NOT_FOUND)
break;
/* requery if didn't resolve CNAME */
if (ret == NULL && *buf != '\0')
{
strlcpy((char *) copy, (char *) buf, sizeof copy);
continue;
}
}
if (dns_reply == NETDB_SUCCESS && strcmp( buf, "&" ) != 0)
{
/* Must include DMARC version */
if (strncasecmp((char *)buf, "v=DMARC1", sizeof buf) == 0)
{
return DMARC_PARSE_OKAY;
}
}
/*
** Retry with a * literal.
*/
strlcpy((char *) copy, (char *) "*", sizeof copy);
strlcat((char *) copy, "._report._dmarc.", sizeof copy);
strlcat((char *) copy, (char *) domain, sizeof copy);
for (i = 0; i < DNS_MAX_RETRIES && ret == NULL; i++)
{
ret = (u_char *) dmarc_dns_get_record((char *) copy, &dns_reply,
(char *) buf, sizeof buf);
if (ret != 0 || dns_reply == HOST_NOT_FOUND)
break;
/* requery if didn't resolve CNAME */
if (ret == NULL && *buf != '\0')
{
strlcpy((char *) copy, (char *) buf, sizeof copy);
continue;
}
}
if (dns_reply == NETDB_SUCCESS && strcmp( buf, "&" ) != 0)
{
/* Must include DMARC version */
if (strncasecmp((char *)buf, "v=DMARC1", sizeof buf) == 0)
{
return DMARC_PARSE_OKAY;
}
else
{
return DMARC_DNS_ERROR_NO_RECORD;
}
}
switch (dns_reply)
{
case HOST_NOT_FOUND:
case NO_DATA:
case NO_RECOVERY:
return DMARC_DNS_ERROR_NO_RECORD;
case TRY_AGAIN:
case NETDB_INTERNAL:
return DMARC_DNS_ERROR_TMPERR;
default:
return DMARC_DNS_ERROR_NO_RECORD;
}
}
/**************************************************************************
** OPENDMARC_POLICY_QUERY_DMARC -- Look up the _dmarc record for the
** specified domain. If not found
** try the organizational domain.
** Parameters:
** pctx -- The context to uptdate
** domain -- The domain for which to lookup the DMARC record
** Returns:
** DMARC_PARSE_OKAY -- On success, and fills pctx
** DMARC_PARSE_ERROR_NULL_CTX -- If pctx was NULL
** DMARC_PARSE_ERROR_EMPTY -- if domain NULL or zero
** DMARC_PARSE_ERROR_NO_DOMAIN -- No domain in domain
** DMARC_DNS_ERROR_NXDOMAIN -- No domain found in DNS
** DMARC_DNS_ERROR_TMPERR -- No domain, try again later
** DMARC_DNS_ERROR_NO_RECORD -- No DMARC record found.
** Side Effects:
** Performs one or more DNS lookups
** Allocates memory.
** Note:
** Does not check to insure that the domain is a
** syntactically valid domain.
** Looks up domain first. If that fails, finds the tld and
** looks up topmost domain under tld. If this later is found
** updates pctx->organizational_domain with the result.
** Warning:
** If no TLD file has been loaded, will silenty not do that
** fallback lookup.
**
***************************************************************************/
OPENDMARC_STATUS_T
opendmarc_policy_query_dmarc(DMARC_POLICY_T *pctx, u_char *domain)
{
u_char buf[BUFSIZ];
u_char copy[256];
u_char tld[256];
u_char * bp = NULL;
int dns_reply = 0;
int tld_reply = 0;
int loop_count = DNS_MAX_RETRIES;
if (pctx == NULL)
return DMARC_PARSE_ERROR_NULL_CTX;
if (domain == NULL || strlen(domain) == 0)
{
if (pctx->from_domain != NULL)
domain = pctx->from_domain;
else
return DMARC_PARSE_ERROR_EMPTY;
}
(void) strlcpy(copy, "_dmarc.", sizeof copy);
(void) strlcat(copy, domain, sizeof copy);
query_again:
(void) memset(buf, '\0', sizeof buf);
bp = dmarc_dns_get_record(copy, &dns_reply, buf, sizeof buf);
if (bp != NULL)
{
if (dns_reply != HOST_NOT_FOUND)
goto got_record;
}
/*
* Was a CNAME was found that the resolver did
* not follow on its own?
*/
if (bp == NULL && *buf != '\0')
{
(void) strlcpy(copy, buf, sizeof copy);
if (--loop_count != 0)
goto query_again;
}
(void) memset(tld, '\0', sizeof tld);
tld_reply = opendmarc_get_tld(domain, tld, sizeof tld);
if (tld_reply != 0)
goto dns_failed;
if (strlen(tld) > 0)
{
pctx->organizational_domain = strdup(tld);
loop_count = DNS_MAX_RETRIES;
(void) strlcpy(copy, "_dmarc.", sizeof copy);
(void) strlcat(copy, tld, sizeof copy);
query_again2:
(void) memset(buf, '\0', sizeof buf);
bp = dmarc_dns_get_record(copy, &dns_reply, buf, sizeof buf);
if (bp != NULL)
goto got_record;
/*
* Was a CNAME was found that the resolver did
* not follow on its own?
*/
if (bp == NULL && *buf != '\0')
{
(void) strlcpy(copy, buf, sizeof copy);
if (--loop_count != 0)
goto query_again2;
}
}
dns_failed:
switch (dns_reply)
{
case HOST_NOT_FOUND:
case NO_DATA:
case NO_RECOVERY:
return DMARC_DNS_ERROR_NO_RECORD;
case TRY_AGAIN:
case NETDB_INTERNAL:
return DMARC_DNS_ERROR_TMPERR;
default:
return DMARC_DNS_ERROR_NO_RECORD;
}
got_record:
return opendmarc_policy_parse_dmarc(pctx, domain, buf);
}
/**************************************************************************
** OPENDMARC_GET_POLICY_TO_ENFORCE -- What to do with this message. i.e. allow
** possible delivery, quarantine, or reject.
** Parameters:
** pctx -- A Policy context
** Returns:
** DMARC_PARSE_ERROR_NULL_CTX -- pctx == NULL
** DMARC_POLICY_ABSENT -- No DMARC record found
** DMARC_FROM_DOMAIN_ABSENT -- No From: domain
** DMARC_POLICY_NONE -- Accept if other policy allows
** DMARC_POLICY_REJECT -- Policy advises to reject the message
** DMARC_POLICY_QUARANTINE -- Policy advises to quarantine the message
** DMARC_POLICY_PASS -- Policy advises to accept the message
** Side Effects:
** Checks for domain alignment.
***************************************************************************/
OPENDMARC_STATUS_T
opendmarc_get_policy_to_enforce(DMARC_POLICY_T *pctx)
{
if (pctx == NULL)
return DMARC_PARSE_ERROR_NULL_CTX;
if (pctx->p == DMARC_RECORD_P_UNSPECIFIED)
return DMARC_POLICY_ABSENT;
if (pctx->from_domain == NULL)
return DMARC_FROM_DOMAIN_ABSENT;
pctx->dkim_alignment = DMARC_POLICY_DKIM_ALIGNMENT_FAIL;
pctx->spf_alignment = DMARC_POLICY_SPF_ALIGNMENT_FAIL;
/* check for DKIM alignment */
if (pctx->dkim_domain != NULL && pctx->dkim_outcome == DMARC_POLICY_DKIM_OUTCOME_PASS)
{
if (opendmarc_policy_check_alignment(pctx->from_domain, pctx->dkim_domain, pctx->adkim) == 0)
pctx->dkim_alignment = DMARC_POLICY_DKIM_ALIGNMENT_PASS;
}
/* check for SPF alignment */
if (pctx->spf_domain != NULL && pctx->spf_outcome == DMARC_POLICY_SPF_OUTCOME_PASS)
{
if (opendmarc_policy_check_alignment(pctx->from_domain, pctx->spf_domain, pctx->aspf) == 0)
pctx->spf_alignment = DMARC_POLICY_SPF_ALIGNMENT_PASS;
}
/*
* If dkim passes and dkim aligns OR spf passes and spf aligns
* Accept the message.
*/
if (pctx->spf_alignment == DMARC_POLICY_SPF_ALIGNMENT_PASS ||
pctx->dkim_alignment == DMARC_POLICY_DKIM_ALIGNMENT_PASS)
return DMARC_POLICY_PASS;
if (pctx->organizational_domain != NULL)
{
switch (pctx->sp)
{
case DMARC_RECORD_P_REJECT:
return DMARC_POLICY_REJECT;
case DMARC_RECORD_P_QUARANTINE:
return DMARC_POLICY_QUARANTINE;
case DMARC_RECORD_P_NONE:
return DMARC_POLICY_NONE;
}
}
switch (pctx->p)
{
case DMARC_RECORD_P_REJECT:
return DMARC_POLICY_REJECT;
case DMARC_RECORD_P_QUARANTINE:
return DMARC_POLICY_QUARANTINE;
case DMARC_RECORD_P_NONE:
return DMARC_POLICY_NONE;
default:
/* XXX -- shouldn't be possible */
return DMARC_POLICY_PASS;
}
}
/*******************************************************************************
** OPENDMARC_PARSE_DMARC -- Parse a DMARC record
**
** Parameters:
** pctx -- A Policy context
** domain -- The domain looked up to get this DMARC record
** record -- The DMARC record to parse
** Returns:
** DMARC_PARSE_ERROR_EMPTY -- if any argument is NULL
** DMARC_PARSE_ERROR_BAD_VERSION -- if v= was bad
** DMARC_PARSE_ERROR_BAD_VALUE -- if value following = was bad
** DMARC_PARSE_ERROR_NO_REQUIRED_P -- if p= was absent
** DMARC_PARSE_OKAY -- On Success
** Side Effects:
** Allocates memory.
*********************************************************************************/
OPENDMARC_STATUS_T
opendmarc_policy_parse_dmarc(DMARC_POLICY_T *pctx, u_char *domain, u_char *record)
{
u_char *cp, *eqp, *ep, *sp, *vp;
u_char copy[BUFSIZ];
u_char cbuf[512];
u_char vbuf[512];
if (pctx == NULL || domain == NULL || record == NULL || strlen((char *)record) == 0)
{
return DMARC_PARSE_ERROR_EMPTY;
}
/*
* Set the defaults to detect missing required items.
*/
pctx->pct = -1;
pctx->ri = -1;
(void) memset((char *)copy, '\0', sizeof copy);
(void) strlcpy((char *)copy, (char *)record, sizeof copy);
ep = copy + strlen((char *)copy);
for (cp = copy; cp != NULL && cp <= ep; )
{
sp = (u_char *)strchr(cp, ';');
if (sp != NULL)
*sp++ = '\0';
eqp = (u_char *)strchr((char *)cp, '=');
if (eqp == NULL)
{
cp = sp;
continue;
}
*eqp = '\0';
vp = eqp + 1;
cp = opendmarc_util_cleanup(cp, cbuf, sizeof cbuf);
if (cp == NULL || strlen((char *)cp) == 0)
{
cp = sp;
continue;
}
vp = opendmarc_util_cleanup(vp, vbuf, sizeof vbuf);
if (vp == NULL || strlen((char *)vp) == 0)
{
cp = sp;
continue;
}
/*
* cp nwo points to the token, and
* vp now points to the token's value
* both with all surronding whitepace removed.
*/
if (strcasecmp((char *)cp, "v") == 0)
{
/*
* Yes, this is required to be first, but why
* reject it if it is not first?
*/
if (strcasecmp((char *)vp, "DMARC1") != 0)
{
return DMARC_PARSE_ERROR_BAD_VERSION;
}
}
else if (strcasecmp((char *)cp, "p") == 0)
{
/*
* Be generous. Accept, for example, "p=r, p=rej, or any
* left match of "reject".
*/
if (strncasecmp((char *)vp, "reject", strlen((char *)vp)) == 0)
pctx->p = DMARC_RECORD_P_REJECT;
else if (strncasecmp((char *)vp, "none", strlen((char *)vp)) == 0)
pctx->p = DMARC_RECORD_P_NONE;
else if (strncasecmp((char *)vp, "quarantine", strlen((char *)vp)) == 0)
pctx->p = DMARC_RECORD_P_QUARANTINE;
else
{
/* A totaly unknown value */
return DMARC_PARSE_ERROR_BAD_VALUE;
}
}
else if (strcasecmp((char *)cp, "sp") == 0)
{
/*
* Be generous. Accept, for example, "sp=r, p=rej, or any
* left match of "reject".
*/
if (strncasecmp((char *)vp, "reject", strlen((char *)vp)) == 0)
pctx->sp = DMARC_RECORD_P_REJECT;
else if (strncasecmp((char *)vp, "none", strlen((char *)vp)) == 0)
pctx->sp = DMARC_RECORD_P_NONE;
else if (strncasecmp((char *)vp, "quarantine", strlen((char *)vp)) == 0)
pctx->sp = DMARC_RECORD_P_QUARANTINE;
else
{
/* A totaly unknown value */
return DMARC_PARSE_ERROR_BAD_VALUE;
}
}
else if (strcasecmp((char *)cp, "adkim") == 0)
{
/*
* Be generous. Accept, for example, "adkim=s, adkim=strict or any
* left match of "strict".
*/
if (strncasecmp((char *)vp, "strict", strlen((char *)vp)) == 0)
pctx->adkim = DMARC_RECORD_A_STRICT;
else if (strncasecmp((char *)vp, "relaxed", strlen((char *)vp)) == 0)
pctx->adkim = DMARC_RECORD_A_RELAXED;
else
{
/* A totaly unknown value */
return DMARC_PARSE_ERROR_BAD_VALUE;
}
}
else if (strcasecmp((char *)cp, "aspf") == 0)
{
/*
* Be generous. Accept, for example, "aspf=s, aspf=strict or any
* left match of "strict".
*/
if (strncasecmp((char *)vp, "strict", strlen((char *)vp)) == 0)
pctx->aspf = DMARC_RECORD_A_STRICT;
else if (strncasecmp((char *)vp, "relaxed", strlen((char *)vp)) == 0)
pctx->aspf = DMARC_RECORD_A_RELAXED;
else
{
/* A totaly unknown value */
return DMARC_PARSE_ERROR_BAD_VALUE;
}
}
else if (strcasecmp((char *)cp, "pct") == 0)
{
errno = 0;
pctx->pct = strtoul(vp, NULL, 10);
if (pctx->pct < 0 || pctx->pct > 100)
{
return DMARC_PARSE_ERROR_BAD_VALUE;
}
if (errno == EINVAL || errno == ERANGE)
{
return DMARC_PARSE_ERROR_BAD_VALUE;
}
}
else if (strcasecmp((char *)cp, "ri") == 0)
{
char *xp;
for (xp = vp; *xp != '\0'; ++xp)
{
if (! isdigit((int)*xp))
return DMARC_PARSE_ERROR_BAD_VALUE;
}
errno = 0;
pctx->ri = strtoul(vp, NULL, 10);
if (errno == EINVAL || errno == ERANGE)
{
return DMARC_PARSE_ERROR_BAD_VALUE;
}
}
else if (strcasecmp((char *)cp, "rf") == 0)
{
char *xp, *yp;
/*
* The list may be a comma delimilted list of choices.
*/
for (xp = vp; *xp != '\0'; )
{
u_char xbuf[32];
yp = strchr(xp, ',');
if (yp != NULL)
*yp = '\0';
xp = opendmarc_util_cleanup(xp, xbuf, sizeof xbuf);
if (xp != NULL && strlen((char *)xp) > 0)
{
/*
* Be generous. Accept, for example, "rf=a, aspf=afrf or any
* left match of "afrf".
*/
if (strncasecmp((char *)xp, "afrf", strlen((char *)xp)) == 0)
pctx->rf |= DMARC_RECORD_RF_AFRF;
else if (strncasecmp((char *)xp, "iodef", strlen((char *)xp)) == 0)
pctx->aspf |= DMARC_RECORD_RF_IODEF;
else
{
/* A totaly unknown value */
return DMARC_PARSE_ERROR_BAD_VALUE;
}
}
else
{
return DMARC_PARSE_ERROR_BAD_VALUE;
}
if (yp != NULL)
xp = yp+1;
else
break;
}
}
else if (strcasecmp((char *)cp, "rua") == 0)
{
char *xp, *yp;
/*
* A possibly comma delimited list of URI of where to send reports.
*/
if (pctx->rua_list != NULL)
return DMARC_PARSE_ERROR_BAD_VALUE;
for (xp = vp; *xp != '\0'; )
{
u_char xbuf[256];
yp = strchr(xp, ',');
if (yp != NULL)
*yp = '\0';
xp = opendmarc_util_cleanup(xp, xbuf, sizeof xbuf);
if (xp != NULL && strlen((char *)xp) > 0)
{
pctx->rua_list = opendmarc_util_pushargv(xp, pctx->rua_list,
&(pctx->rua_cnt));
}
else
{
return DMARC_PARSE_ERROR_BAD_VALUE;
}
if (yp != NULL)
xp = yp+1;
else
break;
}
}
else if (strcasecmp((char *)cp, "ruf") == 0)
{
char *xp, *yp;
/*
* A possibly comma delimited list of URI of where to send
* MARF reports.
*/
if (pctx->ruf_list != NULL)
return DMARC_PARSE_ERROR_BAD_VALUE;
for (xp = vp; *xp != '\0'; )
{
u_char xbuf[256];
yp = strchr(xp, ',');
if (yp != NULL)
*yp = '\0';
xp = opendmarc_util_cleanup(xp, xbuf, sizeof xbuf);
if (xp != NULL && strlen((char *)xp) > 0)
{
pctx->ruf_list = opendmarc_util_pushargv(xp, pctx->ruf_list,
&(pctx->ruf_cnt));
}
else
{
return DMARC_PARSE_ERROR_BAD_VALUE;
}
if (yp != NULL)
xp = yp+1;
else
break;
}
}
else if (strcasecmp((char *)cp, "fo") == 0)
{
char *xp, *yp;
/*
* A possibly colon delimited list of on character settings.
*/
for (xp = vp; *xp != '\0'; )
{
u_char xbuf[256];
yp = strchr(xp, ':');
if (yp != NULL)
*yp = '\0';
xp = opendmarc_util_cleanup(xp, xbuf, sizeof xbuf);
if (xp != NULL && strlen((char *)xp) > 0)
{
switch ((int)*xp)
{
case '0':
pctx->fo |= DMARC_RECORD_FO_0;
break;
case '1':
pctx->fo |= DMARC_RECORD_FO_1;
break;
case 'd':
case 'D':
pctx->fo |= DMARC_RECORD_FO_D;
break;
case 's':
case 'S':
pctx->fo |= DMARC_RECORD_FO_S;
break;
default:
return DMARC_PARSE_ERROR_BAD_VALUE;
}
}
else
{
return DMARC_PARSE_ERROR_BAD_VALUE;
}
if (yp != NULL)
xp = yp+1;
else
break;
}
}
cp = sp;
}
if (pctx->p == DMARC_RECORD_P_UNSPECIFIED)
{
return DMARC_PARSE_ERROR_NO_REQUIRED_P;
}
/*
* Set defaults for unspecifed tokens.
*/
if (pctx->adkim == DMARC_RECORD_A_UNSPECIFIED)
pctx->adkim = DMARC_RECORD_A_RELAXED;
if (pctx->aspf == DMARC_RECORD_A_UNSPECIFIED)
pctx->aspf = DMARC_RECORD_A_RELAXED;
if (pctx->pct < 0)
pctx->pct = 100;
if (pctx->rf == DMARC_RECORD_RF_UNSPECIFIED)
pctx->rf = DMARC_RECORD_RF_AFRF;
if (pctx->ri == -1)
pctx->ri = 86400;
if (pctx->fo == DMARC_RECORD_FO_UNSPECIFIED)
pctx->fo = DMARC_RECORD_FO_0;
if (pctx->from_domain == NULL)
pctx->from_domain = strdup(domain);
return DMARC_PARSE_OKAY;
}
/**************************************************************************
** OPENDMARC_POLICY_STORE_DMARC -- The application looked up the dmarc record
** and hands it to us here.
***************************************************************************/
OPENDMARC_STATUS_T
opendmarc_policy_store_dmarc(DMARC_POLICY_T *pctx, u_char *dmarc_record, u_char *domain, u_char *organizationaldomain)
{
OPENDMARC_STATUS_T status;
if (pctx == NULL)
return DMARC_PARSE_ERROR_NULL_CTX;
if (dmarc_record == NULL)
return DMARC_PARSE_ERROR_EMPTY;
if (domain == NULL)
return DMARC_PARSE_ERROR_NO_DOMAIN;
status = opendmarc_policy_parse_dmarc(pctx, domain , dmarc_record);
if (status != DMARC_PARSE_OKAY)
return status;
if (pctx->from_domain != NULL)
(void) free(pctx->from_domain);
pctx->from_domain = strdup(domain);
if (organizationaldomain != NULL)
{
if (pctx->organizational_domain != NULL)
(void) free(pctx->organizational_domain);
pctx->organizational_domain = (u_char *)strdup(organizationaldomain);
}
return DMARC_PARSE_OKAY;
}
/**************************************************************************
** DMARC LOOKUP HOOKS
***************************************************************************/
OPENDMARC_STATUS_T
opendmarc_policy_fetch_pct(DMARC_POLICY_T *pctx, int *pctp)
{
if (pctx == NULL)
{
return DMARC_PARSE_ERROR_NULL_CTX;
}
if (pctp == NULL)
{
return DMARC_PARSE_ERROR_EMPTY;
}
*pctp = pctx->pct;
return DMARC_PARSE_OKAY;
}
OPENDMARC_STATUS_T
opendmarc_policy_fetch_adkim(DMARC_POLICY_T *pctx, int *adkim)
{
if (pctx == NULL)
{
return DMARC_PARSE_ERROR_NULL_CTX;
}
if (adkim == NULL)
{
return DMARC_PARSE_ERROR_EMPTY;
}
*adkim = pctx->adkim;
return DMARC_PARSE_OKAY;
}
OPENDMARC_STATUS_T
opendmarc_policy_fetch_aspf(DMARC_POLICY_T *pctx, int *aspf)
{
if (pctx == NULL)
{
return DMARC_PARSE_ERROR_NULL_CTX;
}
if (aspf == NULL)
{
return DMARC_PARSE_ERROR_EMPTY;
}
*aspf = pctx->aspf;
return DMARC_PARSE_OKAY;
}
OPENDMARC_STATUS_T
opendmarc_policy_fetch_p(DMARC_POLICY_T *pctx, int *p)
{
if (pctx == NULL)
{
return DMARC_PARSE_ERROR_NULL_CTX;
}
if (p == NULL)
{
return DMARC_PARSE_ERROR_EMPTY;
}
*p = pctx->p;
return DMARC_PARSE_OKAY;
}
OPENDMARC_STATUS_T
opendmarc_policy_fetch_sp(DMARC_POLICY_T *pctx, int *sp)
{
if (pctx == NULL)
{
return DMARC_PARSE_ERROR_NULL_CTX;
}
if (sp == NULL)
{
return DMARC_PARSE_ERROR_EMPTY;
}
*sp = pctx->sp;
return DMARC_PARSE_OKAY;
}
u_char **
opendmarc_policy_fetch_rua(DMARC_POLICY_T *pctx, u_char *list_buf, size_t size_of_buf, int constant)
{
u_char *sp, *ep, *rp;
int i;
int ret;
if (pctx == NULL)
{
return NULL;
}
if (list_buf != NULL && size_of_buf > 0)
{
(void) memset(list_buf, '\0', size_of_buf);
sp = list_buf;
ep = list_buf + size_of_buf;
for (i = 0; i < pctx->rua_cnt; i++)
{
ret = opendmarc_policy_query_dmarc_xdomain(pctx, pctx->rua_list[i]);
if (ret != DMARC_PARSE_OKAY)
continue;
for (rp = (pctx->rua_list)[i]; *rp != '\0'; ++rp)
{
*sp++ = *rp;
if (sp >= (ep - 2))
break;
}
if (sp >= (ep - 2))
break;
if (i != (pctx->rua_cnt -1))
*sp++ = ',';
if (sp >= (ep - 2))
break;
}
}
if (constant != 0)
return pctx->rua_list;
return opendmarc_util_dupe_argv(pctx->rua_list);
}
OPENDMARC_STATUS_T
opendmarc_policy_fetch_alignment(DMARC_POLICY_T *pctx, int *dkim_alignment, int *spf_alignment)
{
if (pctx == NULL)
{
return DMARC_PARSE_ERROR_NULL_CTX;
}
if (dkim_alignment != NULL)
{
*dkim_alignment = pctx->dkim_alignment;
}
if (spf_alignment != NULL)
{
*spf_alignment = pctx->spf_alignment;
}
return DMARC_PARSE_OKAY;
}
u_char **
opendmarc_policy_fetch_ruf(DMARC_POLICY_T *pctx, u_char *list_buf, size_t size_of_buf, int constant)
{
u_char *sp, *ep, *rp;
int i;
int ret;
if (pctx == NULL)
{
return NULL;
}
if (list_buf != NULL || size_of_buf > 0)
{
(void) memset(list_buf, '\0', size_of_buf);
sp = list_buf;
ep = list_buf + size_of_buf;
for (i = 0; i < pctx->ruf_cnt; i++)
{
ret = opendmarc_policy_query_dmarc_xdomain(pctx, pctx->ruf_list[i]);
if (ret != DMARC_PARSE_OKAY)
continue;
for (rp = (pctx->ruf_list)[i]; *rp != '\0'; ++rp)
{
*sp++ = *rp;
if (sp >= (ep - 2))
break;
}
if (sp >= (ep - 2))
break;
if (i != (pctx->ruf_cnt -1))
*sp++ = ',';
if (sp >= (ep - 2))
break;
}
}
if (constant != 0)
return pctx->ruf_list;
return opendmarc_util_dupe_argv(pctx->ruf_list);
}
OPENDMARC_STATUS_T
opendmarc_policy_fetch_fo(DMARC_POLICY_T *pctx, int *fo)
{
if (pctx == NULL)
return DMARC_PARSE_ERROR_NULL_CTX;
if (fo == NULL)
return DMARC_PARSE_ERROR_EMPTY;
if (pctx->ruf_list == NULL)
*fo = DMARC_RECORD_FO_UNSPECIFIED;
else
*fo = pctx->fo;
return DMARC_PARSE_OKAY;
}
OPENDMARC_STATUS_T
opendmarc_policy_fetch_rf(DMARC_POLICY_T *pctx, int *rf)
{
if (pctx == NULL)
return DMARC_PARSE_ERROR_NULL_CTX;
if (rf == NULL)
return DMARC_PARSE_ERROR_EMPTY;
if (pctx->ruf_list == NULL)
*rf = DMARC_RECORD_RF_UNSPECIFIED;
else
*rf = pctx->rf;
return DMARC_PARSE_OKAY;
}
/**************************************************************************************************
** OPENDMARC_POLICY_FETCH_UTILIZED_DOMAIN -- Return domain used to get the dmarc record
** Either the From: domain or the organizational domain
** Arguments
** pctx -- Address of a policy context
** buf -- Where to scribble result
** buflen -- Size of buffer
** Returns
** DMARC_PARSE_OKAY -- On success
** DMARC_PARSE_ERROR_NULL_CTX -- If context NULL
** DMARC_PARSE_ERROR_EMPTY -- If buf null or buflen 0 sized
** DMARC_PARSE_ERROR_NO_DOMAIN -- If neigher address is available
**/
OPENDMARC_STATUS_T
opendmarc_policy_fetch_utilized_domain(DMARC_POLICY_T *pctx, u_char *buf, size_t buflen)
{
u_char *which = NULL;
if (pctx == NULL)
return DMARC_PARSE_ERROR_NULL_CTX;
if (buf == NULL || buflen == 0)
return DMARC_PARSE_ERROR_EMPTY;
if (pctx->organizational_domain != NULL)
which = pctx->organizational_domain;
else if (pctx->from_domain != NULL)
which = pctx->from_domain;
if (which == NULL)
return DMARC_PARSE_ERROR_NO_DOMAIN;
# if HAVE_STRLCPY
(void) strlcpy((char *)buf, (char *)which, buflen);
# else
(void) strncpy((char *)buf, (char *)which, buflen);
# endif
return DMARC_PARSE_OKAY;
}
/**************************************************************************************************
** OPENDMARC_POLICY_FETCH_FROM_DOMAIN -- Return domain parsed from stored From: header
** Arguments
** pctx -- Address of a policy context
** buf -- Where to scribble result
** buflen -- Size of buffer
** Returns
** DMARC_PARSE_OKAY -- On success
** DMARC_PARSE_ERROR_NULL_CTX -- If context NULL
** DMARC_PARSE_ERROR_EMPTY -- If buf null or buflen 0 sized
** DMARC_PARSE_ERROR_NO_DOMAIN -- If neigher address is available
**/
OPENDMARC_STATUS_T
opendmarc_policy_fetch_from_domain(DMARC_POLICY_T *pctx, u_char *buf, size_t buflen)
{
u_char *which = NULL;
if (pctx == NULL)
return DMARC_PARSE_ERROR_NULL_CTX;
if (buf == NULL || buflen == 0)
return DMARC_PARSE_ERROR_EMPTY;
if (pctx->from_domain != NULL)
which = pctx->from_domain;
if (which == NULL)
return DMARC_PARSE_ERROR_NO_DOMAIN;
# if HAVE_STRLCPY
(void) strlcpy((char *)buf, (char *)which, buflen);
# else
(void) strncpy((char *)buf, (char *)which, buflen);
# endif
return DMARC_PARSE_OKAY;
}
/**************************************************************************
** OPENDMARC_GET_POLICY_TOKEN_USED -- Which policy was actually used
**
** Parameters:
** pctx -- A Policy context
** Returns:
** DMARC_PARSE_ERROR_NULL_CTX -- pctx == NULL
** DMARC_USED_POLICY_IS_P -- Domain policy is used
** DMARC_USED_POLICY_IS_SP -- Sub-domain policy is used
***************************************************************************/
OPENDMARC_STATUS_T
opendmarc_get_policy_token_used(DMARC_POLICY_T *pctx)
{
if (pctx == NULL)
return DMARC_PARSE_ERROR_NULL_CTX;
if (pctx->organizational_domain != NULL &&
pctx->sp != DMARC_RECORD_P_UNSPECIFIED)
return DMARC_USED_POLICY_IS_SP;
else
return DMARC_USED_POLICY_IS_P;
}
/******************************************************************************
** OPENDMARC_POLICY_LIBRARY_DNS_HOOK -- Internal hook for dmarc_dns_get_record
*******************************************************************************/
void
opendmarc_policy_library_dns_hook(int *nscountp,
struct sockaddr_in *nsaddr_list)
{
int i;
if (nscountp == NULL || nsaddr_list == NULL)
return;
if (Opendmarc_Libp == NULL)
return;
if (Opendmarc_Libp->nscount == 0 || Opendmarc_Libp->nscount >= MAXNS)
return;
for (i = 0; i < Opendmarc_Libp->nscount; i++)
{
nsaddr_list[i] = Opendmarc_Libp->nsaddr_list[i];
}
*nscountp = i;
return;
}
/**************************************************************************
** OPENDMARC_POLICY_STATUS_TO_STR -- Convert the integer return
** of type OPENDMARC_STATUS_T into
** a human readable string.
** Parameters:
** status -- The status for which to return a string
** Returns:
** NULL -- On error
** const char * -- On success
***************************************************************************/
const char *
opendmarc_policy_status_to_str(OPENDMARC_STATUS_T status)
{
char *msg = "Undefine Value";
switch (status)
{
case DMARC_PARSE_OKAY:
msg = "Success. No Errors";
break;
case DMARC_PARSE_ERROR_EMPTY:
msg = "Function called with nothing to parse";
break;
case DMARC_PARSE_ERROR_NULL_CTX:
msg ="Function called with NULL Context";
break;
case DMARC_PARSE_ERROR_BAD_VERSION:
msg = "Found DMARC record contained a bad v= value";
break;
case DMARC_PARSE_ERROR_BAD_VALUE:
msg = "Found DMARC record contained a bad token value";
break;
case DMARC_PARSE_ERROR_NO_REQUIRED_P:
msg = "Found DMARC record lacked a required p= entry";
break;
case DMARC_PARSE_ERROR_NO_DOMAIN:
msg = "Function found the domain empty, e.g. \"<>\"";
break;
case DMARC_PARSE_ERROR_NO_ALLOC:
msg = "Memory allocation error";
break;
case DMARC_PARSE_ERROR_BAD_SPF_MACRO:
msg = "Attempt to store an illegal value";
break;
case DMARC_DNS_ERROR_NO_RECORD:
msg = "Looked up domain lacked a DMARC record";
break;
case DMARC_DNS_ERROR_NXDOMAIN:
msg = "Looked up domain did not exist";
break;
case DMARC_DNS_ERROR_TMPERR:
msg = "DNS lookup of domain tempfailed";
break;
case DMARC_TLD_ERROR_UNKNOWN:
msg = "Attempt to load an unknown TLD file type";
break;
case DMARC_FROM_DOMAIN_ABSENT:
msg = "No From: domain was supplied";
break;
case DMARC_POLICY_ABSENT:
msg = "Policy up to you. No DMARC record found";
break;
case DMARC_POLICY_PASS:
msg = "Policy OK so accept message";
break;
case DMARC_POLICY_REJECT:
msg = "Policy says to reject message";
break;
case DMARC_POLICY_QUARANTINE:
msg = "Policy says to quarantine message";
break;
case DMARC_POLICY_NONE:
msg = "Policy says to monitor and report";
break;
}
return msg;
}
/*******************************************************************************
** OPENDMARC_POLICY_TO_BUF -- Dump the DMARC_POLICY_T to a user supplied buffer
** Arguments:
** pctx A pointer to a filled in DMARC_POLICY_T sttucture
** buf A char * buffer
** buflen The size of the char * buffer
** Returns:
** 0 On success
** >0 On failure, and fills errno with the error
** Side Effects:
** Blindly overwrites buffer.
*******************************************************************************/
int
opendmarc_policy_to_buf(DMARC_POLICY_T *pctx, char *buf, size_t buflen)
{
char nbuf[32];
int i;
if (pctx == NULL || buf == NULL || buflen == 0)
return errno = EINVAL;
(void) memset(buf, '\0', buflen);
if (strlcat(buf, "IP_ADDR=", buflen) >= buflen) return E2BIG;
if (pctx->ip_addr != NULL)
if (strlcat(buf, pctx->ip_addr, buflen) >= buflen) return E2BIG;
if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
if (strlcat(buf, "IP_TYPE=", buflen) >= buflen) return E2BIG;
if (pctx->ip_addr != NULL)
{
if (pctx->ip_type == DMARC_POLICY_IP_TYPE_IPV4)
{
if (strlcat(buf, "IPv4", buflen) >= buflen) return E2BIG;
}
else if (pctx->ip_type == DMARC_POLICY_IP_TYPE_IPV6)
{
if (strlcat(buf, "IPv6", buflen) >= buflen) return E2BIG;
}
}
if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
if (strlcat(buf, "SPF_DOMAIN=", buflen) >= buflen) return E2BIG;
if (pctx->spf_domain != NULL)
if (strlcat(buf, pctx->spf_domain, buflen) >= buflen) return E2BIG;
if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
if (strlcat(buf, "SPF_ORIGIN=", buflen) >= buflen) return E2BIG;
if (pctx->spf_origin != 0)
{
if (pctx->spf_origin == DMARC_POLICY_SPF_ORIGIN_MAILFROM)
{
if (strlcat(buf, "MAILFROM", buflen) >= buflen) return E2BIG;
}
else if (pctx->spf_origin == DMARC_POLICY_SPF_ORIGIN_HELO)
{
if (strlcat(buf, "HELO", buflen) >= buflen) return E2BIG;
}
}
if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
if (strlcat(buf, "SPF_OUTCOME=", buflen) >= buflen) return E2BIG;
switch (pctx->spf_outcome)
{
default:
case DMARC_POLICY_DKIM_OUTCOME_NONE:
if (strlcat(buf, "NONE", buflen) >= buflen) return E2BIG;
break;
case DMARC_POLICY_DKIM_OUTCOME_PASS:
if (strlcat(buf, "PASS", buflen) >= buflen) return E2BIG;
break;
case DMARC_POLICY_DKIM_OUTCOME_FAIL:
if (strlcat(buf, "FAIL", buflen) >= buflen) return E2BIG;
break;
case DMARC_POLICY_DKIM_OUTCOME_TMPFAIL:
if (strlcat(buf, "TMPFAIL", buflen) >= buflen) return E2BIG;
break;
}
if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
if (strlcat(buf, "SPF_HUMAN_OUTCOME=", buflen) >= buflen) return E2BIG;
if (pctx->spf_human_outcome != NULL)
if (strlcat(buf, pctx->spf_human_outcome, buflen) >= buflen) return E2BIG;
if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
if (strlcat(buf, "DKIM_FINAL=", buflen) >= buflen) return E2BIG;
switch (pctx->dkim_final)
{
case TRUE:
if (strlcat(buf, "TRUE", buflen) >= buflen) return E2BIG;
break;
default:
case FALSE:
if (strlcat(buf, "FALSE", buflen) >= buflen) return E2BIG;
break;
}
if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
if (strlcat(buf, "DKIM_DOMAIN=", buflen) >= buflen) return E2BIG;
if (pctx->dkim_domain != NULL)
if (strlcat(buf, pctx->dkim_domain, buflen) >= buflen) return E2BIG;
if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
if (strlcat(buf, "DKIM_SELECTOR=", buflen) >= buflen) return E2BIG;
if (pctx->dkim_selector != NULL)
if (strlcat(buf, pctx->dkim_selector, buflen) >= buflen) return E2BIG;
if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
if (strlcat(buf, "DKIM_OUTOME=", buflen) >= buflen) return E2BIG;
switch (pctx->dkim_outcome)
{
default:
case DMARC_POLICY_DKIM_OUTCOME_NONE:
if (strlcat(buf, "NONE", buflen) >= buflen) return E2BIG;
break;
case DMARC_POLICY_DKIM_OUTCOME_PASS:
if (strlcat(buf, "PASS", buflen) >= buflen) return E2BIG;
break;
case DMARC_POLICY_DKIM_OUTCOME_FAIL:
if (strlcat(buf, "FAIL", buflen) >= buflen) return E2BIG;
break;
case DMARC_POLICY_DKIM_OUTCOME_TMPFAIL:
if (strlcat(buf, "TMPFAIL", buflen) >= buflen) return E2BIG;
break;
}
if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
if (strlcat(buf, "DKIM_HUMAN_OUTCOME=", buflen) >= buflen) return E2BIG;
if (pctx->dkim_human_outcome != NULL)
if (strlcat(buf, pctx->dkim_human_outcome, buflen) >= buflen) return E2BIG;
if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
if (strlcat(buf, "DKIM_ALIGNMENT=", buflen) >= buflen) return E2BIG;
switch (pctx->dkim_alignment)
{
case DMARC_POLICY_DKIM_ALIGNMENT_PASS:
if (strlcat(buf, "PASS", buflen) >= buflen) return E2BIG;
break;
default:
case DMARC_POLICY_DKIM_ALIGNMENT_FAIL:
if (strlcat(buf, "FAIL", buflen) >= buflen) return E2BIG;
break;
}
if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
if (strlcat(buf, "SPF_ALIGNMENT=", buflen) >= buflen) return E2BIG;
switch (pctx->spf_alignment)
{
case DMARC_POLICY_SPF_ALIGNMENT_PASS:
if (strlcat(buf, "PASS", buflen) >= buflen) return E2BIG;
break;
default:
case DMARC_POLICY_SPF_ALIGNMENT_FAIL:
if (strlcat(buf, "FAIL", buflen) >= buflen) return E2BIG;
break;
}
if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
if (strlcat(buf, "H_ERRNO=", buflen) >= buflen) return E2BIG;
switch (pctx->h_error)
{
case HOST_NOT_FOUND:
if (strlcat(buf, "HOST_NOT_FOUND", buflen) >= buflen) return E2BIG;
break;
case TRY_AGAIN:
if (strlcat(buf, "TRY_AGAIN", buflen) >= buflen) return E2BIG;
break;
case NO_RECOVERY:
if (strlcat(buf, "NO_RECOVERY", buflen) >= buflen) return E2BIG;
break;
case NO_DATA:
if (strlcat(buf, "NO_DATA", buflen) >= buflen) return E2BIG;
break;
case NETDB_INTERNAL:
if (strlcat(buf, "NETDB_INTERNAL", buflen) >= buflen) return E2BIG;
break;
}
if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
if (strlcat(buf, "ADKIM=", buflen) >= buflen) return E2BIG;
switch (pctx->adkim)
{
case DMARC_RECORD_A_UNSPECIFIED:
if (strlcat(buf, "UNSPECIFIED", buflen) >= buflen) return E2BIG;
break;
case DMARC_RECORD_A_STRICT:
if (strlcat(buf, "STRICT", buflen) >= buflen) return E2BIG;
break;
case DMARC_RECORD_A_RELAXED:
if (strlcat(buf, "RELAXED", buflen) >= buflen) return E2BIG;
break;
}
if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
if (strlcat(buf, "ASPF=", buflen) >= buflen) return E2BIG;
switch (pctx->aspf)
{
case DMARC_RECORD_A_UNSPECIFIED:
if (strlcat(buf, "UNSPECIFIED", buflen) >= buflen) return E2BIG;
break;
case DMARC_RECORD_A_STRICT:
if (strlcat(buf, "STRICT", buflen) >= buflen) return E2BIG;
break;
case DMARC_RECORD_A_RELAXED:
if (strlcat(buf, "RELAXED", buflen) >= buflen) return E2BIG;
break;
}
if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
if (strlcat(buf, "P=", buflen) >= buflen) return E2BIG;
switch (pctx->p)
{
case DMARC_RECORD_A_UNSPECIFIED:
if (strlcat(buf, "UNSPECIFIED", buflen) >= buflen) return E2BIG;
break;
case DMARC_RECORD_P_NONE:
if (strlcat(buf, "NONE", buflen) >= buflen) return E2BIG;
break;
case DMARC_RECORD_P_QUARANTINE:
if (strlcat(buf, "QUARANTINE", buflen) >= buflen) return E2BIG;
break;
case DMARC_RECORD_P_REJECT:
if (strlcat(buf, "REJECT", buflen) >= buflen) return E2BIG;
break;
}
if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
if (strlcat(buf, "SP=", buflen) >= buflen) return E2BIG;
switch (pctx->sp)
{
case DMARC_RECORD_A_UNSPECIFIED:
if (strlcat(buf, "UNSPECIFIED", buflen) >= buflen) return E2BIG;
break;
case DMARC_RECORD_P_NONE:
if (strlcat(buf, "NONE", buflen) >= buflen) return E2BIG;
break;
case DMARC_RECORD_P_QUARANTINE:
if (strlcat(buf, "QUARANTINE", buflen) >= buflen) return E2BIG;
break;
case DMARC_RECORD_P_REJECT:
if (strlcat(buf, "REJECT", buflen) >= buflen) return E2BIG;
break;
}
if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
if (strlcat(buf, "PCT=", buflen) >= buflen) return E2BIG;
(void) snprintf(nbuf, sizeof nbuf, "%d", pctx->pct);
if (strlcat(buf, nbuf, buflen) >= buflen) return E2BIG;
if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
if (strlcat(buf, "RF=", buflen) >= buflen) return E2BIG;
if (pctx->rf == 0)
{
if (strlcat(buf, "UNSPECIFIED", buflen) >= buflen) return E2BIG;
}
if ((pctx->rf&DMARC_RECORD_RF_AFRF) != 0)
{
if (strlcat(buf, "AFRF", buflen) >= buflen) return E2BIG;
}
if ((pctx->rf&DMARC_RECORD_RF_IODEF) != 0 &&
(pctx->rf&DMARC_RECORD_RF_AFRF) != 0)
{
if (strlcat(buf, ",", buflen) >= buflen) return E2BIG;
}
if ((pctx->rf&DMARC_RECORD_RF_IODEF) != 0)
{
if (strlcat(buf, "IODEF", buflen) >= buflen) return E2BIG;
}
if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
if (strlcat(buf, "RI=", buflen) >= buflen) return E2BIG;
(void) snprintf(nbuf, sizeof nbuf, "%d", pctx->ri);
if (strlcat(buf, nbuf, buflen) >= buflen) return E2BIG;
if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
if (strlcat(buf, "RUA=", buflen) >= buflen) return E2BIG;
for (i = 0; i < pctx->rua_cnt; ++i)
{
if (i > 0)
{
if (strlcat(buf, ",", buflen) >= buflen) return E2BIG;
}
if (strlcat(buf, (pctx->rua_list)[i], buflen) >= buflen) return E2BIG;
}
if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
if (strlcat(buf, "RUF=", buflen) >= buflen) return E2BIG;
for (i = 0; i < pctx->ruf_cnt; ++i)
{
if (i > 0)
{
if (strlcat(buf, ",", buflen) >= buflen) return E2BIG;
}
if (strlcat(buf, (pctx->ruf_list)[i], buflen) >= buflen) return E2BIG;
}
if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
if (strlcat(buf, "FO=", buflen) >= buflen) return E2BIG;
if (pctx->ruf_list == NULL || pctx->fo == DMARC_RECORD_FO_UNSPECIFIED)
{
if (strlcat(buf, "UNSPECIFIED", buflen) >= buflen) return E2BIG;
}
if ((pctx->fo&DMARC_RECORD_FO_0) != 0)
{
if (strlcat(buf, "0:", buflen) >= buflen) return E2BIG;
}
if ((pctx->fo&DMARC_RECORD_FO_1) != 0)
{
if (strlcat(buf, "1:", buflen) >= buflen) return E2BIG;
}
if ((pctx->fo&DMARC_RECORD_FO_D) != 0)
{
if (strlcat(buf, "d:", buflen) >= buflen) return E2BIG;
}
if ((pctx->fo&DMARC_RECORD_FO_S) != 0)
{
if (strlcat(buf, "s:", buflen) >= buflen) return E2BIG;
}
if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
return 0;
}