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