GSASL: provide $autnN for scram option expansions
[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 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_FAIL:
81 default: srr.herrno = NO_RECOVERY; break;
82 }
83
84 for (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
88 if (found == 0)
89 {
90 SPF_dns_rr_dup(&spfrr, &srr);
91 return spfrr;
92 }
93
94 srr.rr = store_malloc(sizeof(SPF_dns_rr_data_t) * found);
95
96 found = 0;
97 for (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)
105 {
106 case T_MX:
107 s += 2; /* skip the MX precedence field */
108 case T_PTR:
109 {
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;
121
122 if (strncmpic(rr->data+1, US SPF_VER_STR, 6) != 0)
123 {
124 HDEBUG(D_host_lookup) debug_printf("not an spf record\n");
125 continue;
126 }
127
128 for (int off = 0; off < rr->size; off += chunk_len)
129 {
130 if (!(chunk_len = s[off++])) break;
131 g = string_catn(g, s+off, chunk_len);
132 }
133 if (!g)
134 continue;
135 gstring_release_unused(g);
136 s = string_copy_malloc(string_from_gstring(g));
137 break;
138 }
139
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 }
148 }
149 DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", s);
150 srr.rr[found++] = (void *) s;
151 }
152
153 srr.num_rr = found;
154 /* spfrr->rr must have been malloc()d for this */
155 SPF_dns_rr_dup(&spfrr, &srr);
156 return spfrr;
157 }
158
159
160
161 SPF_dns_server_t *
162 SPF_dns_exim_new(int debug)
163 {
164 SPF_dns_server_t * spf_dns_server = store_malloc(sizeof(SPF_dns_server_t));
165
166 DEBUG(D_receive) debug_printf("SPF_dns_exim_new\n");
167
168 memset(spf_dns_server, 0, sizeof(SPF_dns_server_t));
169 spf_dns_server->destroy = NULL;
170 spf_dns_server->lookup = SPF_dns_exim_lookup;
171 spf_dns_server->get_spf = NULL;
172 spf_dns_server->get_exp = NULL;
173 spf_dns_server->add_cache = NULL;
174 spf_dns_server->layer_below = NULL;
175 spf_dns_server->name = "exim";
176 spf_dns_server->debug = debug;
177
178 /* XXX This might have to return NO_DATA sometimes. */
179
180 spf_nxdomain = SPF_dns_rr_new_init(spf_dns_server,
181 "", ns_t_any, 24 * 60 * 60, HOST_NOT_FOUND);
182 if (!spf_nxdomain)
183 {
184 free(spf_dns_server);
185 return NULL;
186 }
187
188 return spf_dns_server;
189 }
190
191
192
193
194 /* Construct the SPF library stack.
195 Return: Boolean success.
196 */
197
198 BOOL
199 spf_init(void)
200 {
201 SPF_dns_server_t * dc;
202 int debug = 0;
203
204 DEBUG(D_receive) debug = 1;
205
206 /* We insert our own DNS access layer rather than letting the spf library
207 do it, so that our dns access path is used for debug tracing and for the
208 testsuite. */
209
210 if (!(dc = SPF_dns_exim_new(debug)))
211 {
212 DEBUG(D_receive) debug_printf("spf: SPF_dns_exim_new() failed\n");
213 return FALSE;
214 }
215 if (!(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 }
220 if (!(spf_server = SPF_server_new_dns(dc, debug)))
221 {
222 DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n");
223 return FALSE;
224 }
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
231 return 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
239 Return: Boolean success
240 */
241
242 BOOL
243 spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr)
244 {
245 DEBUG(D_receive)
246 debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);
247
248 if (!spf_server && !spf_init()) return FALSE;
249
250 if (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;
256 }
257
258 spf_request = SPF_request_new(spf_server);
259
260 if ( 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;
270 }
271
272 if (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;
279 }
280
281 return TRUE;
282 }
283
284
285 /* spf_process adds the envelope sender address to the existing
286 context (if any), retrieves the result, sets up expansion
287 strings and evaluates the condition outcome.
288
289 Return: OK/FAIL */
290
291 int
292 spf_process(const uschar **listptr, uschar *spf_envelope_sender, int action)
293 {
294 int sep = 0;
295 const uschar *list = *listptr;
296 uschar *spf_result_id;
297 int rc = SPF_RESULT_PERMERROR;
298
299 DEBUG(D_receive) debug_printf("spf_process\n");
300
301 if (!(spf_server && spf_request))
302 /* no global context, assume temp error and skip to evaluation */
303 rc = SPF_RESULT_PERMERROR;
304
305 else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender))
306 /* Invalid sender address. This should be a real rare occurrence */
307 rc = SPF_RESULT_PERMERROR;
308
309 else
310 {
311 /* get SPF result */
312 if (action == SPF_PROCESS_FALLBACK)
313 {
314 SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
315 spf_result_guessed = TRUE;
316 }
317 else
318 SPF_request_query_mailfrom(spf_request, &spf_response);
319
320 /* set up expansion items */
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);
325
326 rc = SPF_response_result(spf_response);
327 }
328
329 /* We got a result. Now see if we should return OK or FAIL for it */
330 DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
331
332 if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
333 return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
334
335 while ((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 }
345
346 /* no match */
347 return FAIL;
348 }
349
350
351
352 gstring *
353 authres_spf(gstring * g)
354 {
355 uschar * s;
356 if (!spf_result) return g;
357
358 g = string_append(g, 2, US";\n\tspf=", spf_result);
359 if (spf_result_guessed)
360 g = string_cat(g, US" (best guess record for domain)");
361
362 s = expand_string(US"$sender_address_domain");
363 return s && *s
364 ? string_append(g, 2, US" smtp.mailfrom=", s)
365 : string_cat(g, US" smtp.mailfrom=<>");
366 }
367
368
369 #endif