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 switch (dns_rc = dns_lookup(dnsa, US domain, rr_type, NULL))
77 case DNS_SUCCEED: srr.herrno = NETDB_SUCCESS; break;
78 case DNS_AGAIN: srr.herrno = TRY_AGAIN; break;
79 case DNS_NOMATCH: srr.herrno = HOST_NOT_FOUND; break;
80 case DNS_NODATA: srr.herrno = NO_DATA; break;
82 default: srr.herrno = NO_RECOVERY; break;
85 for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
86 rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
87 if (rr->type == rr_type) found++;
91 SPF_dns_rr_dup(&spfrr, &srr);
95 srr.rr = store_malloc(sizeof(SPF_dns_rr_data_t) * found);
98 for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
99 rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
100 if (rr->type == rr_type)
102 const uschar * s = rr->data;
108 s += 2; /* skip the MX precedence field */
111 uschar * buf = store_malloc(256);
112 (void)dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, s,
113 (DN_EXPAND_ARG4_TYPE)buf, 256);
123 if (strncmpic(rr->data+1, US SPF_VER_STR, 6) != 0)
125 HDEBUG(D_host_lookup) debug_printf("not an spf record: %.*s\n",
130 for (int off = 0; off < rr->size; off += chunk_len)
132 if (!(chunk_len = s[off++])) break;
133 g = string_catn(g, s+off, chunk_len);
137 gstring_release_unused(g);
138 s = string_copy_malloc(string_from_gstring(g));
139 DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", s);
147 uschar * buf = store_malloc(dnsa->answerlen + 1);
148 s = memcpy(buf, s, dnsa->answerlen + 1);
152 srr.rr[found++] = (void *) s;
155 /* Did we filter out all TXT RRs? Return NO_DATA instead of SUCCESS with
156 empty ANSWER section. */
158 if (!(srr.num_rr = found))
159 srr.herrno = NO_DATA;
161 /* spfrr->rr must have been malloc()d for this */
162 SPF_dns_rr_dup(&spfrr, &srr);
169 SPF_dns_exim_new(int debug)
171 SPF_dns_server_t * spf_dns_server = store_malloc(sizeof(SPF_dns_server_t));
173 DEBUG(D_receive) debug_printf("SPF_dns_exim_new\n");
175 memset(spf_dns_server, 0, sizeof(SPF_dns_server_t));
176 spf_dns_server->destroy = NULL;
177 spf_dns_server->lookup = SPF_dns_exim_lookup;
178 spf_dns_server->get_spf = NULL;
179 spf_dns_server->get_exp = NULL;
180 spf_dns_server->add_cache = NULL;
181 spf_dns_server->layer_below = NULL;
182 spf_dns_server->name = "exim";
183 spf_dns_server->debug = debug;
185 /* XXX This might have to return NO_DATA sometimes. */
187 spf_nxdomain = SPF_dns_rr_new_init(spf_dns_server,
188 "", ns_t_any, 24 * 60 * 60, HOST_NOT_FOUND);
191 free(spf_dns_server);
195 return spf_dns_server;
201 /* Construct the SPF library stack.
202 Return: Boolean success.
208 SPF_dns_server_t * dc;
211 DEBUG(D_receive) debug = 1;
213 /* We insert our own DNS access layer rather than letting the spf library
214 do it, so that our dns access path is used for debug tracing and for the
217 if (!(dc = SPF_dns_exim_new(debug)))
219 DEBUG(D_receive) debug_printf("spf: SPF_dns_exim_new() failed\n");
222 if (!(dc = SPF_dns_cache_new(dc, NULL, debug, 8)))
224 DEBUG(D_receive) debug_printf("spf: SPF_dns_cache_new() failed\n");
227 if (!(spf_server = SPF_server_new_dns(dc, debug)))
229 DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n");
232 /* Quick hack to override the outdated explanation URL.
233 See https://www.mail-archive.com/mailop@mailop.org/msg08019.html */
234 SPF_server_set_explanation(spf_server, "Please%_see%_http://www.open-spf.org/Why?id=%{S}&ip=%{C}&receiver=%{R}", &spf_response);
235 if (SPF_response_errcode(spf_response) != SPF_E_SUCCESS)
236 log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", SPF_strerror(SPF_response_errcode(spf_response)));
242 /* Set up a context that can be re-used for several
243 messages on the same SMTP connection (that come from the
244 same host with the same HELO string).
246 Return: Boolean success
250 spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr)
253 debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);
255 if (!spf_server && !spf_init()) return FALSE;
257 if (SPF_server_set_rec_dom(spf_server, CS primary_hostname))
259 DEBUG(D_receive) debug_printf("spf: SPF_server_set_rec_dom(\"%s\") failed.\n",
265 spf_request = SPF_request_new(spf_server);
267 if ( SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr)
268 && SPF_request_set_ipv6_str(spf_request, CS spf_remote_addr)
272 debug_printf("spf: SPF_request_set_ipv4_str() and "
273 "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr);
279 if (SPF_request_set_helo_dom(spf_request, CS spf_helo_domain))
281 DEBUG(D_receive) debug_printf("spf: SPF_set_helo_dom(\"%s\") failed.\n",
293 spf_response_debug(SPF_response_t * spf_response)
295 if (SPF_response_messages(spf_response) == 0)
296 debug_printf(" (no errors)\n");
297 else for (int i = 0; i < SPF_response_messages(spf_response); i++)
299 SPF_error_t * err = SPF_response_message(spf_response, i);
300 debug_printf( "%s_msg = (%d) %s\n",
301 (SPF_error_errorp(err) ? "warn" : "err"),
303 SPF_error_message(err));
308 /* spf_process adds the envelope sender address to the existing
309 context (if any), retrieves the result, sets up expansion
310 strings and evaluates the condition outcome.
315 spf_process(const uschar **listptr, uschar *spf_envelope_sender, int action)
318 const uschar *list = *listptr;
319 uschar *spf_result_id;
320 int rc = SPF_RESULT_PERMERROR;
322 DEBUG(D_receive) debug_printf("spf_process\n");
324 if (!(spf_server && spf_request))
325 /* no global context, assume temp error and skip to evaluation */
326 rc = SPF_RESULT_PERMERROR;
328 else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender))
329 /* Invalid sender address. This should be a real rare occurrence */
330 rc = SPF_RESULT_PERMERROR;
335 if (action == SPF_PROCESS_FALLBACK)
337 SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
338 spf_result_guessed = TRUE;
341 SPF_request_query_mailfrom(spf_request, &spf_response);
343 /* set up expansion items */
344 spf_header_comment = US SPF_response_get_header_comment(spf_response);
345 spf_received = US SPF_response_get_received_spf(spf_response);
346 spf_result = US SPF_strresult(SPF_response_result(spf_response));
347 spf_smtp_comment = US SPF_response_get_smtp_comment(spf_response);
349 rc = SPF_response_result(spf_response);
351 DEBUG(D_acl) spf_response_debug(spf_response);
354 /* We got a result. Now see if we should return OK or FAIL for it */
355 DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
357 if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
358 return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
360 while ((spf_result_id = string_nextinlist(&list, &sep, NULL, 0)))
364 if ((negate = spf_result_id[0] == '!'))
367 result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name) == 0;
368 if (negate != result) return OK;
378 authres_spf(gstring * g)
381 if (!spf_result) return g;
383 g = string_append(g, 2, US";\n\tspf=", spf_result);
384 if (spf_result_guessed)
385 g = string_cat(g, US" (best guess record for domain)");
387 s = expand_string(US"$sender_address_domain");
389 ? string_append(g, 2, US" smtp.mailfrom=", s)
390 : string_cat(g, US" smtp.mailfrom=<>");