Permalink
Cannot retrieve contributors at this time
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
2050 lines (1880 sloc)
55.5 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /************************************************************************* | |
| ** 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; | |
| } |