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-only
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
76 DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", domain);
78 /* Shortcircuit SPF RR lookups by returning NO_DATA. They were obsoleted by
79 RFC 6686/7208 years ago. see bug #1294 */
83 HDEBUG(D_host_lookup) debug_printf("faking NO_DATA for SPF RR(99) lookup\n");
85 SPF_dns_rr_dup(&spfrr, &srr);
86 store_free_dns_answer(dnsa);
90 switch (dns_rc = dns_lookup(dnsa, US domain, rr_type, NULL))
92 case DNS_SUCCEED: srr.herrno = NETDB_SUCCESS; break;
93 case DNS_AGAIN: srr.herrno = TRY_AGAIN; break;
94 case DNS_NOMATCH: srr.herrno = HOST_NOT_FOUND; break;
95 case DNS_NODATA: srr.herrno = NO_DATA; break;
97 default: srr.herrno = NO_RECOVERY; break;
100 for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
101 rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
102 if (rr->type == rr_type) found++;
106 SPF_dns_rr_dup(&spfrr, &srr);
107 store_free_dns_answer(dnsa);
111 srr.rr = store_malloc(sizeof(SPF_dns_rr_data_t) * found);
114 for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
115 rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
116 if (rr->type == rr_type)
118 const uschar * s = rr->data;
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 (strncmpic(rr->data+1, US SPF_VER_STR, 6) != 0)
141 HDEBUG(D_host_lookup) debug_printf("not an spf record: %.*s\n",
146 for (int off = 0; off < rr->size; off += chunk_len)
148 if (!(chunk_len = s[off++])) break;
149 g = string_catn(g, s+off, chunk_len);
153 gstring_release_unused(g);
154 s = string_copy_malloc(string_from_gstring(g));
155 DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", s);
163 uschar * buf = store_malloc(dnsa->answerlen + 1);
164 s = memcpy(buf, s, dnsa->answerlen + 1);
168 srr.rr[found++] = (void *) s;
171 /* Did we filter out all TXT RRs? Return NO_DATA instead of SUCCESS with
172 empty ANSWER section. */
174 if (!(srr.num_rr = found))
175 srr.herrno = NO_DATA;
177 /* spfrr->rr must have been malloc()d for this */
178 SPF_dns_rr_dup(&spfrr, &srr);
179 store_free_dns_answer(dnsa);
186 SPF_dns_exim_new(int debug)
188 SPF_dns_server_t * spf_dns_server = store_malloc(sizeof(SPF_dns_server_t));
190 DEBUG(D_receive) debug_printf("SPF_dns_exim_new\n");
192 memset(spf_dns_server, 0, sizeof(SPF_dns_server_t));
193 spf_dns_server->destroy = NULL;
194 spf_dns_server->lookup = SPF_dns_exim_lookup;
195 spf_dns_server->get_spf = NULL;
196 spf_dns_server->get_exp = NULL;
197 spf_dns_server->add_cache = NULL;
198 spf_dns_server->layer_below = NULL;
199 spf_dns_server->name = "exim";
200 spf_dns_server->debug = debug;
202 /* XXX This might have to return NO_DATA sometimes. */
204 spf_nxdomain = SPF_dns_rr_new_init(spf_dns_server,
205 "", ns_t_any, 24 * 60 * 60, HOST_NOT_FOUND);
208 store_free(spf_dns_server);
212 return spf_dns_server;
218 /* Construct the SPF library stack.
219 Return: Boolean success.
225 SPF_dns_server_t * dc;
229 DEBUG(D_receive) debug = 1;
231 /* We insert our own DNS access layer rather than letting the spf library
232 do it, so that our dns access path is used for debug tracing and for the
235 if (!(dc = SPF_dns_exim_new(debug)))
237 DEBUG(D_receive) debug_printf("spf: SPF_dns_exim_new() failed\n");
240 if (!(dc = SPF_dns_cache_new(dc, NULL, debug, 8)))
242 DEBUG(D_receive) debug_printf("spf: SPF_dns_cache_new() failed\n");
245 if (!(spf_server = SPF_server_new_dns(dc, debug)))
247 DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n");
250 /* Override the outdated explanation URL.
251 See https://www.mail-archive.com/mailop@mailop.org/msg08019.html
252 Used to work as "Please%_see%_http://www.open-spf.org/Why?id=%{S}&ip=%{C}&receiver=%{R}",
253 but is broken now (May 18th, 2020) */
254 if (!(s = expand_string(spf_smtp_comment_template)))
255 log_write(0, LOG_MAIN|LOG_PANIC_DIE, "expansion of spf_smtp_comment_template failed");
257 SPF_server_set_explanation(spf_server, CCS s, &spf_response);
258 if (SPF_response_errcode(spf_response) != SPF_E_SUCCESS)
259 log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", SPF_strerror(SPF_response_errcode(spf_response)));
265 /* Set up a context that can be re-used for several
266 messages on the same SMTP connection (that come from the
267 same host with the same HELO string).
269 Return: Boolean success
273 spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr)
276 debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);
278 if (!spf_server && !spf_init()) return FALSE;
280 if (SPF_server_set_rec_dom(spf_server, CS primary_hostname))
282 DEBUG(D_receive) debug_printf("spf: SPF_server_set_rec_dom(\"%s\") failed.\n",
288 spf_request = SPF_request_new(spf_server);
290 if ( SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr)
291 && SPF_request_set_ipv6_str(spf_request, CS spf_remote_addr)
295 debug_printf("spf: SPF_request_set_ipv4_str() and "
296 "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr);
302 if (SPF_request_set_helo_dom(spf_request, CS spf_helo_domain))
304 DEBUG(D_receive) debug_printf("spf: SPF_set_helo_dom(\"%s\") failed.\n",
316 spf_response_debug(SPF_response_t * spf_response)
318 if (SPF_response_messages(spf_response) == 0)
319 debug_printf(" (no errors)\n");
320 else for (int i = 0; i < SPF_response_messages(spf_response); i++)
322 SPF_error_t * err = SPF_response_message(spf_response, i);
323 debug_printf( "%s_msg = (%d) %s\n",
324 (SPF_error_errorp(err) ? "warn" : "err"),
326 SPF_error_message(err));
331 /* spf_process adds the envelope sender address to the existing
332 context (if any), retrieves the result, sets up expansion
333 strings and evaluates the condition outcome.
338 spf_process(const uschar **listptr, uschar *spf_envelope_sender, int action)
341 const uschar *list = *listptr;
342 uschar *spf_result_id;
343 int rc = SPF_RESULT_PERMERROR;
345 DEBUG(D_receive) debug_printf("spf_process\n");
347 if (!(spf_server && spf_request))
348 /* no global context, assume temp error and skip to evaluation */
349 rc = SPF_RESULT_PERMERROR;
351 else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender))
352 /* Invalid sender address. This should be a real rare occurrence */
353 rc = SPF_RESULT_PERMERROR;
358 if (action == SPF_PROCESS_FALLBACK)
360 SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
361 spf_result_guessed = TRUE;
364 SPF_request_query_mailfrom(spf_request, &spf_response);
366 /* set up expansion items */
367 spf_header_comment = US SPF_response_get_header_comment(spf_response);
368 spf_received = US SPF_response_get_received_spf(spf_response);
369 spf_result = US SPF_strresult(SPF_response_result(spf_response));
370 spf_smtp_comment = US SPF_response_get_smtp_comment(spf_response);
372 rc = SPF_response_result(spf_response);
374 DEBUG(D_acl) spf_response_debug(spf_response);
377 /* We got a result. Now see if we should return OK or FAIL for it */
378 DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
380 if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
381 return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
383 while ((spf_result_id = string_nextinlist(&list, &sep, NULL, 0)))
387 if ((negate = spf_result_id[0] == '!'))
390 result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name) == 0;
391 if (negate != result) return OK;
401 authres_spf(gstring * g)
404 if (!spf_result) return g;
406 g = string_append(g, 2, US";\n\tspf=", spf_result);
407 if (spf_result_guessed)
408 g = string_cat(g, US" (best guess record for domain)");
410 s = expand_string(US"$sender_address_domain");
412 return string_append(g, 2, US" smtp.mailfrom=", s);
414 s = sender_helo_name;
416 ? string_append(g, 2, US" smtp.helo=", s)
417 : string_cat(g, US" smtp.mailfrom=<>");