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
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 s += 2; /* skip the MX precedence field */
128 uschar * buf = store_malloc(256);
129 (void)dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, s,
130 (DN_EXPAND_ARG4_TYPE)buf, 256);
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 for (int off = 0; off < rr->size; off += chunk_len)
149 if (!(chunk_len = s[off++])) break;
150 g = string_catn(g, s+off, chunk_len);
154 gstring_release_unused(g);
155 s = string_copy_malloc(string_from_gstring(g));
156 DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", s);
164 uschar * buf = store_malloc(dnsa->answerlen + 1);
165 s = memcpy(buf, s, dnsa->answerlen + 1);
169 srr.rr[found++] = (void *) s;
172 /* Did we filter out all TXT RRs? Return NO_DATA instead of SUCCESS with
173 empty ANSWER section. */
175 if (!(srr.num_rr = found))
176 srr.herrno = NO_DATA;
178 /* spfrr->rr must have been malloc()d for this */
179 SPF_dns_rr_dup(&spfrr, &srr);
180 store_free_dns_answer(dnsa);
187 SPF_dns_exim_new(int debug)
189 SPF_dns_server_t * spf_dns_server = store_malloc(sizeof(SPF_dns_server_t));
191 DEBUG(D_receive) debug_printf("SPF_dns_exim_new\n");
193 memset(spf_dns_server, 0, sizeof(SPF_dns_server_t));
194 spf_dns_server->destroy = NULL;
195 spf_dns_server->lookup = SPF_dns_exim_lookup;
196 spf_dns_server->get_spf = NULL;
197 spf_dns_server->get_exp = NULL;
198 spf_dns_server->add_cache = NULL;
199 spf_dns_server->layer_below = NULL;
200 spf_dns_server->name = "exim";
201 spf_dns_server->debug = debug;
203 /* XXX This might have to return NO_DATA sometimes. */
205 spf_nxdomain = SPF_dns_rr_new_init(spf_dns_server,
206 "", ns_t_any, 24 * 60 * 60, HOST_NOT_FOUND);
209 store_free(spf_dns_server);
213 return spf_dns_server;
219 /* Construct the SPF library stack.
220 Return: Boolean success.
226 SPF_dns_server_t * dc;
230 DEBUG(D_receive) debug = 1;
232 /* We insert our own DNS access layer rather than letting the spf library
233 do it, so that our dns access path is used for debug tracing and for the
236 if (!(dc = SPF_dns_exim_new(debug)))
238 DEBUG(D_receive) debug_printf("spf: SPF_dns_exim_new() failed\n");
241 if (!(dc = SPF_dns_cache_new(dc, NULL, debug, 8)))
243 DEBUG(D_receive) debug_printf("spf: SPF_dns_cache_new() failed\n");
246 if (!(spf_server = SPF_server_new_dns(dc, debug)))
248 DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n");
251 /* Override the outdated explanation URL.
252 See https://www.mail-archive.com/mailop@mailop.org/msg08019.html
253 Used to work as "Please%_see%_http://www.open-spf.org/Why?id=%{S}&ip=%{C}&receiver=%{R}",
254 but is broken now (May 18th, 2020) */
255 if (!(s = expand_string(spf_smtp_comment_template)))
256 log_write(0, LOG_MAIN|LOG_PANIC_DIE, "expansion of spf_smtp_comment_template failed");
258 SPF_server_set_explanation(spf_server, CCS s, &spf_response);
259 if (SPF_response_errcode(spf_response) != SPF_E_SUCCESS)
260 log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", SPF_strerror(SPF_response_errcode(spf_response)));
266 /* Set up a context that can be re-used for several
267 messages on the same SMTP connection (that come from the
268 same host with the same HELO string).
270 Return: Boolean success
274 spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr)
277 debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);
279 if (!spf_server && !spf_init()) return FALSE;
281 if (SPF_server_set_rec_dom(spf_server, CS primary_hostname))
283 DEBUG(D_receive) debug_printf("spf: SPF_server_set_rec_dom(\"%s\") failed.\n",
289 spf_request = SPF_request_new(spf_server);
291 if ( SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr)
292 && SPF_request_set_ipv6_str(spf_request, CS spf_remote_addr)
296 debug_printf("spf: SPF_request_set_ipv4_str() and "
297 "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr);
303 if (SPF_request_set_helo_dom(spf_request, CS spf_helo_domain))
305 DEBUG(D_receive) debug_printf("spf: SPF_set_helo_dom(\"%s\") failed.\n",
317 spf_response_debug(SPF_response_t * spf_response)
319 if (SPF_response_messages(spf_response) == 0)
320 debug_printf(" (no errors)\n");
321 else for (int i = 0; i < SPF_response_messages(spf_response); i++)
323 SPF_error_t * err = SPF_response_message(spf_response, i);
324 debug_printf( "%s_msg = (%d) %s\n",
325 (SPF_error_errorp(err) ? "warn" : "err"),
327 SPF_error_message(err));
332 /* spf_process adds the envelope sender address to the existing
333 context (if any), retrieves the result, sets up expansion
334 strings and evaluates the condition outcome.
339 spf_process(const uschar **listptr, uschar *spf_envelope_sender, int action)
342 const uschar *list = *listptr;
343 uschar *spf_result_id;
344 int rc = SPF_RESULT_PERMERROR;
346 DEBUG(D_receive) debug_printf("spf_process\n");
348 if (!(spf_server && spf_request))
349 /* no global context, assume temp error and skip to evaluation */
350 rc = SPF_RESULT_PERMERROR;
352 else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender))
353 /* Invalid sender address. This should be a real rare occurrence */
354 rc = SPF_RESULT_PERMERROR;
359 if (action == SPF_PROCESS_FALLBACK)
361 SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
362 spf_result_guessed = TRUE;
365 SPF_request_query_mailfrom(spf_request, &spf_response);
367 /* set up expansion items */
368 spf_header_comment = US SPF_response_get_header_comment(spf_response);
369 spf_received = US SPF_response_get_received_spf(spf_response);
370 spf_result = US SPF_strresult(SPF_response_result(spf_response));
371 spf_smtp_comment = US SPF_response_get_smtp_comment(spf_response);
373 rc = SPF_response_result(spf_response);
375 DEBUG(D_acl) spf_response_debug(spf_response);
378 /* We got a result. Now see if we should return OK or FAIL for it */
379 DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
381 if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
382 return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
384 while ((spf_result_id = string_nextinlist(&list, &sep, NULL, 0)))
388 if ((negate = spf_result_id[0] == '!'))
391 result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name) == 0;
392 if (negate != result) return OK;
402 authres_spf(gstring * g)
407 int start = 0; /* Compiler quietening */
408 DEBUG(D_acl) start = gstring_length(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 g = string_append(g, 2, US" smtp.mailfrom=", s);
419 s = sender_helo_name;
421 ? string_append(g, 2, US" smtp.helo=", s)
422 : string_cat(g, US" smtp.mailfrom=<>");
424 DEBUG(D_acl) debug_printf("SPF:\tauthres '%.*s'\n",
425 gstring_length(g) - start - 3, g->s + start + 3);
428 DEBUG(D_acl) debug_printf("SPF:\tno authres\n");