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