1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
6 Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 - 2014
8 Copyright (c) The Exim Maintainers 2015 - 2020
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(FILE * fp)
41 SPF_get_lib_version(&maj, &min, &patch);
42 fprintf(fp, "Library version: spf2: Compile: %d.%d.%d\n",
43 SPF_LIB_VERSION_MAJOR, SPF_LIB_VERSION_MINOR, SPF_LIB_VERSION_PATCH);
44 fprintf(fp, " Runtime: %d.%d.%d\n",
51 SPF_dns_exim_lookup(SPF_dns_server_t *spf_dns_server,
52 const char *domain, ns_type rr_type, int should_cache)
54 dns_answer * dnsa = store_get_dns_answer();
60 .domain = CS domain, /* query information */
64 .rr_buf_len = 0, /* answer information */
65 .rr_buf_num = 0, /* no free of s */
68 .hook = NULL, /* misc information */
69 .source = spf_dns_server
73 DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", domain);
75 /* Shortcircuit SPF RR lookups by returning NO_DATA. They were obsoleted by
76 RFC 6686/7208 years ago. see bug #1294 */
80 HDEBUG(D_host_lookup) debug_printf("faking NO_DATA for SPF RR(99) lookup\n");
82 SPF_dns_rr_dup(&spfrr, &srr);
86 switch (dns_rc = dns_lookup(dnsa, US domain, rr_type, NULL))
88 case DNS_SUCCEED: srr.herrno = NETDB_SUCCESS; break;
89 case DNS_AGAIN: srr.herrno = TRY_AGAIN; break;
90 case DNS_NOMATCH: srr.herrno = HOST_NOT_FOUND; break;
91 case DNS_NODATA: srr.herrno = NO_DATA; break;
93 default: srr.herrno = NO_RECOVERY; break;
96 for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
97 rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
98 if (rr->type == rr_type) found++;
102 SPF_dns_rr_dup(&spfrr, &srr);
106 srr.rr = store_malloc(sizeof(SPF_dns_rr_data_t) * found);
109 for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
110 rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
111 if (rr->type == rr_type)
113 const uschar * s = rr->data;
119 s += 2; /* skip the MX precedence field */
122 uschar * buf = store_malloc(256);
123 (void)dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, s,
124 (DN_EXPAND_ARG4_TYPE)buf, 256);
134 if (strncmpic(rr->data+1, US SPF_VER_STR, 6) != 0)
136 HDEBUG(D_host_lookup) debug_printf("not an spf record: %.*s\n",
141 for (int off = 0; off < rr->size; off += chunk_len)
143 if (!(chunk_len = s[off++])) break;
144 g = string_catn(g, s+off, chunk_len);
148 gstring_release_unused(g);
149 s = string_copy_malloc(string_from_gstring(g));
150 DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", s);
158 uschar * buf = store_malloc(dnsa->answerlen + 1);
159 s = memcpy(buf, s, dnsa->answerlen + 1);
163 srr.rr[found++] = (void *) s;
166 /* Did we filter out all TXT RRs? Return NO_DATA instead of SUCCESS with
167 empty ANSWER section. */
169 if (!(srr.num_rr = found))
170 srr.herrno = NO_DATA;
172 /* spfrr->rr must have been malloc()d for this */
173 SPF_dns_rr_dup(&spfrr, &srr);
180 SPF_dns_exim_new(int debug)
182 SPF_dns_server_t * spf_dns_server = store_malloc(sizeof(SPF_dns_server_t));
184 DEBUG(D_receive) debug_printf("SPF_dns_exim_new\n");
186 memset(spf_dns_server, 0, sizeof(SPF_dns_server_t));
187 spf_dns_server->destroy = NULL;
188 spf_dns_server->lookup = SPF_dns_exim_lookup;
189 spf_dns_server->get_spf = NULL;
190 spf_dns_server->get_exp = NULL;
191 spf_dns_server->add_cache = NULL;
192 spf_dns_server->layer_below = NULL;
193 spf_dns_server->name = "exim";
194 spf_dns_server->debug = debug;
196 /* XXX This might have to return NO_DATA sometimes. */
198 spf_nxdomain = SPF_dns_rr_new_init(spf_dns_server,
199 "", ns_t_any, 24 * 60 * 60, HOST_NOT_FOUND);
202 free(spf_dns_server);
206 return spf_dns_server;
212 /* Construct the SPF library stack.
213 Return: Boolean success.
219 SPF_dns_server_t * dc;
223 DEBUG(D_receive) debug = 1;
225 /* We insert our own DNS access layer rather than letting the spf library
226 do it, so that our dns access path is used for debug tracing and for the
229 if (!(dc = SPF_dns_exim_new(debug)))
231 DEBUG(D_receive) debug_printf("spf: SPF_dns_exim_new() failed\n");
234 if (!(dc = SPF_dns_cache_new(dc, NULL, debug, 8)))
236 DEBUG(D_receive) debug_printf("spf: SPF_dns_cache_new() failed\n");
239 if (!(spf_server = SPF_server_new_dns(dc, debug)))
241 DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n");
244 /* Override the outdated explanation URL.
245 See https://www.mail-archive.com/mailop@mailop.org/msg08019.html
246 Used to work as "Please%_see%_http://www.open-spf.org/Why?id=%{S}&ip=%{C}&receiver=%{R}",
247 but is broken now (May 18th, 2020) */
248 if (!(s = expand_string(spf_smtp_comment_template)))
249 log_write(0, LOG_MAIN|LOG_PANIC_DIE, "expansion of spf_smtp_comment_template failed");
251 SPF_server_set_explanation(spf_server, CCS s, &spf_response);
252 if (SPF_response_errcode(spf_response) != SPF_E_SUCCESS)
253 log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", SPF_strerror(SPF_response_errcode(spf_response)));
259 /* Set up a context that can be re-used for several
260 messages on the same SMTP connection (that come from the
261 same host with the same HELO string).
263 Return: Boolean success
267 spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr)
270 debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);
272 if (!spf_server && !spf_init()) return FALSE;
274 if (SPF_server_set_rec_dom(spf_server, CS primary_hostname))
276 DEBUG(D_receive) debug_printf("spf: SPF_server_set_rec_dom(\"%s\") failed.\n",
282 spf_request = SPF_request_new(spf_server);
284 if ( SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr)
285 && SPF_request_set_ipv6_str(spf_request, CS spf_remote_addr)
289 debug_printf("spf: SPF_request_set_ipv4_str() and "
290 "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr);
296 if (SPF_request_set_helo_dom(spf_request, CS spf_helo_domain))
298 DEBUG(D_receive) debug_printf("spf: SPF_set_helo_dom(\"%s\") failed.\n",
310 spf_response_debug(SPF_response_t * spf_response)
312 if (SPF_response_messages(spf_response) == 0)
313 debug_printf(" (no errors)\n");
314 else for (int i = 0; i < SPF_response_messages(spf_response); i++)
316 SPF_error_t * err = SPF_response_message(spf_response, i);
317 debug_printf( "%s_msg = (%d) %s\n",
318 (SPF_error_errorp(err) ? "warn" : "err"),
320 SPF_error_message(err));
325 /* spf_process adds the envelope sender address to the existing
326 context (if any), retrieves the result, sets up expansion
327 strings and evaluates the condition outcome.
332 spf_process(const uschar **listptr, uschar *spf_envelope_sender, int action)
335 const uschar *list = *listptr;
336 uschar *spf_result_id;
337 int rc = SPF_RESULT_PERMERROR;
339 DEBUG(D_receive) debug_printf("spf_process\n");
341 if (!(spf_server && spf_request))
342 /* no global context, assume temp error and skip to evaluation */
343 rc = SPF_RESULT_PERMERROR;
345 else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender))
346 /* Invalid sender address. This should be a real rare occurrence */
347 rc = SPF_RESULT_PERMERROR;
352 if (action == SPF_PROCESS_FALLBACK)
354 SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
355 spf_result_guessed = TRUE;
358 SPF_request_query_mailfrom(spf_request, &spf_response);
360 /* set up expansion items */
361 spf_header_comment = US SPF_response_get_header_comment(spf_response);
362 spf_received = US SPF_response_get_received_spf(spf_response);
363 spf_result = US SPF_strresult(SPF_response_result(spf_response));
364 spf_smtp_comment = US SPF_response_get_smtp_comment(spf_response);
366 rc = SPF_response_result(spf_response);
368 DEBUG(D_acl) spf_response_debug(spf_response);
371 /* We got a result. Now see if we should return OK or FAIL for it */
372 DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
374 if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
375 return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
377 while ((spf_result_id = string_nextinlist(&list, &sep, NULL, 0)))
381 if ((negate = spf_result_id[0] == '!'))
384 result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name) == 0;
385 if (negate != result) return OK;
395 authres_spf(gstring * g)
398 if (!spf_result) return g;
400 g = string_append(g, 2, US";\n\tspf=", spf_result);
401 if (spf_result_guessed)
402 g = string_cat(g, US" (best guess record for domain)");
404 s = expand_string(US"$sender_address_domain");
406 ? string_append(g, 2, US" smtp.mailfrom=", s)
407 : string_cat(g, US" smtp.mailfrom=<>");