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;
156 /* spfrr->rr must have been malloc()d for this */
157 SPF_dns_rr_dup(&spfrr, &srr);
164 SPF_dns_exim_new(int debug)
166 SPF_dns_server_t * spf_dns_server = store_malloc(sizeof(SPF_dns_server_t));
168 DEBUG(D_receive) debug_printf("SPF_dns_exim_new\n");
170 memset(spf_dns_server, 0, sizeof(SPF_dns_server_t));
171 spf_dns_server->destroy = NULL;
172 spf_dns_server->lookup = SPF_dns_exim_lookup;
173 spf_dns_server->get_spf = NULL;
174 spf_dns_server->get_exp = NULL;
175 spf_dns_server->add_cache = NULL;
176 spf_dns_server->layer_below = NULL;
177 spf_dns_server->name = "exim";
178 spf_dns_server->debug = debug;
180 /* XXX This might have to return NO_DATA sometimes. */
182 spf_nxdomain = SPF_dns_rr_new_init(spf_dns_server,
183 "", ns_t_any, 24 * 60 * 60, HOST_NOT_FOUND);
186 free(spf_dns_server);
190 return spf_dns_server;
196 /* Construct the SPF library stack.
197 Return: Boolean success.
203 SPF_dns_server_t * dc;
206 DEBUG(D_receive) debug = 1;
208 /* We insert our own DNS access layer rather than letting the spf library
209 do it, so that our dns access path is used for debug tracing and for the
212 if (!(dc = SPF_dns_exim_new(debug)))
214 DEBUG(D_receive) debug_printf("spf: SPF_dns_exim_new() failed\n");
217 if (!(dc = SPF_dns_cache_new(dc, NULL, debug, 8)))
219 DEBUG(D_receive) debug_printf("spf: SPF_dns_cache_new() failed\n");
222 if (!(spf_server = SPF_server_new_dns(dc, debug)))
224 DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n");
227 /* Quick hack to override the outdated explanation URL.
228 See https://www.mail-archive.com/mailop@mailop.org/msg08019.html */
229 SPF_server_set_explanation(spf_server, "Please%_see%_http://www.open-spf.org/Why?id=%{S}&ip=%{C}&receiver=%{R}", &spf_response);
230 if (SPF_response_errcode(spf_response) != SPF_E_SUCCESS)
231 log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", SPF_strerror(SPF_response_errcode(spf_response)));
237 /* Set up a context that can be re-used for several
238 messages on the same SMTP connection (that come from the
239 same host with the same HELO string).
241 Return: Boolean success
245 spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr)
248 debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);
250 if (!spf_server && !spf_init()) return FALSE;
252 if (SPF_server_set_rec_dom(spf_server, CS primary_hostname))
254 DEBUG(D_receive) debug_printf("spf: SPF_server_set_rec_dom(\"%s\") failed.\n",
260 spf_request = SPF_request_new(spf_server);
262 if ( SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr)
263 && SPF_request_set_ipv6_str(spf_request, CS spf_remote_addr)
267 debug_printf("spf: SPF_request_set_ipv4_str() and "
268 "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr);
274 if (SPF_request_set_helo_dom(spf_request, CS spf_helo_domain))
276 DEBUG(D_receive) debug_printf("spf: SPF_set_helo_dom(\"%s\") failed.\n",
288 spf_response_debug(SPF_response_t * spf_response)
290 if (SPF_response_messages(spf_response) == 0)
291 debug_printf(" (no errors)\n");
292 else for (int i = 0; i < SPF_response_messages(spf_response); i++)
294 SPF_error_t * err = SPF_response_message(spf_response, i);
295 debug_printf( "%s_msg = (%d) %s\n",
296 (SPF_error_errorp(err) ? "warn" : "err"),
298 SPF_error_message(err));
303 /* spf_process adds the envelope sender address to the existing
304 context (if any), retrieves the result, sets up expansion
305 strings and evaluates the condition outcome.
310 spf_process(const uschar **listptr, uschar *spf_envelope_sender, int action)
313 const uschar *list = *listptr;
314 uschar *spf_result_id;
315 int rc = SPF_RESULT_PERMERROR;
317 DEBUG(D_receive) debug_printf("spf_process\n");
319 if (!(spf_server && spf_request))
320 /* no global context, assume temp error and skip to evaluation */
321 rc = SPF_RESULT_PERMERROR;
323 else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender))
324 /* Invalid sender address. This should be a real rare occurrence */
325 rc = SPF_RESULT_PERMERROR;
330 if (action == SPF_PROCESS_FALLBACK)
332 SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
333 spf_result_guessed = TRUE;
336 SPF_request_query_mailfrom(spf_request, &spf_response);
338 /* set up expansion items */
339 spf_header_comment = US SPF_response_get_header_comment(spf_response);
340 spf_received = US SPF_response_get_received_spf(spf_response);
341 spf_result = US SPF_strresult(SPF_response_result(spf_response));
342 spf_smtp_comment = US SPF_response_get_smtp_comment(spf_response);
344 rc = SPF_response_result(spf_response);
346 DEBUG(D_acl) spf_response_debug(spf_response);
349 /* We got a result. Now see if we should return OK or FAIL for it */
350 DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
352 if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
353 return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
355 while ((spf_result_id = string_nextinlist(&list, &sep, NULL, 0)))
359 if ((negate = spf_result_id[0] == '!'))
362 result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name) == 0;
363 if (negate != result) return OK;
373 authres_spf(gstring * g)
376 if (!spf_result) return g;
378 g = string_append(g, 2, US";\n\tspf=", spf_result);
379 if (spf_result_guessed)
380 g = string_cat(g, US" (best guess record for domain)");
382 s = expand_string(US"$sender_address_domain");
384 ? string_append(g, 2, US" smtp.mailfrom=", s)
385 : string_cat(g, US" smtp.mailfrom=<>");