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