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