SPF: handle DNS NO_DATA return. Bug 2499
[exim.git] / src / src / spf.c
1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
4
5 /* SPF support.
6 Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 - 2014
7 License: GPL
8 Copyright (c) The Exim Maintainers 2015 - 2019
9 */
10
11 /* Code for calling spf checks via libspf-alt. Called from acl.c. */
12
13 #include "exim.h"
14 #ifdef SUPPORT_SPF
15
16 /* must be kept in numeric order */
17 static spf_result_id spf_result_id_list[] = {
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 },
25 { US"temperror", 6 }, /* RFC 4408 defined */
26 { US"permerror", 7 } /* RFC 4408 defined */
27 };
28
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;
33
34 SPF_dns_rr_t * spf_nxdomain = NULL;
35
36
37 void
38 spf_lib_version_report(FILE * fp)
39 {
40 int maj, min, patch;
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",
45 maj, min, patch);
46 }
47
48
49
50 static SPF_dns_rr_t *
51 SPF_dns_exim_lookup(SPF_dns_server_t *spf_dns_server,
52 const char *domain, ns_type rr_type, int should_cache)
53 {
54 dns_answer * dnsa = store_get_dns_answer();
55 dns_scan dnss;
56 SPF_dns_rr_t * spfrr;
57 unsigned found = 0;
58
59 SPF_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 };
71 int dns_rc;
72
73 DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", domain);
74
75 switch (dns_rc = dns_lookup(dnsa, US domain, rr_type, NULL))
76 {
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;
81 case DNS_FAIL:
82 default: srr.herrno = NO_RECOVERY; break;
83 }
84
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++;
88
89 if (found == 0)
90 {
91 SPF_dns_rr_dup(&spfrr, &srr);
92 return spfrr;
93 }
94
95 srr.rr = store_malloc(sizeof(SPF_dns_rr_data_t) * found);
96
97 found = 0;
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)
101 {
102 const uschar * s = rr->data;
103
104 srr.ttl = rr->ttl;
105 switch(rr_type)
106 {
107 case T_MX:
108 s += 2; /* skip the MX precedence field */
109 case T_PTR:
110 {
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;
122
123 if (strncmpic(rr->data+1, US SPF_VER_STR, 6) != 0)
124 {
125 HDEBUG(D_host_lookup) debug_printf("not an spf record\n");
126 continue;
127 }
128
129 for (int off = 0; off < rr->size; off += chunk_len)
130 {
131 if (!(chunk_len = s[off++])) break;
132 g = string_catn(g, s+off, chunk_len);
133 }
134 if (!g)
135 continue;
136 gstring_release_unused(g);
137 s = string_copy_malloc(string_from_gstring(g));
138 break;
139 }
140
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 }
149 }
150 DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", s);
151 srr.rr[found++] = (void *) s;
152 }
153
154 srr.num_rr = found;
155 /* spfrr->rr must have been malloc()d for this */
156 SPF_dns_rr_dup(&spfrr, &srr);
157 return spfrr;
158 }
159
160
161
162 SPF_dns_server_t *
163 SPF_dns_exim_new(int debug)
164 {
165 SPF_dns_server_t * spf_dns_server = store_malloc(sizeof(SPF_dns_server_t));
166
167 DEBUG(D_receive) debug_printf("SPF_dns_exim_new\n");
168
169 memset(spf_dns_server, 0, sizeof(SPF_dns_server_t));
170 spf_dns_server->destroy = NULL;
171 spf_dns_server->lookup = SPF_dns_exim_lookup;
172 spf_dns_server->get_spf = NULL;
173 spf_dns_server->get_exp = NULL;
174 spf_dns_server->add_cache = NULL;
175 spf_dns_server->layer_below = NULL;
176 spf_dns_server->name = "exim";
177 spf_dns_server->debug = debug;
178
179 /* XXX This might have to return NO_DATA sometimes. */
180
181 spf_nxdomain = SPF_dns_rr_new_init(spf_dns_server,
182 "", ns_t_any, 24 * 60 * 60, HOST_NOT_FOUND);
183 if (!spf_nxdomain)
184 {
185 free(spf_dns_server);
186 return NULL;
187 }
188
189 return spf_dns_server;
190 }
191
192
193
194
195 /* Construct the SPF library stack.
196 Return: Boolean success.
197 */
198
199 BOOL
200 spf_init(void)
201 {
202 SPF_dns_server_t * dc;
203 int debug = 0;
204
205 DEBUG(D_receive) debug = 1;
206
207 /* We insert our own DNS access layer rather than letting the spf library
208 do it, so that our dns access path is used for debug tracing and for the
209 testsuite. */
210
211 if (!(dc = SPF_dns_exim_new(debug)))
212 {
213 DEBUG(D_receive) debug_printf("spf: SPF_dns_exim_new() failed\n");
214 return FALSE;
215 }
216 if (!(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 }
221 if (!(spf_server = SPF_server_new_dns(dc, debug)))
222 {
223 DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n");
224 return FALSE;
225 }
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
232 return 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
240 Return: Boolean success
241 */
242
243 BOOL
244 spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr)
245 {
246 DEBUG(D_receive)
247 debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);
248
249 if (!spf_server && !spf_init()) return FALSE;
250
251 if (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;
257 }
258
259 spf_request = SPF_request_new(spf_server);
260
261 if ( 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;
271 }
272
273 if (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;
280 }
281
282 return TRUE;
283 }
284
285
286 /* spf_process adds the envelope sender address to the existing
287 context (if any), retrieves the result, sets up expansion
288 strings and evaluates the condition outcome.
289
290 Return: OK/FAIL */
291
292 int
293 spf_process(const uschar **listptr, uschar *spf_envelope_sender, int action)
294 {
295 int sep = 0;
296 const uschar *list = *listptr;
297 uschar *spf_result_id;
298 int rc = SPF_RESULT_PERMERROR;
299
300 DEBUG(D_receive) debug_printf("spf_process\n");
301
302 if (!(spf_server && spf_request))
303 /* no global context, assume temp error and skip to evaluation */
304 rc = SPF_RESULT_PERMERROR;
305
306 else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender))
307 /* Invalid sender address. This should be a real rare occurrence */
308 rc = SPF_RESULT_PERMERROR;
309
310 else
311 {
312 /* get SPF result */
313 if (action == SPF_PROCESS_FALLBACK)
314 {
315 SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
316 spf_result_guessed = TRUE;
317 }
318 else
319 SPF_request_query_mailfrom(spf_request, &spf_response);
320
321 /* set up expansion items */
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);
326
327 rc = SPF_response_result(spf_response);
328 }
329
330 /* We got a result. Now see if we should return OK or FAIL for it */
331 DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
332
333 if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
334 return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
335
336 while ((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 }
346
347 /* no match */
348 return FAIL;
349 }
350
351
352
353 gstring *
354 authres_spf(gstring * g)
355 {
356 uschar * s;
357 if (!spf_result) return g;
358
359 g = string_append(g, 2, US";\n\tspf=", spf_result);
360 if (spf_result_guessed)
361 g = string_cat(g, US" (best guess record for domain)");
362
363 s = expand_string(US"$sender_address_domain");
364 return s && *s
365 ? string_append(g, 2, US" smtp.mailfrom=", s)
366 : string_cat(g, US" smtp.mailfrom=<>");
367 }
368
369
370 #endif