SPF: fix result for case of only non-spf TXT RRs. 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 - 2020
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: %.*s\n",
126 (int) s[0], s+1);
127 continue;
128 }
129
130 for (int off = 0; off < rr->size; off += chunk_len)
131 {
132 if (!(chunk_len = s[off++])) break;
133 g = string_catn(g, s+off, chunk_len);
134 }
135 if (!g)
136 continue;
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);
140 break;
141 }
142
143 case T_A:
144 case T_AAAA:
145 default:
146 {
147 uschar * buf = store_malloc(dnsa->answerlen + 1);
148 s = memcpy(buf, s, dnsa->answerlen + 1);
149 break;
150 }
151 }
152 srr.rr[found++] = (void *) s;
153 }
154
155 /* Did we filter out all TXT RRs? Return NO_DATA instead of SUCCESS with
156 empty ANSWER section. */
157
158 if (!(srr.num_rr = found))
159 srr.herrno = NO_DATA;
160
161 /* spfrr->rr must have been malloc()d for this */
162 SPF_dns_rr_dup(&spfrr, &srr);
163 return spfrr;
164 }
165
166
167
168 SPF_dns_server_t *
169 SPF_dns_exim_new(int debug)
170 {
171 SPF_dns_server_t * spf_dns_server = store_malloc(sizeof(SPF_dns_server_t));
172
173 DEBUG(D_receive) debug_printf("SPF_dns_exim_new\n");
174
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;
184
185 /* XXX This might have to return NO_DATA sometimes. */
186
187 spf_nxdomain = SPF_dns_rr_new_init(spf_dns_server,
188 "", ns_t_any, 24 * 60 * 60, HOST_NOT_FOUND);
189 if (!spf_nxdomain)
190 {
191 free(spf_dns_server);
192 return NULL;
193 }
194
195 return spf_dns_server;
196 }
197
198
199
200
201 /* Construct the SPF library stack.
202 Return: Boolean success.
203 */
204
205 BOOL
206 spf_init(void)
207 {
208 SPF_dns_server_t * dc;
209 int debug = 0;
210
211 DEBUG(D_receive) debug = 1;
212
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
215 testsuite. */
216
217 if (!(dc = SPF_dns_exim_new(debug)))
218 {
219 DEBUG(D_receive) debug_printf("spf: SPF_dns_exim_new() failed\n");
220 return FALSE;
221 }
222 if (!(dc = SPF_dns_cache_new(dc, NULL, debug, 8)))
223 {
224 DEBUG(D_receive) debug_printf("spf: SPF_dns_cache_new() failed\n");
225 return FALSE;
226 }
227 if (!(spf_server = SPF_server_new_dns(dc, debug)))
228 {
229 DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n");
230 return FALSE;
231 }
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)));
237
238 return TRUE;
239 }
240
241
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).
245
246 Return: Boolean success
247 */
248
249 BOOL
250 spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr)
251 {
252 DEBUG(D_receive)
253 debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);
254
255 if (!spf_server && !spf_init()) return FALSE;
256
257 if (SPF_server_set_rec_dom(spf_server, CS primary_hostname))
258 {
259 DEBUG(D_receive) debug_printf("spf: SPF_server_set_rec_dom(\"%s\") failed.\n",
260 primary_hostname);
261 spf_server = NULL;
262 return FALSE;
263 }
264
265 spf_request = SPF_request_new(spf_server);
266
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)
269 )
270 {
271 DEBUG(D_receive)
272 debug_printf("spf: SPF_request_set_ipv4_str() and "
273 "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr);
274 spf_server = NULL;
275 spf_request = NULL;
276 return FALSE;
277 }
278
279 if (SPF_request_set_helo_dom(spf_request, CS spf_helo_domain))
280 {
281 DEBUG(D_receive) debug_printf("spf: SPF_set_helo_dom(\"%s\") failed.\n",
282 spf_helo_domain);
283 spf_server = NULL;
284 spf_request = NULL;
285 return FALSE;
286 }
287
288 return TRUE;
289 }
290
291
292 void
293 spf_response_debug(SPF_response_t * spf_response)
294 {
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++)
298 {
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"),
302 SPF_error_code(err),
303 SPF_error_message(err));
304 }
305 }
306
307
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.
311
312 Return: OK/FAIL */
313
314 int
315 spf_process(const uschar **listptr, uschar *spf_envelope_sender, int action)
316 {
317 int sep = 0;
318 const uschar *list = *listptr;
319 uschar *spf_result_id;
320 int rc = SPF_RESULT_PERMERROR;
321
322 DEBUG(D_receive) debug_printf("spf_process\n");
323
324 if (!(spf_server && spf_request))
325 /* no global context, assume temp error and skip to evaluation */
326 rc = SPF_RESULT_PERMERROR;
327
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;
331
332 else
333 {
334 /* get SPF result */
335 if (action == SPF_PROCESS_FALLBACK)
336 {
337 SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
338 spf_result_guessed = TRUE;
339 }
340 else
341 SPF_request_query_mailfrom(spf_request, &spf_response);
342
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);
348
349 rc = SPF_response_result(spf_response);
350
351 DEBUG(D_acl) spf_response_debug(spf_response);
352 }
353
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);
356
357 if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
358 return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
359
360 while ((spf_result_id = string_nextinlist(&list, &sep, NULL, 0)))
361 {
362 BOOL negate, result;
363
364 if ((negate = spf_result_id[0] == '!'))
365 spf_result_id++;
366
367 result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name) == 0;
368 if (negate != result) return OK;
369 }
370
371 /* no match */
372 return FAIL;
373 }
374
375
376
377 gstring *
378 authres_spf(gstring * g)
379 {
380 uschar * s;
381 if (!spf_result) return g;
382
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)");
386
387 s = expand_string(US"$sender_address_domain");
388 return s && *s
389 ? string_append(g, 2, US" smtp.mailfrom=", s)
390 : string_cat(g, US" smtp.mailfrom=<>");
391 }
392
393
394 #endif