SPF: additional debug
[exim.git] / src / src / spf.c
1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
4
5 /* SPF support.
6 Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 - 2014
7 License: GPL
8 Copyright (c) The Exim Maintainers 2015 - 2020
9 */
10
11 /* Code for calling spf checks via libspf-alt. Called from acl.c. */
12
13 #include "exim.h"
14 #ifdef SUPPORT_SPF
15
16 /* must be kept in numeric order */
17 static spf_result_id spf_result_id_list[] = {
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 },
25 { US"temperror", 6 }, /* RFC 4408 defined */
26 { US"permerror", 7 } /* RFC 4408 defined */
27 };
28
29 SPF_server_t *spf_server = NULL;
30 SPF_request_t *spf_request = NULL;
31 SPF_response_t *spf_response = NULL;
32 SPF_response_t *spf_response_2mx = NULL;
33
34 SPF_dns_rr_t * spf_nxdomain = NULL;
35
36
37 void
38 spf_lib_version_report(FILE * fp)
39 {
40 int maj, min, patch;
41 SPF_get_lib_version(&maj, &min, &patch);
42 fprintf(fp, "Library version: spf2: Compile: %d.%d.%d\n",
43 SPF_LIB_VERSION_MAJOR, SPF_LIB_VERSION_MINOR, SPF_LIB_VERSION_PATCH);
44 fprintf(fp, " Runtime: %d.%d.%d\n",
45 maj, min, patch);
46 }
47
48
49
50 static SPF_dns_rr_t *
51 SPF_dns_exim_lookup(SPF_dns_server_t *spf_dns_server,
52 const char *domain, ns_type rr_type, int should_cache)
53 {
54 dns_answer * dnsa = store_get_dns_answer();
55 dns_scan dnss;
56 SPF_dns_rr_t * spfrr;
57 unsigned found = 0;
58
59 SPF_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 };
71 int dns_rc;
72
73 DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", domain);
74
75 switch (dns_rc = dns_lookup(dnsa, US domain, rr_type, NULL))
76 {
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_NODATA: srr.herrno = NO_DATA; break;
81 case DNS_FAIL:
82 default: srr.herrno = NO_RECOVERY; break;
83 }
84
85 for (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
89 if (found == 0)
90 {
91 SPF_dns_rr_dup(&spfrr, &srr);
92 return spfrr;
93 }
94
95 srr.rr = store_malloc(sizeof(SPF_dns_rr_data_t) * found);
96
97 found = 0;
98 for (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)
106 {
107 case T_MX:
108 s += 2; /* skip the MX precedence field */
109 case T_PTR:
110 {
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;
122
123 if (strncmpic(rr->data+1, US SPF_VER_STR, 6) != 0)
124 {
125 HDEBUG(D_host_lookup) debug_printf("not an spf record: %.*s\n",
126 (int) s[0], s+1);
127 continue;
128 }
129
130 for (int off = 0; off < rr->size; off += chunk_len)
131 {
132 if (!(chunk_len = s[off++])) break;
133 g = string_catn(g, s+off, chunk_len);
134 }
135 if (!g)
136 continue;
137 gstring_release_unused(g);
138 s = string_copy_malloc(string_from_gstring(g));
139 DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", s);
140 break;
141 }
142
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 }
151 }
152 srr.rr[found++] = (void *) s;
153 }
154
155 srr.num_rr = found;
156 /* spfrr->rr must have been malloc()d for this */
157 SPF_dns_rr_dup(&spfrr, &srr);
158 return spfrr;
159 }
160
161
162
163 SPF_dns_server_t *
164 SPF_dns_exim_new(int debug)
165 {
166 SPF_dns_server_t * spf_dns_server = store_malloc(sizeof(SPF_dns_server_t));
167
168 DEBUG(D_receive) debug_printf("SPF_dns_exim_new\n");
169
170 memset(spf_dns_server, 0, sizeof(SPF_dns_server_t));
171 spf_dns_server->destroy = NULL;
172 spf_dns_server->lookup = SPF_dns_exim_lookup;
173 spf_dns_server->get_spf = NULL;
174 spf_dns_server->get_exp = NULL;
175 spf_dns_server->add_cache = NULL;
176 spf_dns_server->layer_below = NULL;
177 spf_dns_server->name = "exim";
178 spf_dns_server->debug = debug;
179
180 /* XXX This might have to return NO_DATA sometimes. */
181
182 spf_nxdomain = SPF_dns_rr_new_init(spf_dns_server,
183 "", ns_t_any, 24 * 60 * 60, HOST_NOT_FOUND);
184 if (!spf_nxdomain)
185 {
186 free(spf_dns_server);
187 return NULL;
188 }
189
190 return spf_dns_server;
191 }
192
193
194
195
196 /* Construct the SPF library stack.
197 Return: Boolean success.
198 */
199
200 BOOL
201 spf_init(void)
202 {
203 SPF_dns_server_t * dc;
204 int debug = 0;
205
206 DEBUG(D_receive) debug = 1;
207
208 /* We insert our own DNS access layer rather than letting the spf library
209 do it, so that our dns access path is used for debug tracing and for the
210 testsuite. */
211
212 if (!(dc = SPF_dns_exim_new(debug)))
213 {
214 DEBUG(D_receive) debug_printf("spf: SPF_dns_exim_new() failed\n");
215 return FALSE;
216 }
217 if (!(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 }
222 if (!(spf_server = SPF_server_new_dns(dc, debug)))
223 {
224 DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n");
225 return FALSE;
226 }
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
233 return 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
241 Return: Boolean success
242 */
243
244 BOOL
245 spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr)
246 {
247 DEBUG(D_receive)
248 debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);
249
250 if (!spf_server && !spf_init()) return FALSE;
251
252 if (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;
258 }
259
260 spf_request = SPF_request_new(spf_server);
261
262 if ( 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;
272 }
273
274 if (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;
281 }
282
283 return TRUE;
284 }
285
286
287 void
288 spf_response_debug(SPF_response_t * spf_response)
289 {
290 if (SPF_response_messages(spf_response) == 0)
291 debug_printf(" (no errors)\n");
292 else 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
303 /* spf_process adds the envelope sender address to the existing
304 context (if any), retrieves the result, sets up expansion
305 strings and evaluates the condition outcome.
306
307 Return: OK/FAIL */
308
309 int
310 spf_process(const uschar **listptr, uschar *spf_envelope_sender, int action)
311 {
312 int sep = 0;
313 const uschar *list = *listptr;
314 uschar *spf_result_id;
315 int rc = SPF_RESULT_PERMERROR;
316
317 DEBUG(D_receive) debug_printf("spf_process\n");
318
319 if (!(spf_server && spf_request))
320 /* no global context, assume temp error and skip to evaluation */
321 rc = SPF_RESULT_PERMERROR;
322
323 else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender))
324 /* Invalid sender address. This should be a real rare occurrence */
325 rc = SPF_RESULT_PERMERROR;
326
327 else
328 {
329 /* get SPF result */
330 if (action == SPF_PROCESS_FALLBACK)
331 {
332 SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
333 spf_result_guessed = TRUE;
334 }
335 else
336 SPF_request_query_mailfrom(spf_request, &spf_response);
337
338 /* set up expansion items */
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);
343
344 rc = SPF_response_result(spf_response);
345
346 DEBUG(D_acl) spf_response_debug(spf_response);
347 }
348
349 /* We got a result. Now see if we should return OK or FAIL for it */
350 DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
351
352 if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
353 return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
354
355 while ((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 }
365
366 /* no match */
367 return FAIL;
368 }
369
370
371
372 gstring *
373 authres_spf(gstring * g)
374 {
375 uschar * s;
376 if (!spf_result) return g;
377
378 g = string_append(g, 2, US";\n\tspf=", spf_result);
379 if (spf_result_guessed)
380 g = string_cat(g, US" (best guess record for domain)");
381
382 s = expand_string(US"$sender_address_domain");
383 return s && *s
384 ? string_append(g, 2, US" smtp.mailfrom=", s)
385 : string_cat(g, US" smtp.mailfrom=<>");
386 }
387
388
389 #endif