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