SPF: support uppercase in v=spf1, permerror-on-multiple-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 - 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
60 DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", domain);
61
62 if (dns_lookup(dnsa, US domain, rr_type, NULL) == DNS_NOMATCH)
63 {
64 SPF_dns_rr_dup(&spfrr, spf_nxdomain);
65 return spfrr;
66 }
67
68 for (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
72 srr.num_rr = found;
73 srr.rr = store_malloc(sizeof(SPF_dns_rr_data_t) * found);
74 srr.herrno = h_errno,
75
76 found = 0;
77 for (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)
85 {
86 case T_MX:
87 s += 2; /* skip the MX precedence field */
88 case T_PTR:
89 {
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;
101
102 if (strncmpic(rr->data+1, US"v=spf1", 6) != 0)
103 {
104 HDEBUG(D_host_lookup) debug_printf("not an spf record\n");
105 continue;
106 }
107
108 for (int off = 0; off < rr->size; off += chunk_len)
109 {
110 if (!(chunk_len = s[off++])) break;
111 g = string_catn(g, s+off, chunk_len);
112 }
113 if (!g)
114 continue;
115 gstring_release_unused(g);
116 s = string_copy_malloc(string_from_gstring(g));
117 break;
118 }
119
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 }
128 }
129 DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", s);
130 srr.rr[found++] = (void *) s;
131 }
132
133 /* spfrr->rr must have been malloc()d for this */
134 SPF_dns_rr_dup(&spfrr, &srr);
135 return spfrr;
136 }
137
138
139
140 SPF_dns_server_t *
141 SPF_dns_exim_new(int debug)
142 {
143 SPF_dns_server_t * spf_dns_server = store_malloc(sizeof(SPF_dns_server_t));
144
145 DEBUG(D_receive) debug_printf("SPF_dns_exim_new\n");
146
147 memset(spf_dns_server, 0, sizeof(SPF_dns_server_t));
148 spf_dns_server->destroy = NULL;
149 spf_dns_server->lookup = SPF_dns_exim_lookup;
150 spf_dns_server->get_spf = NULL;
151 spf_dns_server->get_exp = NULL;
152 spf_dns_server->add_cache = NULL;
153 spf_dns_server->layer_below = NULL;
154 spf_dns_server->name = "exim";
155 spf_dns_server->debug = debug;
156
157 /* XXX This might have to return NO_DATA sometimes. */
158
159 spf_nxdomain = SPF_dns_rr_new_init(spf_dns_server,
160 "", ns_t_any, 24 * 60 * 60, HOST_NOT_FOUND);
161 if (!spf_nxdomain)
162 {
163 free(spf_dns_server);
164 return NULL;
165 }
166
167 return spf_dns_server;
168 }
169
170
171
172
173 /* Construct the SPF library stack.
174 Return: Boolean success.
175 */
176
177 BOOL
178 spf_init(void)
179 {
180 SPF_dns_server_t * dc;
181 int debug = 0;
182
183 DEBUG(D_receive) debug = 1;
184
185 /* We insert our own DNS access layer rather than letting the spf library
186 do it, so that our dns access path is used for debug tracing and for the
187 testsuite. */
188
189 if (!(dc = SPF_dns_exim_new(debug)))
190 {
191 DEBUG(D_receive) debug_printf("spf: SPF_dns_exim_new() failed\n");
192 return FALSE;
193 }
194 if (!(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 }
199 if (!(spf_server = SPF_server_new_dns(dc, debug)))
200 {
201 DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n");
202 return FALSE;
203 }
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
210 return 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
218 Return: Boolean success
219 */
220
221 BOOL
222 spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr)
223 {
224 DEBUG(D_receive)
225 debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);
226
227 if (!spf_server && !spf_init()) return FALSE;
228
229 if (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;
235 }
236
237 spf_request = SPF_request_new(spf_server);
238
239 if ( 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;
249 }
250
251 if (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;
258 }
259
260 return TRUE;
261 }
262
263
264 /* spf_process adds the envelope sender address to the existing
265 context (if any), retrieves the result, sets up expansion
266 strings and evaluates the condition outcome.
267
268 Return: OK/FAIL */
269
270 int
271 spf_process(const uschar **listptr, uschar *spf_envelope_sender, int action)
272 {
273 int sep = 0;
274 const uschar *list = *listptr;
275 uschar *spf_result_id;
276 int rc = SPF_RESULT_PERMERROR;
277
278 DEBUG(D_receive) debug_printf("spf_process\n");
279
280 if (!(spf_server && spf_request))
281 /* no global context, assume temp error and skip to evaluation */
282 rc = SPF_RESULT_PERMERROR;
283
284 else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender))
285 /* Invalid sender address. This should be a real rare occurrence */
286 rc = SPF_RESULT_PERMERROR;
287
288 else
289 {
290 /* get SPF result */
291 if (action == SPF_PROCESS_FALLBACK)
292 {
293 SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
294 spf_result_guessed = TRUE;
295 }
296 else
297 SPF_request_query_mailfrom(spf_request, &spf_response);
298
299 /* set up expansion items */
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);
304
305 rc = SPF_response_result(spf_response);
306 }
307
308 /* We got a result. Now see if we should return OK or FAIL for it */
309 DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
310
311 if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
312 return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
313
314 while ((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 }
324
325 /* no match */
326 return FAIL;
327 }
328
329
330
331 gstring *
332 authres_spf(gstring * g)
333 {
334 uschar * s;
335 if (!spf_result) return g;
336
337 g = string_append(g, 2, US";\n\tspf=", spf_result);
338 if (spf_result_guessed)
339 g = string_cat(g, US" (best guess record for domain)");
340
341 s = expand_string(US"$sender_address_domain");
342 return s && *s
343 ? string_append(g, 2, US" smtp.mailfrom=", s)
344 : string_cat(g, US" smtp.mailfrom=<>");
345 }
346
347
348 #endif