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