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