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