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