X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/4a8ce2d88e1b24b456199ec5f2a799a6cb22ae91..071c51f70266916a7be153ce67c0045beb58b841:/src/src/dmarc.c diff --git a/src/src/dmarc.c b/src/src/dmarc.c index d0a827bb8..ca1c29bbb 100644 --- a/src/src/dmarc.c +++ b/src/src/dmarc.c @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ /* Experimental DMARC support. - Copyright (c) Todd Lyons 2012, 2013 + Copyright (c) Todd Lyons 2012 - 2014 License: GPL */ /* Portions Copyright (c) 2012, 2013, The Trusted Domain Project; @@ -31,13 +31,25 @@ uschar *dmarc_pass_fail = US"skipped"; extern pdkim_signature *dkim_signatures; header_line *from_header = NULL; extern SPF_response_t *spf_response; -int dmarc_spf_result = 0; +int dmarc_spf_ares_result = 0; uschar *spf_sender_domain = NULL; uschar *spf_human_readable = NULL; u_char *header_from_sender = NULL; int history_file_status = DMARC_HIST_OK; uschar *dkim_history_buffer= NULL; +typedef struct dmarc_exim_p { + uschar *name; + int value; +} dmarc_exim_p; + +static dmarc_exim_p dmarc_policy_description[] = { + { US"", DMARC_RECORD_P_UNSPECIFIED }, + { US"none", DMARC_RECORD_P_NONE }, + { US"quarantine", DMARC_RECORD_P_QUARANTINE }, + { US"reject", DMARC_RECORD_P_REJECT }, + { NULL, 0 } +}; /* Accept an error_block struct, initialize if empty, parse to the * end, and append the two strings passed to it. Used for adding * variable amounts of value:pair data to the forensic emails. */ @@ -81,6 +93,8 @@ int dmarc_init() dmarc_abort = FALSE; dmarc_pass_fail = US"skipped"; dmarc_used_domain = US""; + dmarc_ar_header = NULL; + dmarc_has_been_checked = FALSE; header_from_sender = NULL; spf_sender_domain = NULL; spf_human_readable = NULL; @@ -98,7 +112,9 @@ int dmarc_init() opendmarc_policy_status_to_str(libdm_status)); dmarc_abort = TRUE; } - if (opendmarc_tld_read_file(tld_file, NULL, NULL, NULL)) + if (dmarc_tld_file == NULL) + dmarc_abort = TRUE; + else if (opendmarc_tld_read_file(tld_file, NULL, NULL, NULL)) { log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to load tld list %s: %d", tld_file, errno); @@ -141,7 +157,9 @@ int dmarc_store_data(header_line *hdr) { strings and evaluates the condition outcome. */ int dmarc_process() { - int sr, origin; /* used in SPF section */ + int sr, origin; /* used in SPF section */ + int dmarc_spf_result = 0; /* stores spf into dmarc conn ctx */ + int tmp_ans, c; pdkim_signature *sig = NULL; BOOL has_dmarc_record = TRUE; u_char **ruf; /* forensic report addressees, if called for */ @@ -163,26 +181,31 @@ int dmarc_process() { dmarc_abort = TRUE; else { - /* I strongly encourage anybody who can make this better to contact me directly! - * Is this an insane way to extract the email address from the From: header? - * it's sure a horrid layer-crossing.... - * I'm not denying that :-/ - * there may well be no better though - */ - header_from_sender = expand_string( - string_sprintf("${domain:${extract{1}{:}{${addresses:%s}}}}", - from_header->text) ); + uschar * errormsg; + int dummy, domain; + uschar * p; + uschar saveend; + + parse_allow_group = TRUE; + p = parse_find_address_end(from_header->text, FALSE); + saveend = *p; *p = '\0'; + if ((header_from_sender = parse_extract_address(from_header->text, &errormsg, + &dummy, &dummy, &domain, FALSE))) + header_from_sender += domain; + *p = saveend; + /* The opendmarc library extracts the domain from the email address, but * only try to store it if it's not empty. Otherwise, skip out of DMARC. */ - if (strcmp( CCS header_from_sender, "") == 0) + if (!header_from_sender || (strcmp( CCS header_from_sender, "") == 0)) dmarc_abort = TRUE; - libdm_status = (dmarc_abort == TRUE) ? - DMARC_PARSE_OKAY : - opendmarc_policy_store_from_domain(dmarc_pctx, header_from_sender); + libdm_status = dmarc_abort ? + DMARC_PARSE_OKAY : + opendmarc_policy_store_from_domain(dmarc_pctx, header_from_sender); if (libdm_status != DMARC_PARSE_OKAY) { - log_write(0, LOG_MAIN|LOG_PANIC, "failure to store header From: in DMARC: %s, header was '%s'", - opendmarc_policy_status_to_str(libdm_status), from_header->text); + log_write(0, LOG_MAIN|LOG_PANIC, + "failure to store header From: in DMARC: %s, header was '%s'", + opendmarc_policy_status_to_str(libdm_status), from_header->text); dmarc_abort = TRUE; } } @@ -196,34 +219,17 @@ int dmarc_process() { if ( spf_response == NULL ) { /* No spf data means null envelope sender so generate a domain name - * from the sender_host_name || sender_helo_name */ + * from the sender_helo_name */ if (spf_sender_domain == NULL) { - spf_sender_domain = (sender_host_name == NULL) ? sender_helo_name : sender_host_name; - uschar *subdomain = spf_sender_domain; - int count = 0; - while (subdomain && *subdomain != '.') - { - subdomain++; - count++; - } - /* If parsed characters in temp var "subdomain" and is pointing to - * a period now, get rid of the period and use that. Otherwise - * will use whatever was first set in spf_sender_domain. Goal is to - * generate a sane answer, not necessarily the right/best answer b/c - * at this point with a null sender, it's a bounce message, making - * the spf domain be subjective. */ - if (count > 0 && *subdomain == '.') - { - subdomain++; - spf_sender_domain = subdomain; - } + spf_sender_domain = sender_helo_name; log_write(0, LOG_MAIN, "DMARC using synthesized SPF sender domain = %s\n", spf_sender_domain); DEBUG(D_receive) debug_printf("DMARC using synthesized SPF sender domain = %s\n", spf_sender_domain); } dmarc_spf_result = DMARC_POLICY_SPF_OUTCOME_NONE; + dmarc_spf_ares_result = ARES_RESULT_UNKNOWN; origin = DMARC_POLICY_SPF_ORIGIN_HELO; spf_human_readable = US""; } @@ -235,6 +241,14 @@ int dmarc_process() { (sr == SPF_RESULT_FAIL) ? DMARC_POLICY_SPF_OUTCOME_FAIL : (sr == SPF_RESULT_SOFTFAIL) ? DMARC_POLICY_SPF_OUTCOME_TMPFAIL : DMARC_POLICY_SPF_OUTCOME_NONE; + dmarc_spf_ares_result = (sr == SPF_RESULT_NEUTRAL) ? ARES_RESULT_NEUTRAL : + (sr == SPF_RESULT_PASS) ? ARES_RESULT_PASS : + (sr == SPF_RESULT_FAIL) ? ARES_RESULT_FAIL : + (sr == SPF_RESULT_SOFTFAIL) ? ARES_RESULT_SOFTFAIL : + (sr == SPF_RESULT_NONE) ? ARES_RESULT_NONE : + (sr == SPF_RESULT_TEMPERROR) ? ARES_RESULT_TEMPERROR : + (sr == SPF_RESULT_PERMERROR) ? ARES_RESULT_PERMERROR : + ARES_RESULT_UNKNOWN; origin = DMARC_POLICY_SPF_ORIGIN_MAILFROM; spf_human_readable = (uschar *)spf_response->header_comment; DEBUG(D_receive) @@ -257,22 +271,32 @@ int dmarc_process() { dkim_history_buffer = US""; while (sig != NULL) { - int dkim_result, vs; - vs = sig->verify_status; + int dkim_result, dkim_ares_result, vs, ves; + vs = sig->verify_status; + ves = sig->verify_ext_status; dkim_result = ( vs == PDKIM_VERIFY_PASS ) ? DMARC_POLICY_DKIM_OUTCOME_PASS : - ( vs == PDKIM_VERIFY_FAIL ) ? DMARC_POLICY_DKIM_OUTCOME_FAIL : - ( vs == PDKIM_VERIFY_INVALID ) ? DMARC_POLICY_DKIM_OUTCOME_TMPFAIL : + ( vs == PDKIM_VERIFY_FAIL ) ? DMARC_POLICY_DKIM_OUTCOME_FAIL : + ( vs == PDKIM_VERIFY_INVALID ) ? DMARC_POLICY_DKIM_OUTCOME_TMPFAIL : DMARC_POLICY_DKIM_OUTCOME_NONE; libdm_status = opendmarc_policy_store_dkim(dmarc_pctx, (uschar *)sig->domain, - dkim_result, US""); + dkim_result, US""); DEBUG(D_receive) debug_printf("DMARC adding DKIM sender domain = %s\n", sig->domain); if (libdm_status != DMARC_PARSE_OKAY) log_write(0, LOG_MAIN|LOG_PANIC, "failure to store dkim (%s) for DMARC: %s", - sig->domain, opendmarc_policy_status_to_str(libdm_status)); - + sig->domain, opendmarc_policy_status_to_str(libdm_status)); + + dkim_ares_result = ( vs == PDKIM_VERIFY_PASS ) ? ARES_RESULT_PASS : + ( vs == PDKIM_VERIFY_FAIL ) ? ARES_RESULT_FAIL : + ( vs == PDKIM_VERIFY_NONE ) ? ARES_RESULT_NONE : + ( vs == PDKIM_VERIFY_INVALID ) ? + ( ves == PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE ? ARES_RESULT_PERMERROR : + ves == PDKIM_VERIFY_INVALID_BUFFER_SIZE ? ARES_RESULT_PERMERROR : + ves == PDKIM_VERIFY_INVALID_PUBKEY_PARSING ? ARES_RESULT_PERMERROR : + ARES_RESULT_UNKNOWN ) : + ARES_RESULT_UNKNOWN; dkim_history_buffer = string_sprintf("%sdkim %s %d\n", dkim_history_buffer, - sig->domain, dkim_result); + sig->domain, dkim_ares_result); sig = sig->next; } libdm_status = opendmarc_policy_query_dmarc(dmarc_pctx, US""); @@ -301,11 +325,21 @@ int dmarc_process() { has_dmarc_record = FALSE; break; } + + /* Store the policy string in an expandable variable. */ + libdm_status = opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans); + for (c=0; dmarc_policy_description[c].name != NULL; c++) { + if (tmp_ans == dmarc_policy_description[c].value) { + dmarc_domain_policy = string_sprintf("%s",dmarc_policy_description[c].name); + break; + } + } + /* Can't use exim's string manipulation functions so allocate memory * for libopendmarc using its max hostname length definition. */ uschar *dmarc_domain = (uschar *)calloc(DMARC_MAXHOSTNAMELEN, sizeof(uschar)); libdm_status = opendmarc_policy_fetch_utilized_domain(dmarc_pctx, dmarc_domain, - DMARC_MAXHOSTNAMELEN-1); + DMARC_MAXHOSTNAMELEN-1); dmarc_used_domain = string_copy(dmarc_domain); free(dmarc_domain); if (libdm_status != DMARC_PARSE_OKAY) @@ -319,7 +353,7 @@ int dmarc_process() { { case DMARC_POLICY_ABSENT: /* No DMARC record found */ dmarc_status = US"norecord"; - dmarc_pass_fail = US"temperror"; + dmarc_pass_fail = US"none"; dmarc_status_text = US"No DMARC record"; action = DMARC_RESULT_ACCEPT; break; @@ -397,7 +431,7 @@ int dmarc_process() { int dmarc_write_history_file() { - static int history_file_fd; + int history_file_fd; ssize_t written_len; int tmp_ans; u_char **rua; /* aggregate report addressees */ @@ -424,8 +458,8 @@ int dmarc_write_history_file() expand_string(US"$sender_address_domain")); if (spf_response != NULL) - history_buffer = string_sprintf("%sspf %d\n", history_buffer, dmarc_spf_result); - // history_buffer = string_sprintf("%sspf -1\n", history_buffer); + history_buffer = string_sprintf("%sspf %d\n", history_buffer, dmarc_spf_ares_result); + /* history_buffer = string_sprintf("%sspf -1\n", history_buffer); */ history_buffer = string_sprintf("%s%s", history_buffer, dkim_history_buffer); history_buffer = string_sprintf("%spdomain %s\n", history_buffer, dmarc_used_domain); @@ -603,5 +637,3 @@ uschar *dmarc_auth_results_header(header_line *from_header, uschar *hostname) #endif /* EXPERIMENTAL_SPF */ #endif /* EXPERIMENTAL_DMARC */ - -// vim:sw=2 expandtab