1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
6 Copyright (c) The Exim Maintainers 2015 - 2022
7 Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 - 2014
11 /* Code for calling spf checks via libspf-alt. Called from acl.c. */
16 /* must be kept in numeric order */
17 static spf_result_id spf_result_id_list[] = {
25 { US"temperror", 6 }, /* RFC 4408 defined */
26 { US"permerror", 7 } /* RFC 4408 defined */
29 SPF_server_t *spf_server = NULL;
30 SPF_request_t *spf_request = NULL;
31 SPF_response_t *spf_response = NULL;
32 SPF_response_t *spf_response_2mx = NULL;
34 SPF_dns_rr_t * spf_nxdomain = NULL;
38 spf_lib_version_report(gstring * g)
42 SPF_get_lib_version(&maj, &min, &patch);
43 g = string_fmt_append(g, "Library version: spf2: Compile: %d.%d.%d\n",
44 SPF_LIB_VERSION_MAJOR, SPF_LIB_VERSION_MINOR, SPF_LIB_VERSION_PATCH);
45 g = string_fmt_append(g, " Runtime: %d.%d.%d\n",
53 SPF_dns_exim_lookup(SPF_dns_server_t *spf_dns_server,
54 const char *domain, ns_type rr_type, int should_cache)
56 dns_answer * dnsa = store_get_dns_answer();
62 .domain = CS domain, /* query information */
66 .rr_buf_len = 0, /* answer information */
67 .rr_buf_num = 0, /* no free of s */
70 .hook = NULL, /* misc information */
71 .source = spf_dns_server
75 DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", domain);
77 /* Shortcircuit SPF RR lookups by returning NO_DATA. They were obsoleted by
78 RFC 6686/7208 years ago. see bug #1294 */
82 HDEBUG(D_host_lookup) debug_printf("faking NO_DATA for SPF RR(99) lookup\n");
84 SPF_dns_rr_dup(&spfrr, &srr);
85 store_free_dns_answer(dnsa);
89 switch (dns_rc = dns_lookup(dnsa, US domain, rr_type, NULL))
91 case DNS_SUCCEED: srr.herrno = NETDB_SUCCESS; break;
92 case DNS_AGAIN: srr.herrno = TRY_AGAIN; break;
93 case DNS_NOMATCH: srr.herrno = HOST_NOT_FOUND; break;
94 case DNS_NODATA: srr.herrno = NO_DATA; break;
96 default: srr.herrno = NO_RECOVERY; break;
99 for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
100 rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
101 if (rr->type == rr_type) found++;
105 SPF_dns_rr_dup(&spfrr, &srr);
106 store_free_dns_answer(dnsa);
110 srr.rr = store_malloc(sizeof(SPF_dns_rr_data_t) * found);
113 for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
114 rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
115 if (rr->type == rr_type)
117 const uschar * s = rr->data;
123 if (rr->size < 2) continue;
124 s += 2; /* skip the MX precedence field */
127 uschar * buf = store_malloc(256);
128 (void)dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, s,
129 (DN_EXPAND_ARG4_TYPE)buf, 256);
139 if (rr->size < 1+6) continue; /* min for version str */
140 if (strncmpic(rr->data+1, US SPF_VER_STR, 6) != 0)
142 HDEBUG(D_host_lookup) debug_printf("not an spf record: %.*s\n",
147 /* require 1 byte for the chunk_len */
148 for (int off = 0; off < rr->size - 1; off += chunk_len)
150 if ( !(chunk_len = s[off++])
151 || rr->size < off + chunk_len /* ignore bogus size chunks */
153 g = string_catn(g, s+off, chunk_len);
157 gstring_release_unused(g);
158 s = string_copy_malloc(string_from_gstring(g));
159 DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", s);
167 uschar * buf = store_malloc(dnsa->answerlen + 1);
168 s = memcpy(buf, s, dnsa->answerlen + 1);
172 srr.rr[found++] = (void *) s;
175 /* Did we filter out all TXT RRs? Return NO_DATA instead of SUCCESS with
176 empty ANSWER section. */
178 if (!(srr.num_rr = found))
179 srr.herrno = NO_DATA;
181 /* spfrr->rr must have been malloc()d for this */
182 SPF_dns_rr_dup(&spfrr, &srr);
183 store_free_dns_answer(dnsa);
190 SPF_dns_exim_new(int debug)
192 SPF_dns_server_t * spf_dns_server = store_malloc(sizeof(SPF_dns_server_t));
194 DEBUG(D_receive) debug_printf("SPF_dns_exim_new\n");
196 memset(spf_dns_server, 0, sizeof(SPF_dns_server_t));
197 spf_dns_server->destroy = NULL;
198 spf_dns_server->lookup = SPF_dns_exim_lookup;
199 spf_dns_server->get_spf = NULL;
200 spf_dns_server->get_exp = NULL;
201 spf_dns_server->add_cache = NULL;
202 spf_dns_server->layer_below = NULL;
203 spf_dns_server->name = "exim";
204 spf_dns_server->debug = debug;
206 /* XXX This might have to return NO_DATA sometimes. */
208 spf_nxdomain = SPF_dns_rr_new_init(spf_dns_server,
209 "", ns_t_any, 24 * 60 * 60, HOST_NOT_FOUND);
212 free(spf_dns_server);
216 return spf_dns_server;
222 /* Construct the SPF library stack.
223 Return: Boolean success.
229 SPF_dns_server_t * dc;
233 DEBUG(D_receive) debug = 1;
235 /* We insert our own DNS access layer rather than letting the spf library
236 do it, so that our dns access path is used for debug tracing and for the
239 if (!(dc = SPF_dns_exim_new(debug)))
241 DEBUG(D_receive) debug_printf("spf: SPF_dns_exim_new() failed\n");
244 if (!(dc = SPF_dns_cache_new(dc, NULL, debug, 8)))
246 DEBUG(D_receive) debug_printf("spf: SPF_dns_cache_new() failed\n");
249 if (!(spf_server = SPF_server_new_dns(dc, debug)))
251 DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n");
254 /* Override the outdated explanation URL.
255 See https://www.mail-archive.com/mailop@mailop.org/msg08019.html
256 Used to work as "Please%_see%_http://www.open-spf.org/Why?id=%{S}&ip=%{C}&receiver=%{R}",
257 but is broken now (May 18th, 2020) */
258 if (!(s = expand_string(spf_smtp_comment_template)))
259 log_write(0, LOG_MAIN|LOG_PANIC_DIE, "expansion of spf_smtp_comment_template failed");
261 SPF_server_set_explanation(spf_server, CCS s, &spf_response);
262 if (SPF_response_errcode(spf_response) != SPF_E_SUCCESS)
263 log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", SPF_strerror(SPF_response_errcode(spf_response)));
269 /* Set up a context that can be re-used for several
270 messages on the same SMTP connection (that come from the
271 same host with the same HELO string).
273 Return: Boolean success
277 spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr)
280 debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);
282 if (!spf_server && !spf_init()) return FALSE;
284 if (SPF_server_set_rec_dom(spf_server, CS primary_hostname))
286 DEBUG(D_receive) debug_printf("spf: SPF_server_set_rec_dom(\"%s\") failed.\n",
292 spf_request = SPF_request_new(spf_server);
294 if ( SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr)
295 && SPF_request_set_ipv6_str(spf_request, CS spf_remote_addr)
299 debug_printf("spf: SPF_request_set_ipv4_str() and "
300 "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr);
306 if (SPF_request_set_helo_dom(spf_request, CS spf_helo_domain))
308 DEBUG(D_receive) debug_printf("spf: SPF_set_helo_dom(\"%s\") failed.\n",
320 spf_response_debug(SPF_response_t * spf_response)
322 if (SPF_response_messages(spf_response) == 0)
323 debug_printf(" (no errors)\n");
324 else for (int i = 0; i < SPF_response_messages(spf_response); i++)
326 SPF_error_t * err = SPF_response_message(spf_response, i);
327 debug_printf( "%s_msg = (%d) %s\n",
328 (SPF_error_errorp(err) ? "warn" : "err"),
330 SPF_error_message(err));
335 /* spf_process adds the envelope sender address to the existing
336 context (if any), retrieves the result, sets up expansion
337 strings and evaluates the condition outcome.
342 spf_process(const uschar **listptr, uschar *spf_envelope_sender, int action)
345 const uschar *list = *listptr;
346 uschar *spf_result_id;
347 int rc = SPF_RESULT_PERMERROR;
349 DEBUG(D_receive) debug_printf("spf_process\n");
351 if (!(spf_server && spf_request))
352 /* no global context, assume temp error and skip to evaluation */
353 rc = SPF_RESULT_PERMERROR;
355 else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender))
356 /* Invalid sender address. This should be a real rare occurrence */
357 rc = SPF_RESULT_PERMERROR;
362 if (action == SPF_PROCESS_FALLBACK)
364 SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
365 spf_result_guessed = TRUE;
368 SPF_request_query_mailfrom(spf_request, &spf_response);
370 /* set up expansion items */
371 spf_header_comment = US SPF_response_get_header_comment(spf_response);
372 spf_received = US SPF_response_get_received_spf(spf_response);
373 spf_result = US SPF_strresult(SPF_response_result(spf_response));
374 spf_smtp_comment = US SPF_response_get_smtp_comment(spf_response);
376 rc = SPF_response_result(spf_response);
378 DEBUG(D_acl) spf_response_debug(spf_response);
381 /* We got a result. Now see if we should return OK or FAIL for it */
382 DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
384 if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
385 return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
387 while ((spf_result_id = string_nextinlist(&list, &sep, NULL, 0)))
391 if ((negate = spf_result_id[0] == '!'))
394 result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name) == 0;
395 if (negate != result) return OK;
405 authres_spf(gstring * g)
408 if (!spf_result) return g;
410 g = string_append(g, 2, US";\n\tspf=", spf_result);
411 if (spf_result_guessed)
412 g = string_cat(g, US" (best guess record for domain)");
414 s = expand_string(US"$sender_address_domain");
416 return string_append(g, 2, US" smtp.mailfrom=", s);
418 s = sender_helo_name;
420 ? string_append(g, 2, US" smtp.helo=", s)
421 : string_cat(g, US" smtp.mailfrom=<>");