SPF: handle DNS NO_DATA return. Bug 2499
[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
b8e97684 8 Copyright (c) The Exim Maintainers 2015 - 2019
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
44e90dfa 75switch (dns_rc = dns_lookup(dnsa, US domain, rr_type, NULL))
4533e306 76 {
44e90dfa
WB
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;
f73eb7e3 80 case DNS_NODATA: srr.herrno = NO_DATA; break;
44e90dfa
WB
81 case DNS_FAIL:
82 default: srr.herrno = NO_RECOVERY; break;
83 }
4533e306
JH
84
85for (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++;
88
44e90dfa
WB
89if (found == 0)
90 {
91 SPF_dns_rr_dup(&spfrr, &srr);
92 return spfrr;
93 }
94
4533e306 95srr.rr = store_malloc(sizeof(SPF_dns_rr_data_t) * found);
4533e306
JH
96
97found = 0;
98for (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)
101 {
102 const uschar * s = rr->data;
103
104 srr.ttl = rr->ttl;
105 switch(rr_type)
b8e97684 106 {
4533e306 107 case T_MX:
44e90dfa 108 s += 2; /* skip the MX precedence field */
4533e306 109 case T_PTR:
b8e97684 110 {
4533e306
JH
111 uschar * buf = store_malloc(256);
112 (void)dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, s,
113 (DN_EXPAND_ARG4_TYPE)buf, 256);
114 s = buf;
115 break;
116 }
117
118 case T_TXT:
119 {
120 gstring * g = NULL;
121 uschar chunk_len;
4a3709fb 122
85e453f6 123 if (strncmpic(rr->data+1, US SPF_VER_STR, 6) != 0)
4a3709fb 124 {
4533e306
JH
125 HDEBUG(D_host_lookup) debug_printf("not an spf record\n");
126 continue;
4a3709fb
WB
127 }
128
4533e306 129 for (int off = 0; off < rr->size; off += chunk_len)
4a3709fb 130 {
4533e306
JH
131 if (!(chunk_len = s[off++])) break;
132 g = string_catn(g, s+off, chunk_len);
4a3709fb 133 }
4533e306
JH
134 if (!g)
135 continue;
136 gstring_release_unused(g);
137 s = string_copy_malloc(string_from_gstring(g));
138 break;
b8e97684 139 }
b8e97684 140
4533e306
JH
141 case T_A:
142 case T_AAAA:
143 default:
144 {
145 uschar * buf = store_malloc(dnsa->answerlen + 1);
146 s = memcpy(buf, s, dnsa->answerlen + 1);
147 break;
148 }
b8e97684 149 }
4533e306
JH
150 DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", s);
151 srr.rr[found++] = (void *) s;
152 }
b8e97684 153
44e90dfa 154srr.num_rr = found;
4533e306
JH
155/* spfrr->rr must have been malloc()d for this */
156SPF_dns_rr_dup(&spfrr, &srr);
b8e97684
JH
157return spfrr;
158}
159
160
161
162SPF_dns_server_t *
163SPF_dns_exim_new(int debug)
164{
4533e306 165SPF_dns_server_t * spf_dns_server = store_malloc(sizeof(SPF_dns_server_t));
b8e97684
JH
166
167DEBUG(D_receive) debug_printf("SPF_dns_exim_new\n");
168
b8e97684 169memset(spf_dns_server, 0, sizeof(SPF_dns_server_t));
b8e97684
JH
170spf_dns_server->destroy = NULL;
171spf_dns_server->lookup = SPF_dns_exim_lookup;
172spf_dns_server->get_spf = NULL;
173spf_dns_server->get_exp = NULL;
174spf_dns_server->add_cache = NULL;
175spf_dns_server->layer_below = NULL;
176spf_dns_server->name = "exim";
177spf_dns_server->debug = debug;
178
179/* XXX This might have to return NO_DATA sometimes. */
180
181spf_nxdomain = SPF_dns_rr_new_init(spf_dns_server,
182 "", ns_t_any, 24 * 60 * 60, HOST_NOT_FOUND);
183if (!spf_nxdomain)
184 {
185 free(spf_dns_server);
186 return NULL;
187 }
188
189return spf_dns_server;
190}
191
192
eb52e2cb 193
8e669ac1 194
73ec116f
JH
195/* Construct the SPF library stack.
196 Return: Boolean success.
197*/
8e669ac1 198
eb52e2cb 199BOOL
73ec116f 200spf_init(void)
eb52e2cb 201{
b8e97684 202SPF_dns_server_t * dc;
73ec116f 203int debug = 0;
b8e97684 204
73ec116f 205DEBUG(D_receive) debug = 1;
b8e97684
JH
206
207/* We insert our own DNS access layer rather than letting the spf library
208do it, so that our dns access path is used for debug tracing and for the
209testsuite. */
8e669ac1 210
b8e97684
JH
211if (!(dc = SPF_dns_exim_new(debug)))
212 {
213 DEBUG(D_receive) debug_printf("spf: SPF_dns_exim_new() failed\n");
214 return FALSE;
215 }
216if (!(dc = SPF_dns_cache_new(dc, NULL, debug, 8)))
217 {
218 DEBUG(D_receive) debug_printf("spf: SPF_dns_cache_new() failed\n");
219 return FALSE;
220 }
221if (!(spf_server = SPF_server_new_dns(dc, debug)))
eb52e2cb
JH
222 {
223 DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n");
224 return FALSE;
8523533c 225 }
05e4f4de
HSHR
226 /* Quick hack to override the outdated explanation URL.
227 See https://www.mail-archive.com/mailop@mailop.org/msg08019.html */
228 SPF_server_set_explanation(spf_server, "Please%_see%_http://www.open-spf.org/Why?id=%{S}&ip=%{C}&receiver=%{R}", &spf_response);
229 if (SPF_response_errcode(spf_response) != SPF_E_SUCCESS)
230 log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", SPF_strerror(SPF_response_errcode(spf_response)));
231
73ec116f
JH
232return TRUE;
233}
234
235
236/* Set up a context that can be re-used for several
237 messages on the same SMTP connection (that come from the
238 same host with the same HELO string).
239
240Return: Boolean success
241*/
242
243BOOL
244spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr)
245{
246DEBUG(D_receive)
247 debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);
248
249if (!spf_server && !spf_init()) return FALSE;
8523533c 250
eb52e2cb
JH
251if (SPF_server_set_rec_dom(spf_server, CS primary_hostname))
252 {
253 DEBUG(D_receive) debug_printf("spf: SPF_server_set_rec_dom(\"%s\") failed.\n",
254 primary_hostname);
255 spf_server = NULL;
256 return FALSE;
7b3a77e5
TK
257 }
258
eb52e2cb
JH
259spf_request = SPF_request_new(spf_server);
260
261if ( SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr)
262 && SPF_request_set_ipv6_str(spf_request, CS spf_remote_addr)
263 )
264 {
265 DEBUG(D_receive)
266 debug_printf("spf: SPF_request_set_ipv4_str() and "
267 "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr);
268 spf_server = NULL;
269 spf_request = NULL;
270 return FALSE;
8523533c
TK
271 }
272
eb52e2cb
JH
273if (SPF_request_set_helo_dom(spf_request, CS spf_helo_domain))
274 {
275 DEBUG(D_receive) debug_printf("spf: SPF_set_helo_dom(\"%s\") failed.\n",
276 spf_helo_domain);
277 spf_server = NULL;
278 spf_request = NULL;
279 return FALSE;
8523533c 280 }
8e669ac1 281
eb52e2cb 282return TRUE;
8523533c
TK
283}
284
285
286/* spf_process adds the envelope sender address to the existing
287 context (if any), retrieves the result, sets up expansion
eb52e2cb 288 strings and evaluates the condition outcome.
f9ba5e22 289
eb52e2cb
JH
290Return: OK/FAIL */
291
292int
293spf_process(const uschar **listptr, uschar *spf_envelope_sender, int action)
294{
295int sep = 0;
296const uschar *list = *listptr;
297uschar *spf_result_id;
298int rc = SPF_RESULT_PERMERROR;
299
b8e97684
JH
300DEBUG(D_receive) debug_printf("spf_process\n");
301
eb52e2cb
JH
302if (!(spf_server && spf_request))
303 /* no global context, assume temp error and skip to evaluation */
304 rc = SPF_RESULT_PERMERROR;
305
306else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender))
aded2255 307 /* Invalid sender address. This should be a real rare occurrence */
eb52e2cb
JH
308 rc = SPF_RESULT_PERMERROR;
309
310else
311 {
8523533c 312 /* get SPF result */
65a7d8c3 313 if (action == SPF_PROCESS_FALLBACK)
87e9d061 314 {
7156b1ef 315 SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
87e9d061
JH
316 spf_result_guessed = TRUE;
317 }
65a7d8c3
NM
318 else
319 SPF_request_query_mailfrom(spf_request, &spf_response);
8523533c
TK
320
321 /* set up expansion items */
5903c6ff
JH
322 spf_header_comment = US SPF_response_get_header_comment(spf_response);
323 spf_received = US SPF_response_get_received_spf(spf_response);
324 spf_result = US SPF_strresult(SPF_response_result(spf_response));
325 spf_smtp_comment = US SPF_response_get_smtp_comment(spf_response);
8523533c 326
384152a6 327 rc = SPF_response_result(spf_response);
eb52e2cb
JH
328 }
329
330/* We got a result. Now see if we should return OK or FAIL for it */
331DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
332
333if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
334 return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
335
336while ((spf_result_id = string_nextinlist(&list, &sep, NULL, 0)))
337 {
338 BOOL negate, result;
339
340 if ((negate = spf_result_id[0] == '!'))
341 spf_result_id++;
342
343 result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name) == 0;
344 if (negate != result) return OK;
345 }
8523533c 346
eb52e2cb
JH
347/* no match */
348return FAIL;
8523533c
TK
349}
350
dfbcb5ac
JH
351
352
353gstring *
354authres_spf(gstring * g)
355{
87e9d061 356uschar * s;
dfbcb5ac
JH
357if (!spf_result) return g;
358
87e9d061
JH
359g = string_append(g, 2, US";\n\tspf=", spf_result);
360if (spf_result_guessed)
361 g = string_cat(g, US" (best guess record for domain)");
362
363s = expand_string(US"$sender_address_domain");
364return s && *s
365 ? string_append(g, 2, US" smtp.mailfrom=", s)
366 : string_cat(g, US" smtp.mailfrom=<>");
dfbcb5ac
JH
367}
368
369
8523533c 370#endif