1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
6 Copyright (c) The Exim Maintainers 2015 - 2024
7 Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 - 2014
9 SPDX-License-Identifier: GPL-2.0-or-later
12 /* Code for calling spf checks via libspf-alt. Called from acl.c. */
17 /* must be kept in numeric order */
18 static spf_result_id spf_result_id_list[] = {
26 { US"temperror", 6 }, /* RFC 4408 defined */
27 { US"permerror", 7 } /* RFC 4408 defined */
30 SPF_server_t *spf_server = NULL;
31 SPF_request_t *spf_request = NULL;
32 SPF_response_t *spf_response = NULL;
33 SPF_response_t *spf_response_2mx = NULL;
35 SPF_dns_rr_t * spf_nxdomain = NULL;
37 uschar * spf_guess = US"v=spf1 a/24 mx/24 ptr ?all";
38 uschar * spf_header_comment = NULL;
39 uschar * spf_received = NULL;
40 uschar * spf_result = NULL;
41 uschar * spf_smtp_comment = NULL;
42 uschar * spf_smtp_comment_template
43 /* Used to be: "Please%_see%_http://www.open-spf.org/Why?id=%{S}&ip=%{C}&receiver=%{R}" */
44 = US"Please%_see%_http://www.open-spf.org/Why";
45 BOOL spf_result_guessed = FALSE;
51 spf_lib_version_report(gstring * g)
55 SPF_get_lib_version(&maj, &min, &patch);
56 g = string_fmt_append(g, "Library version: spf2: Compile: %d.%d.%d\n",
57 SPF_LIB_VERSION_MAJOR, SPF_LIB_VERSION_MINOR, SPF_LIB_VERSION_PATCH);
58 g = string_fmt_append(g, " Runtime: %d.%d.%d\n",
66 SPF_dns_exim_lookup(SPF_dns_server_t *spf_dns_server,
67 const char *domain, ns_type rr_type, int should_cache)
69 dns_answer * dnsa = store_get_dns_answer();
75 .domain = CS domain, /* query information */
79 .rr_buf_len = 0, /* answer information */
80 .rr_buf_num = 0, /* no free of s */
83 .hook = NULL, /* misc information */
84 .source = spf_dns_server
87 DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", domain);
89 /* Shortcircuit SPF RR lookups by returning NO_DATA. They were obsoleted by
90 RFC 6686/7208 years ago. see bug #1294 */
94 HDEBUG(D_host_lookup) debug_printf("faking NO_DATA for SPF RR(99) lookup\n");
96 SPF_dns_rr_dup(&spfrr, &srr);
97 store_free_dns_answer(dnsa);
101 switch (dns_lookup(dnsa, US domain, rr_type, NULL))
103 case DNS_AGAIN: srr.herrno = TRY_AGAIN; break;
104 case DNS_NOMATCH: srr.herrno = HOST_NOT_FOUND; break;
105 case DNS_NODATA: srr.herrno = NO_DATA; break;
107 default: srr.herrno = NO_RECOVERY; break;
109 srr.herrno = NETDB_SUCCESS;
110 for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
111 rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
112 /* Need to alloc space for all records, so no early-out */
113 if (rr->type == rr_type) found++;
119 SPF_dns_rr_dup(&spfrr, &srr);
120 store_free_dns_answer(dnsa);
124 srr.rr = store_malloc(sizeof(SPF_dns_rr_data_t) * found);
127 for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
128 rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
129 if (rr->type == rr_type)
131 const uschar * s = rr->data;
137 if (rr->size < 2) continue;
138 s += 2; /* skip the MX precedence field */
141 uschar * buf = store_malloc(256);
142 (void)dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, s,
143 (DN_EXPAND_ARG4_TYPE)buf, 256);
153 if (rr->size < 1+6) continue; /* min for version str */
154 if (strncmpic(rr->data+1, US SPF_VER_STR, 6) != 0)
156 HDEBUG(D_host_lookup) debug_printf("not an spf record: %.*s\n",
161 /* require 1 byte for the chunk_len */
162 for (int off = 0; off < rr->size - 1; off += chunk_len)
164 if ( !(chunk_len = s[off++])
165 || rr->size < off + chunk_len /* ignore bogus size chunks */
167 g = string_catn(g, s+off, chunk_len);
171 gstring_release_unused(g);
172 s = string_copy_malloc(string_from_gstring(g));
173 DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", s);
181 uschar * buf = store_malloc(dnsa->answerlen + 1);
182 s = memcpy(buf, s, dnsa->answerlen + 1);
186 srr.rr[found++] = (void *) s;
189 /* Did we filter out all TXT RRs? Return NO_DATA instead of SUCCESS with
190 empty ANSWER section. */
192 if (!(srr.num_rr = found))
193 srr.herrno = NO_DATA;
195 /* spfrr->rr must have been malloc()d for this */
196 SPF_dns_rr_dup(&spfrr, &srr);
197 store_free_dns_answer(dnsa);
203 static SPF_dns_server_t *
204 SPF_dns_exim_new(int debug)
206 SPF_dns_server_t * spf_dns_server = store_malloc(sizeof(SPF_dns_server_t));
208 /* DEBUG(D_receive) debug_printf("SPF_dns_exim_new\n"); */
210 memset(spf_dns_server, 0, sizeof(SPF_dns_server_t));
211 spf_dns_server->destroy = NULL;
212 spf_dns_server->lookup = SPF_dns_exim_lookup;
213 spf_dns_server->get_spf = NULL;
214 spf_dns_server->get_exp = NULL;
215 spf_dns_server->add_cache = NULL;
216 spf_dns_server->layer_below = NULL;
217 spf_dns_server->name = "exim";
218 spf_dns_server->debug = debug;
220 /* XXX This might have to return NO_DATA sometimes. */
222 spf_nxdomain = SPF_dns_rr_new_init(spf_dns_server,
223 "", ns_t_any, 24 * 60 * 60, HOST_NOT_FOUND);
226 store_free(spf_dns_server);
230 return spf_dns_server;
236 /* Construct the SPF library stack.
237 Return: Boolean success.
241 spf_init(void * dummy_ctx)
243 SPF_dns_server_t * dc;
247 DEBUG(D_receive) debug = 1;
249 /* We insert our own DNS access layer rather than letting the spf library
250 do it, so that our dns access path is used for debug tracing and for the
253 if (!(dc = SPF_dns_exim_new(debug)))
255 DEBUG(D_receive) debug_printf("spf: SPF_dns_exim_new() failed\n");
258 if (!(dc = SPF_dns_cache_new(dc, NULL, debug, 8)))
260 DEBUG(D_receive) debug_printf("spf: SPF_dns_cache_new() failed\n");
263 if (!(spf_server = SPF_server_new_dns(dc, debug)))
265 DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n");
269 /* Override the outdated explanation URL.
270 See https://www.mail-archive.com/mailop@mailop.org/msg08019.html
271 Used to work as "Please%_see%_http://www.open-spf.org/Why?id=%{S}&ip=%{C}&receiver=%{R}",
272 but is broken now (May 18th, 2020) */
274 GET_OPTION("spf_smtp_comment_template");
275 if (!(s = expand_string(spf_smtp_comment_template)))
276 log_write(0, LOG_MAIN|LOG_PANIC_DIE, "expansion of spf_smtp_comment_template failed");
278 SPF_server_set_explanation(spf_server, CCS s, &spf_response);
279 if (SPF_response_errcode(spf_response) != SPF_E_SUCCESS)
280 log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", SPF_strerror(SPF_response_errcode(spf_response)));
286 /* Set up a context that can be re-used for several
287 messages on the same SMTP connection (that come from the
288 same host with the same HELO string).
290 Return: Boolean success
294 spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr)
297 debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);
299 if (!spf_server && !spf_init(NULL)) return FALSE;
301 if (SPF_server_set_rec_dom(spf_server, CS primary_hostname))
303 DEBUG(D_receive) debug_printf("spf: SPF_server_set_rec_dom(\"%s\") failed.\n",
309 spf_request = SPF_request_new(spf_server);
311 if ( SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr)
312 && SPF_request_set_ipv6_str(spf_request, CS spf_remote_addr)
316 debug_printf("spf: SPF_request_set_ipv4_str() and "
317 "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr);
323 if (SPF_request_set_helo_dom(spf_request, CS spf_helo_domain))
325 DEBUG(D_receive) debug_printf("spf: SPF_set_helo_dom(\"%s\") failed.\n",
338 spf_header_comment = spf_received = spf_result = spf_smtp_comment = NULL;
339 spf_result_guessed = FALSE;
344 spf_response_debug(SPF_response_t * spf_response)
346 if (SPF_response_messages(spf_response) == 0)
347 debug_printf(" (no errors)\n");
348 else for (int i = 0; i < SPF_response_messages(spf_response); i++)
350 SPF_error_t * err = SPF_response_message(spf_response, i);
351 debug_printf( "%s_msg = (%d) %s\n",
352 (SPF_error_errorp(err) ? "warn" : "err"),
354 SPF_error_message(err));
359 /* spf_process adds the envelope sender address to the existing
360 context (if any), retrieves the result, sets up expansion
361 strings and evaluates the condition outcome.
366 spf_process(const uschar ** listptr, const uschar * spf_envelope_sender,
370 const uschar *list = *listptr;
371 uschar *spf_result_id;
372 int rc = SPF_RESULT_PERMERROR;
374 DEBUG(D_receive) debug_printf("spf_process\n");
376 if (!(spf_server && spf_request))
377 /* no global context, assume temp error and skip to evaluation */
378 rc = SPF_RESULT_PERMERROR;
380 else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender))
381 /* Invalid sender address. This should be a real rare occurrence */
382 rc = SPF_RESULT_PERMERROR;
387 if (action == SPF_PROCESS_FALLBACK)
389 SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
390 spf_result_guessed = TRUE;
393 SPF_request_query_mailfrom(spf_request, &spf_response);
395 /* set up expansion items */
396 spf_header_comment = US SPF_response_get_header_comment(spf_response);
397 spf_received = US SPF_response_get_received_spf(spf_response);
398 spf_result = US SPF_strresult(SPF_response_result(spf_response));
399 spf_smtp_comment = US SPF_response_get_smtp_comment(spf_response);
401 rc = SPF_response_result(spf_response);
403 DEBUG(D_acl) spf_response_debug(spf_response);
406 /* We got a result. Now see if we should return OK or FAIL for it */
407 DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
409 if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
410 return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
412 while ((spf_result_id = string_nextinlist(&list, &sep, NULL, 0)))
416 if ((negate = spf_result_id[0] == '!'))
419 result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name) == 0;
420 if (negate != result) return OK;
430 authres_spf(gstring * g)
435 int start = 0; /* Compiler quietening */
436 DEBUG(D_acl) start = gstring_length(g);
438 g = string_append(g, 2, US";\n\tspf=", spf_result);
439 if (spf_result_guessed)
440 g = string_cat(g, US" (best guess record for domain)");
442 s = expand_string(US"$sender_address_domain");
444 g = string_append(g, 2, US" smtp.mailfrom=", s);
447 s = sender_helo_name;
449 ? string_append(g, 2, US" smtp.helo=", s)
450 : string_cat(g, US" smtp.mailfrom=<>");
452 DEBUG(D_acl) debug_printf("SPF:\tauthres '%.*s'\n",
453 gstring_length(g) - start - 3, g->s + start + 3);
456 DEBUG(D_acl) debug_printf("SPF:\tno authres\n");
461 /* Ugly; used only by dmarc (peeking into our data!)
462 Exposure of values as $variables might be better? */
464 static SPF_response_t *
465 spf_get_response(void)
470 /******************************************************************************/
474 spf_lookup_open(const uschar * filename, uschar ** errmsg)
476 SPF_dns_server_t * dc;
477 SPF_server_t * spf_server = NULL;
480 DEBUG(D_lookup) debug = 1;
482 if ((dc = SPF_dns_exim_new(debug)))
483 if ((dc = SPF_dns_cache_new(dc, NULL, debug, 8)))
484 spf_server = SPF_server_new_dns(dc, debug);
488 *errmsg = US"SPF_dns_exim_nnew() failed";
491 return (void *) spf_server;
495 spf_lookup_close(void * handle)
497 SPF_server_t * spf_server = handle;
498 if (spf_server) SPF_server_free(spf_server);
502 spf_lookup_find(void * handle, const uschar * filename,
503 const uschar * keystring, int key_len, uschar ** result, uschar ** errmsg,
504 uint * do_cache, const uschar * opts)
506 SPF_server_t *spf_server = handle;
507 SPF_request_t *spf_request;
508 SPF_response_t *spf_response = NULL;
510 if (!(spf_request = SPF_request_new(spf_server)))
512 *errmsg = US"SPF_request_new() failed";
517 switch (string_is_ip_address(filename, NULL))
523 if (!SPF_request_set_ipv4_str(spf_request, CS filename))
525 *errmsg = string_sprintf("invalid IPv4 address '%s'", filename);
530 if (!SPF_request_set_ipv6_str(spf_request, CS filename))
532 *errmsg = string_sprintf("invalid IPv6 address '%s'", filename);
536 *errmsg = string_sprintf("invalid IP address '%s'", filename);
541 if (SPF_request_set_env_from(spf_request, CS keystring))
543 *errmsg = string_sprintf("invalid envelope from address '%s'", keystring);
547 SPF_request_query_mailfrom(spf_request, &spf_response);
548 *result = string_copy(US SPF_strresult(SPF_response_result(spf_response)));
550 DEBUG(D_lookup) spf_response_debug(spf_response);
552 SPF_response_free(spf_response);
553 SPF_request_free(spf_request);
558 /******************************************************************************/
561 static optionlist spf_options[] = {
562 { "spf_guess", opt_stringptr, {&spf_guess} },
563 { "spf_smtp_comment_template",opt_stringptr, {&spf_smtp_comment_template} },
566 static void * spf_functions[] = {
570 spf_get_response, /* ugly; for dmarc */
578 static var_entry spf_variables[] = {
579 { "spf_guess", vtype_stringptr, &spf_guess },
580 { "spf_header_comment", vtype_stringptr, &spf_header_comment },
581 { "spf_received", vtype_stringptr, &spf_received },
582 { "spf_result", vtype_stringptr, &spf_result },
583 { "spf_result_guessed", vtype_bool, &spf_result_guessed },
584 { "spf_smtp_comment", vtype_stringptr, &spf_smtp_comment },
587 misc_module_info spf_module_info =
591 .dyn_magic = MISC_MODULE_MAGIC,
594 .lib_vers_report = spf_lib_version_report,
596 .options = spf_options,
597 .options_count = nelem(spf_options),
599 .functions = spf_functions,
600 .functions_count = nelem(spf_functions),
602 .variables = spf_variables,
603 .variables_count = nelem(spf_variables),
606 #endif /* almost all the file */