SPF: shortcircuit SPF RR lookups. Bug 1294
[exim.git] / src / src / spf.c
CommitLineData
8523533c
TK
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
8e669ac1 4
b8e97684 5/* SPF support.
5a66c31b 6 Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 - 2014
80fea873 7 License: GPL
53ef3d84 8 Copyright (c) The Exim Maintainers 2015 - 2020
80fea873 9*/
8e669ac1 10
8523533c
TK
11/* Code for calling spf checks via libspf-alt. Called from acl.c. */
12
13#include "exim.h"
7952eef9 14#ifdef SUPPORT_SPF
8523533c 15
6d06cf48
TK
16/* must be kept in numeric order */
17static spf_result_id spf_result_id_list[] = {
f2ed27cf
JH
18 /* name value */
19 { US"invalid", 0},
20 { US"neutral", 1 },
21 { US"pass", 2 },
22 { US"fail", 3 },
23 { US"softfail", 4 },
24 { US"none", 5 },
f2ed27cf
JH
25 { US"temperror", 6 }, /* RFC 4408 defined */
26 { US"permerror", 7 } /* RFC 4408 defined */
6d06cf48
TK
27};
28
384152a6
TK
29SPF_server_t *spf_server = NULL;
30SPF_request_t *spf_request = NULL;
31SPF_response_t *spf_response = NULL;
32SPF_response_t *spf_response_2mx = NULL;
8523533c 33
b8e97684
JH
34SPF_dns_rr_t * spf_nxdomain = NULL;
35
36
85e453f6
JH
37void
38spf_lib_version_report(FILE * fp)
39{
40int maj, min, patch;
41SPF_get_lib_version(&maj, &min, &patch);
42fprintf(fp, "Library version: spf2: Compile: %d.%d.%d\n",
43 SPF_LIB_VERSION_MAJOR, SPF_LIB_VERSION_MINOR, SPF_LIB_VERSION_PATCH);
44fprintf(fp, " Runtime: %d.%d.%d\n",
45 maj, min, patch);
46}
47
48
b8e97684
JH
49
50static SPF_dns_rr_t *
51SPF_dns_exim_lookup(SPF_dns_server_t *spf_dns_server,
44e90dfa 52 const char *domain, ns_type rr_type, int should_cache)
b8e97684 53{
8743d3ac 54dns_answer * dnsa = store_get_dns_answer();
b8e97684
JH
55dns_scan dnss;
56SPF_dns_rr_t * spfrr;
4533e306
JH
57unsigned found = 0;
58
59SPF_dns_rr_t srr = {
60 .domain = CS domain, /* query information */
61 .domain_buf_len = 0,
62 .rr_type = rr_type,
63
64 .rr_buf_len = 0, /* answer information */
65 .rr_buf_num = 0, /* no free of s */
66 .utc_ttl = 0,
67
68 .hook = NULL, /* misc information */
69 .source = spf_dns_server
70};
44e90dfa 71int dns_rc;
b8e97684 72
4a3709fb 73DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", domain);
b8e97684 74
79e5ebf9
WB
75/* Shortcircuit SPF RR lookups by returning HOST_NOT_FOUND (shortest code path
76in libspf2). They were obsoleted by RFC 6686/7208 years ago. see bug #1294
77*/
78
79if (rr_type == T_SPF)
80 {
81 HDEBUG(D_host_lookup) debug_printf("faking HOST_NOT_FOUND for SPF RR(99) lookup\n");
82 srr.herrno = HOST_NOT_FOUND;
83 SPF_dns_rr_dup(&spfrr, &srr);
84 return spfrr;
85 }
86
44e90dfa 87switch (dns_rc = dns_lookup(dnsa, US domain, rr_type, NULL))
4533e306 88 {
44e90dfa
WB
89 case DNS_SUCCEED: srr.herrno = NETDB_SUCCESS; break;
90 case DNS_AGAIN: srr.herrno = TRY_AGAIN; break;
91 case DNS_NOMATCH: srr.herrno = HOST_NOT_FOUND; break;
f73eb7e3 92 case DNS_NODATA: srr.herrno = NO_DATA; break;
44e90dfa
WB
93 case DNS_FAIL:
94 default: srr.herrno = NO_RECOVERY; break;
95 }
4533e306
JH
96
97for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
98 rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
99 if (rr->type == rr_type) found++;
100
44e90dfa
WB
101if (found == 0)
102 {
103 SPF_dns_rr_dup(&spfrr, &srr);
104 return spfrr;
105 }
106
4533e306 107srr.rr = store_malloc(sizeof(SPF_dns_rr_data_t) * found);
4533e306
JH
108
109found = 0;
110for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
111 rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
112 if (rr->type == rr_type)
113 {
114 const uschar * s = rr->data;
115
116 srr.ttl = rr->ttl;
117 switch(rr_type)
b8e97684 118 {
4533e306 119 case T_MX:
44e90dfa 120 s += 2; /* skip the MX precedence field */
4533e306 121 case T_PTR:
b8e97684 122 {
4533e306
JH
123 uschar * buf = store_malloc(256);
124 (void)dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, s,
125 (DN_EXPAND_ARG4_TYPE)buf, 256);
126 s = buf;
127 break;
128 }
129
130 case T_TXT:
131 {
132 gstring * g = NULL;
133 uschar chunk_len;
4a3709fb 134
85e453f6 135 if (strncmpic(rr->data+1, US SPF_VER_STR, 6) != 0)
4a3709fb 136 {
53ef3d84
JH
137 HDEBUG(D_host_lookup) debug_printf("not an spf record: %.*s\n",
138 (int) s[0], s+1);
4533e306 139 continue;
4a3709fb
WB
140 }
141
4533e306 142 for (int off = 0; off < rr->size; off += chunk_len)
4a3709fb 143 {
4533e306
JH
144 if (!(chunk_len = s[off++])) break;
145 g = string_catn(g, s+off, chunk_len);
4a3709fb 146 }
4533e306
JH
147 if (!g)
148 continue;
149 gstring_release_unused(g);
150 s = string_copy_malloc(string_from_gstring(g));
53ef3d84 151 DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", s);
4533e306 152 break;
b8e97684 153 }
b8e97684 154
4533e306
JH
155 case T_A:
156 case T_AAAA:
157 default:
158 {
159 uschar * buf = store_malloc(dnsa->answerlen + 1);
160 s = memcpy(buf, s, dnsa->answerlen + 1);
161 break;
162 }
b8e97684 163 }
4533e306
JH
164 srr.rr[found++] = (void *) s;
165 }
b8e97684 166
67794d2b
WB
167/* Did we filter out all TXT RRs? Return NO_DATA instead of SUCCESS with
168empty ANSWER section. */
169
170if (!(srr.num_rr = found))
171 srr.herrno = NO_DATA;
172
4533e306
JH
173/* spfrr->rr must have been malloc()d for this */
174SPF_dns_rr_dup(&spfrr, &srr);
b8e97684
JH
175return spfrr;
176}
177
178
179
180SPF_dns_server_t *
181SPF_dns_exim_new(int debug)
182{
4533e306 183SPF_dns_server_t * spf_dns_server = store_malloc(sizeof(SPF_dns_server_t));
b8e97684
JH
184
185DEBUG(D_receive) debug_printf("SPF_dns_exim_new\n");
186
b8e97684 187memset(spf_dns_server, 0, sizeof(SPF_dns_server_t));
b8e97684
JH
188spf_dns_server->destroy = NULL;
189spf_dns_server->lookup = SPF_dns_exim_lookup;
190spf_dns_server->get_spf = NULL;
191spf_dns_server->get_exp = NULL;
192spf_dns_server->add_cache = NULL;
193spf_dns_server->layer_below = NULL;
194spf_dns_server->name = "exim";
195spf_dns_server->debug = debug;
196
197/* XXX This might have to return NO_DATA sometimes. */
198
199spf_nxdomain = SPF_dns_rr_new_init(spf_dns_server,
200 "", ns_t_any, 24 * 60 * 60, HOST_NOT_FOUND);
201if (!spf_nxdomain)
202 {
203 free(spf_dns_server);
204 return NULL;
205 }
206
207return spf_dns_server;
208}
209
210
eb52e2cb 211
8e669ac1 212
73ec116f
JH
213/* Construct the SPF library stack.
214 Return: Boolean success.
215*/
8e669ac1 216
eb52e2cb 217BOOL
73ec116f 218spf_init(void)
eb52e2cb 219{
b8e97684 220SPF_dns_server_t * dc;
73ec116f 221int debug = 0;
b8e97684 222
73ec116f 223DEBUG(D_receive) debug = 1;
b8e97684
JH
224
225/* We insert our own DNS access layer rather than letting the spf library
226do it, so that our dns access path is used for debug tracing and for the
227testsuite. */
8e669ac1 228
b8e97684
JH
229if (!(dc = SPF_dns_exim_new(debug)))
230 {
231 DEBUG(D_receive) debug_printf("spf: SPF_dns_exim_new() failed\n");
232 return FALSE;
233 }
234if (!(dc = SPF_dns_cache_new(dc, NULL, debug, 8)))
235 {
236 DEBUG(D_receive) debug_printf("spf: SPF_dns_cache_new() failed\n");
237 return FALSE;
238 }
239if (!(spf_server = SPF_server_new_dns(dc, debug)))
eb52e2cb
JH
240 {
241 DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n");
242 return FALSE;
8523533c 243 }
05e4f4de
HSHR
244 /* Quick hack to override the outdated explanation URL.
245 See https://www.mail-archive.com/mailop@mailop.org/msg08019.html */
246 SPF_server_set_explanation(spf_server, "Please%_see%_http://www.open-spf.org/Why?id=%{S}&ip=%{C}&receiver=%{R}", &spf_response);
247 if (SPF_response_errcode(spf_response) != SPF_E_SUCCESS)
248 log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", SPF_strerror(SPF_response_errcode(spf_response)));
249
73ec116f
JH
250return TRUE;
251}
252
253
254/* Set up a context that can be re-used for several
255 messages on the same SMTP connection (that come from the
256 same host with the same HELO string).
257
258Return: Boolean success
259*/
260
261BOOL
262spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr)
263{
264DEBUG(D_receive)
265 debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);
266
267if (!spf_server && !spf_init()) return FALSE;
8523533c 268
eb52e2cb
JH
269if (SPF_server_set_rec_dom(spf_server, CS primary_hostname))
270 {
271 DEBUG(D_receive) debug_printf("spf: SPF_server_set_rec_dom(\"%s\") failed.\n",
272 primary_hostname);
273 spf_server = NULL;
274 return FALSE;
7b3a77e5
TK
275 }
276
eb52e2cb
JH
277spf_request = SPF_request_new(spf_server);
278
279if ( SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr)
280 && SPF_request_set_ipv6_str(spf_request, CS spf_remote_addr)
281 )
282 {
283 DEBUG(D_receive)
284 debug_printf("spf: SPF_request_set_ipv4_str() and "
285 "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr);
286 spf_server = NULL;
287 spf_request = NULL;
288 return FALSE;
8523533c
TK
289 }
290
eb52e2cb
JH
291if (SPF_request_set_helo_dom(spf_request, CS spf_helo_domain))
292 {
293 DEBUG(D_receive) debug_printf("spf: SPF_set_helo_dom(\"%s\") failed.\n",
294 spf_helo_domain);
295 spf_server = NULL;
296 spf_request = NULL;
297 return FALSE;
8523533c 298 }
8e669ac1 299
eb52e2cb 300return TRUE;
8523533c
TK
301}
302
303
53ef3d84
JH
304void
305spf_response_debug(SPF_response_t * spf_response)
306{
307if (SPF_response_messages(spf_response) == 0)
308 debug_printf(" (no errors)\n");
309else for (int i = 0; i < SPF_response_messages(spf_response); i++)
310 {
311 SPF_error_t * err = SPF_response_message(spf_response, i);
312 debug_printf( "%s_msg = (%d) %s\n",
313 (SPF_error_errorp(err) ? "warn" : "err"),
314 SPF_error_code(err),
315 SPF_error_message(err));
316 }
317}
318
319
8523533c
TK
320/* spf_process adds the envelope sender address to the existing
321 context (if any), retrieves the result, sets up expansion
eb52e2cb 322 strings and evaluates the condition outcome.
f9ba5e22 323
eb52e2cb
JH
324Return: OK/FAIL */
325
326int
327spf_process(const uschar **listptr, uschar *spf_envelope_sender, int action)
328{
329int sep = 0;
330const uschar *list = *listptr;
331uschar *spf_result_id;
332int rc = SPF_RESULT_PERMERROR;
333
b8e97684
JH
334DEBUG(D_receive) debug_printf("spf_process\n");
335
eb52e2cb
JH
336if (!(spf_server && spf_request))
337 /* no global context, assume temp error and skip to evaluation */
338 rc = SPF_RESULT_PERMERROR;
339
340else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender))
aded2255 341 /* Invalid sender address. This should be a real rare occurrence */
eb52e2cb
JH
342 rc = SPF_RESULT_PERMERROR;
343
344else
345 {
8523533c 346 /* get SPF result */
65a7d8c3 347 if (action == SPF_PROCESS_FALLBACK)
87e9d061 348 {
7156b1ef 349 SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
87e9d061
JH
350 spf_result_guessed = TRUE;
351 }
65a7d8c3
NM
352 else
353 SPF_request_query_mailfrom(spf_request, &spf_response);
8523533c
TK
354
355 /* set up expansion items */
5903c6ff
JH
356 spf_header_comment = US SPF_response_get_header_comment(spf_response);
357 spf_received = US SPF_response_get_received_spf(spf_response);
358 spf_result = US SPF_strresult(SPF_response_result(spf_response));
359 spf_smtp_comment = US SPF_response_get_smtp_comment(spf_response);
8523533c 360
384152a6 361 rc = SPF_response_result(spf_response);
53ef3d84
JH
362
363 DEBUG(D_acl) spf_response_debug(spf_response);
eb52e2cb
JH
364 }
365
366/* We got a result. Now see if we should return OK or FAIL for it */
367DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
368
369if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
370 return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
371
372while ((spf_result_id = string_nextinlist(&list, &sep, NULL, 0)))
373 {
374 BOOL negate, result;
375
376 if ((negate = spf_result_id[0] == '!'))
377 spf_result_id++;
378
379 result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name) == 0;
380 if (negate != result) return OK;
381 }
8523533c 382
eb52e2cb
JH
383/* no match */
384return FAIL;
8523533c
TK
385}
386
dfbcb5ac
JH
387
388
389gstring *
390authres_spf(gstring * g)
391{
87e9d061 392uschar * s;
dfbcb5ac
JH
393if (!spf_result) return g;
394
87e9d061
JH
395g = string_append(g, 2, US";\n\tspf=", spf_result);
396if (spf_result_guessed)
397 g = string_cat(g, US" (best guess record for domain)");
398
399s = expand_string(US"$sender_address_domain");
400return s && *s
401 ? string_append(g, 2, US" smtp.mailfrom=", s)
402 : string_cat(g, US" smtp.mailfrom=<>");
dfbcb5ac
JH
403}
404
405
8523533c 406#endif