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;
39 spf_lib_version_report(gstring * g)
43 SPF_get_lib_version(&maj, &min, &patch);
44 g = string_fmt_append(g, "Library version: spf2: Compile: %d.%d.%d\n",
45 SPF_LIB_VERSION_MAJOR, SPF_LIB_VERSION_MINOR, SPF_LIB_VERSION_PATCH);
46 g = string_fmt_append(g, " Runtime: %d.%d.%d\n",
54 SPF_dns_exim_lookup(SPF_dns_server_t *spf_dns_server,
55 const char *domain, ns_type rr_type, int should_cache)
57 dns_answer * dnsa = store_get_dns_answer();
63 .domain = CS domain, /* query information */
67 .rr_buf_len = 0, /* answer information */
68 .rr_buf_num = 0, /* no free of s */
71 .hook = NULL, /* misc information */
72 .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_lookup(dnsa, US domain, rr_type, NULL))
91 case DNS_AGAIN: srr.herrno = TRY_AGAIN; break;
92 case DNS_NOMATCH: srr.herrno = HOST_NOT_FOUND; break;
93 case DNS_NODATA: srr.herrno = NO_DATA; break;
95 default: srr.herrno = NO_RECOVERY; break;
97 srr.herrno = NETDB_SUCCESS;
98 for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
99 rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
100 /* Need to alloc space for all records, so no early-out */
101 if (rr->type == rr_type) found++;
107 SPF_dns_rr_dup(&spfrr, &srr);
108 store_free_dns_answer(dnsa);
112 srr.rr = store_malloc(sizeof(SPF_dns_rr_data_t) * found);
115 for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
116 rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
117 if (rr->type == rr_type)
119 const uschar * s = rr->data;
125 if (rr->size < 2) continue;
126 s += 2; /* skip the MX precedence field */
129 uschar * buf = store_malloc(256);
130 (void)dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, s,
131 (DN_EXPAND_ARG4_TYPE)buf, 256);
141 if (rr->size < 1+6) continue; /* min for version str */
142 if (strncmpic(rr->data+1, US SPF_VER_STR, 6) != 0)
144 HDEBUG(D_host_lookup) debug_printf("not an spf record: %.*s\n",
149 /* require 1 byte for the chunk_len */
150 for (int off = 0; off < rr->size - 1; off += chunk_len)
152 if ( !(chunk_len = s[off++])
153 || rr->size < off + chunk_len /* ignore bogus size chunks */
155 g = string_catn(g, s+off, chunk_len);
159 gstring_release_unused(g);
160 s = string_copy_malloc(string_from_gstring(g));
161 DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", s);
169 uschar * buf = store_malloc(dnsa->answerlen + 1);
170 s = memcpy(buf, s, dnsa->answerlen + 1);
174 srr.rr[found++] = (void *) s;
177 /* Did we filter out all TXT RRs? Return NO_DATA instead of SUCCESS with
178 empty ANSWER section. */
180 if (!(srr.num_rr = found))
181 srr.herrno = NO_DATA;
183 /* spfrr->rr must have been malloc()d for this */
184 SPF_dns_rr_dup(&spfrr, &srr);
185 store_free_dns_answer(dnsa);
192 SPF_dns_exim_new(int debug)
194 SPF_dns_server_t * spf_dns_server = store_malloc(sizeof(SPF_dns_server_t));
196 DEBUG(D_receive) debug_printf("SPF_dns_exim_new\n");
198 memset(spf_dns_server, 0, sizeof(SPF_dns_server_t));
199 spf_dns_server->destroy = NULL;
200 spf_dns_server->lookup = SPF_dns_exim_lookup;
201 spf_dns_server->get_spf = NULL;
202 spf_dns_server->get_exp = NULL;
203 spf_dns_server->add_cache = NULL;
204 spf_dns_server->layer_below = NULL;
205 spf_dns_server->name = "exim";
206 spf_dns_server->debug = debug;
208 /* XXX This might have to return NO_DATA sometimes. */
210 spf_nxdomain = SPF_dns_rr_new_init(spf_dns_server,
211 "", ns_t_any, 24 * 60 * 60, HOST_NOT_FOUND);
214 store_free(spf_dns_server);
218 return spf_dns_server;
224 /* Construct the SPF library stack.
225 Return: Boolean success.
231 SPF_dns_server_t * dc;
235 DEBUG(D_receive) debug = 1;
237 /* We insert our own DNS access layer rather than letting the spf library
238 do it, so that our dns access path is used for debug tracing and for the
241 if (!(dc = SPF_dns_exim_new(debug)))
243 DEBUG(D_receive) debug_printf("spf: SPF_dns_exim_new() failed\n");
246 if (!(dc = SPF_dns_cache_new(dc, NULL, debug, 8)))
248 DEBUG(D_receive) debug_printf("spf: SPF_dns_cache_new() failed\n");
251 if (!(spf_server = SPF_server_new_dns(dc, debug)))
253 DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n");
257 /* Override the outdated explanation URL.
258 See https://www.mail-archive.com/mailop@mailop.org/msg08019.html
259 Used to work as "Please%_see%_http://www.open-spf.org/Why?id=%{S}&ip=%{C}&receiver=%{R}",
260 but is broken now (May 18th, 2020) */
262 GET_OPTION("spf_smtp_comment_template");
263 if (!(s = expand_string(spf_smtp_comment_template)))
264 log_write(0, LOG_MAIN|LOG_PANIC_DIE, "expansion of spf_smtp_comment_template failed");
266 SPF_server_set_explanation(spf_server, CCS s, &spf_response);
267 if (SPF_response_errcode(spf_response) != SPF_E_SUCCESS)
268 log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", SPF_strerror(SPF_response_errcode(spf_response)));
274 /* Set up a context that can be re-used for several
275 messages on the same SMTP connection (that come from the
276 same host with the same HELO string).
278 Return: Boolean success
282 spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr)
285 debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);
287 if (!spf_server && !spf_init()) return FALSE;
289 if (SPF_server_set_rec_dom(spf_server, CS primary_hostname))
291 DEBUG(D_receive) debug_printf("spf: SPF_server_set_rec_dom(\"%s\") failed.\n",
297 spf_request = SPF_request_new(spf_server);
299 if ( SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr)
300 && SPF_request_set_ipv6_str(spf_request, CS spf_remote_addr)
304 debug_printf("spf: SPF_request_set_ipv4_str() and "
305 "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr);
311 if (SPF_request_set_helo_dom(spf_request, CS spf_helo_domain))
313 DEBUG(D_receive) debug_printf("spf: SPF_set_helo_dom(\"%s\") failed.\n",
325 spf_response_debug(SPF_response_t * spf_response)
327 if (SPF_response_messages(spf_response) == 0)
328 debug_printf(" (no errors)\n");
329 else for (int i = 0; i < SPF_response_messages(spf_response); i++)
331 SPF_error_t * err = SPF_response_message(spf_response, i);
332 debug_printf( "%s_msg = (%d) %s\n",
333 (SPF_error_errorp(err) ? "warn" : "err"),
335 SPF_error_message(err));
340 /* spf_process adds the envelope sender address to the existing
341 context (if any), retrieves the result, sets up expansion
342 strings and evaluates the condition outcome.
347 spf_process(const uschar ** listptr, const uschar * spf_envelope_sender,
351 const uschar *list = *listptr;
352 uschar *spf_result_id;
353 int rc = SPF_RESULT_PERMERROR;
355 DEBUG(D_receive) debug_printf("spf_process\n");
357 if (!(spf_server && spf_request))
358 /* no global context, assume temp error and skip to evaluation */
359 rc = SPF_RESULT_PERMERROR;
361 else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender))
362 /* Invalid sender address. This should be a real rare occurrence */
363 rc = SPF_RESULT_PERMERROR;
368 if (action == SPF_PROCESS_FALLBACK)
370 SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
371 spf_result_guessed = TRUE;
374 SPF_request_query_mailfrom(spf_request, &spf_response);
376 /* set up expansion items */
377 spf_header_comment = US SPF_response_get_header_comment(spf_response);
378 spf_received = US SPF_response_get_received_spf(spf_response);
379 spf_result = US SPF_strresult(SPF_response_result(spf_response));
380 spf_smtp_comment = US SPF_response_get_smtp_comment(spf_response);
382 rc = SPF_response_result(spf_response);
384 DEBUG(D_acl) spf_response_debug(spf_response);
387 /* We got a result. Now see if we should return OK or FAIL for it */
388 DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
390 if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
391 return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
393 while ((spf_result_id = string_nextinlist(&list, &sep, NULL, 0)))
397 if ((negate = spf_result_id[0] == '!'))
400 result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name) == 0;
401 if (negate != result) return OK;
411 authres_spf(gstring * g)
416 int start = 0; /* Compiler quietening */
417 DEBUG(D_acl) start = gstring_length(g);
419 g = string_append(g, 2, US";\n\tspf=", spf_result);
420 if (spf_result_guessed)
421 g = string_cat(g, US" (best guess record for domain)");
423 s = expand_string(US"$sender_address_domain");
425 g = string_append(g, 2, US" smtp.mailfrom=", s);
428 s = sender_helo_name;
430 ? string_append(g, 2, US" smtp.helo=", s)
431 : string_cat(g, US" smtp.mailfrom=<>");
433 DEBUG(D_acl) debug_printf("SPF:\tauthres '%.*s'\n",
434 gstring_length(g) - start - 3, g->s + start + 3);
437 DEBUG(D_acl) debug_printf("SPF:\tno authres\n");