SPF: fix handling mix of spf and other txt records. 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,
bf4d7837 40 const char *domain, ns_type rr_type, int should_cache)
b8e97684 41{
8743d3ac 42dns_answer * dnsa = store_get_dns_answer();
b8e97684
JH
43dns_scan dnss;
44SPF_dns_rr_t * spfrr;
bf4d7837
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};
59int dns_rc;
b8e97684 60
1bfc75b1 61DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", domain);
b8e97684 62
bf4d7837
JH
63switch (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
73for (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
77if (found == 0)
78 {
79 SPF_dns_rr_dup(&spfrr, &srr);
80 return spfrr;
81 }
82
83srr.rr = store_malloc(sizeof(SPF_dns_rr_data_t) * found);
84
85found = 0;
86for (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)
b8e97684 94 {
bf4d7837
JH
95 case T_MX:
96 s += 2; /* skip the MX precedence field */
97 case T_PTR:
b8e97684 98 {
bf4d7837
JH
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 }
1bfc75b1 105
bf4d7837
JH
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)
1bfc75b1 112 {
bf4d7837
JH
113 HDEBUG(D_host_lookup) debug_printf("not an spf record\n");
114 continue;
1bfc75b1
WB
115 }
116
bf4d7837 117 for (int off = 0; off < rr->size; off += chunk_len)
1bfc75b1 118 {
bf4d7837
JH
119 if (!(chunk_len = s[off++])) break;
120 g = string_catn(g, s+off, chunk_len);
1bfc75b1 121 }
bf4d7837
JH
122 if (!g)
123 continue;
124 gstring_release_unused(g);
125 s = string_copy_malloc(string_from_gstring(g));
126 break;
b8e97684 127 }
b8e97684 128
bf4d7837
JH
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 }
b8e97684 137 }
bf4d7837
JH
138 DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", s);
139 srr.rr[found++] = (void *) s;
140 }
b8e97684 141
bf4d7837
JH
142srr.num_rr = found;
143/* spfrr->rr must have been malloc()d for this */
144SPF_dns_rr_dup(&spfrr, &srr);
b8e97684
JH
145return spfrr;
146}
147
148
149
150SPF_dns_server_t *
151SPF_dns_exim_new(int debug)
152{
bf4d7837 153SPF_dns_server_t * spf_dns_server = store_malloc(sizeof(SPF_dns_server_t));
b8e97684
JH
154
155DEBUG(D_receive) debug_printf("SPF_dns_exim_new\n");
156
b8e97684 157memset(spf_dns_server, 0, sizeof(SPF_dns_server_t));
b8e97684
JH
158spf_dns_server->destroy = NULL;
159spf_dns_server->lookup = SPF_dns_exim_lookup;
160spf_dns_server->get_spf = NULL;
161spf_dns_server->get_exp = NULL;
162spf_dns_server->add_cache = NULL;
163spf_dns_server->layer_below = NULL;
164spf_dns_server->name = "exim";
165spf_dns_server->debug = debug;
166
167/* XXX This might have to return NO_DATA sometimes. */
168
169spf_nxdomain = SPF_dns_rr_new_init(spf_dns_server,
170 "", ns_t_any, 24 * 60 * 60, HOST_NOT_FOUND);
171if (!spf_nxdomain)
172 {
173 free(spf_dns_server);
174 return NULL;
175 }
176
177return spf_dns_server;
178}
179
180
eb52e2cb 181
8e669ac1 182
73ec116f
JH
183/* Construct the SPF library stack.
184 Return: Boolean success.
185*/
8e669ac1 186
eb52e2cb 187BOOL
73ec116f 188spf_init(void)
eb52e2cb 189{
b8e97684 190SPF_dns_server_t * dc;
73ec116f 191int debug = 0;
b8e97684 192
73ec116f 193DEBUG(D_receive) debug = 1;
b8e97684
JH
194
195/* We insert our own DNS access layer rather than letting the spf library
196do it, so that our dns access path is used for debug tracing and for the
197testsuite. */
8e669ac1 198
b8e97684
JH
199if (!(dc = SPF_dns_exim_new(debug)))
200 {
201 DEBUG(D_receive) debug_printf("spf: SPF_dns_exim_new() failed\n");
202 return FALSE;
203 }
204if (!(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 }
209if (!(spf_server = SPF_server_new_dns(dc, debug)))
eb52e2cb
JH
210 {
211 DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n");
212 return FALSE;
8523533c 213 }
05e4f4de
HSHR
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
73ec116f
JH
220return 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
228Return: Boolean success
229*/
230
231BOOL
232spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr)
233{
234DEBUG(D_receive)
235 debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);
236
237if (!spf_server && !spf_init()) return FALSE;
8523533c 238
eb52e2cb
JH
239if (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;
7b3a77e5
TK
245 }
246
eb52e2cb
JH
247spf_request = SPF_request_new(spf_server);
248
249if ( 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;
8523533c
TK
259 }
260
eb52e2cb
JH
261if (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;
8523533c 268 }
8e669ac1 269
eb52e2cb 270return TRUE;
8523533c
TK
271}
272
273
274/* spf_process adds the envelope sender address to the existing
275 context (if any), retrieves the result, sets up expansion
eb52e2cb 276 strings and evaluates the condition outcome.
f9ba5e22 277
eb52e2cb
JH
278Return: OK/FAIL */
279
280int
281spf_process(const uschar **listptr, uschar *spf_envelope_sender, int action)
282{
283int sep = 0;
284const uschar *list = *listptr;
285uschar *spf_result_id;
286int rc = SPF_RESULT_PERMERROR;
287
b8e97684
JH
288DEBUG(D_receive) debug_printf("spf_process\n");
289
eb52e2cb
JH
290if (!(spf_server && spf_request))
291 /* no global context, assume temp error and skip to evaluation */
292 rc = SPF_RESULT_PERMERROR;
293
294else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender))
aded2255 295 /* Invalid sender address. This should be a real rare occurrence */
eb52e2cb
JH
296 rc = SPF_RESULT_PERMERROR;
297
298else
299 {
8523533c 300 /* get SPF result */
65a7d8c3 301 if (action == SPF_PROCESS_FALLBACK)
87e9d061 302 {
7156b1ef 303 SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
87e9d061
JH
304 spf_result_guessed = TRUE;
305 }
65a7d8c3
NM
306 else
307 SPF_request_query_mailfrom(spf_request, &spf_response);
8523533c
TK
308
309 /* set up expansion items */
5903c6ff
JH
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);
8523533c 314
384152a6 315 rc = SPF_response_result(spf_response);
eb52e2cb
JH
316 }
317
318/* We got a result. Now see if we should return OK or FAIL for it */
319DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
320
321if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
322 return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
323
324while ((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 }
8523533c 334
eb52e2cb
JH
335/* no match */
336return FAIL;
8523533c
TK
337}
338
dfbcb5ac
JH
339
340
341gstring *
342authres_spf(gstring * g)
343{
87e9d061 344uschar * s;
dfbcb5ac
JH
345if (!spf_result) return g;
346
87e9d061
JH
347g = string_append(g, 2, US";\n\tspf=", spf_result);
348if (spf_result_guessed)
349 g = string_cat(g, US" (best guess record for domain)");
350
351s = expand_string(US"$sender_address_domain");
352return s && *s
353 ? string_append(g, 2, US" smtp.mailfrom=", s)
354 : string_cat(g, US" smtp.mailfrom=<>");
dfbcb5ac
JH
355}
356
357
8523533c 358#endif