DNS: use tainted memory for all lookups
[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 /* spf_init sets up a context that can be re-used for several
137 messages on the same SMTP connection (that come from the
138 same host with the same HELO string).
139 XXX the spf_server layer could usefully be separately init'd
140 given that it sets up a dns cache.
141
142 Return: Boolean success */
143
144 BOOL
145 spf_init(uschar *spf_helo_domain, uschar *spf_remote_addr)
146 {
147 int debug = 0;
148 SPF_dns_server_t * dc;
149
150 DEBUG(D_receive)
151 {
152 debug_printf("spf_init: %s %s\n", spf_helo_domain, spf_remote_addr);
153 debug = 1;
154 }
155
156 /* We insert our own DNS access layer rather than letting the spf library
157 do it, so that our dns access path is used for debug tracing and for the
158 testsuite. */
159
160 if (!(dc = SPF_dns_exim_new(debug)))
161 {
162 DEBUG(D_receive) debug_printf("spf: SPF_dns_exim_new() failed\n");
163 return FALSE;
164 }
165 if (!(dc = SPF_dns_cache_new(dc, NULL, debug, 8)))
166 {
167 DEBUG(D_receive) debug_printf("spf: SPF_dns_cache_new() failed\n");
168 return FALSE;
169 }
170 if (!(spf_server = SPF_server_new_dns(dc, debug)))
171 {
172 DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n");
173 return FALSE;
174 }
175
176 if (SPF_server_set_rec_dom(spf_server, CS primary_hostname))
177 {
178 DEBUG(D_receive) debug_printf("spf: SPF_server_set_rec_dom(\"%s\") failed.\n",
179 primary_hostname);
180 spf_server = NULL;
181 return FALSE;
182 }
183
184 spf_request = SPF_request_new(spf_server);
185
186 if ( SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr)
187 && SPF_request_set_ipv6_str(spf_request, CS spf_remote_addr)
188 )
189 {
190 DEBUG(D_receive)
191 debug_printf("spf: SPF_request_set_ipv4_str() and "
192 "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr);
193 spf_server = NULL;
194 spf_request = NULL;
195 return FALSE;
196 }
197
198 if (SPF_request_set_helo_dom(spf_request, CS spf_helo_domain))
199 {
200 DEBUG(D_receive) debug_printf("spf: SPF_set_helo_dom(\"%s\") failed.\n",
201 spf_helo_domain);
202 spf_server = NULL;
203 spf_request = NULL;
204 return FALSE;
205 }
206
207 return TRUE;
208 }
209
210
211 /* spf_process adds the envelope sender address to the existing
212 context (if any), retrieves the result, sets up expansion
213 strings and evaluates the condition outcome.
214
215 Return: OK/FAIL */
216
217 int
218 spf_process(const uschar **listptr, uschar *spf_envelope_sender, int action)
219 {
220 int sep = 0;
221 const uschar *list = *listptr;
222 uschar *spf_result_id;
223 int rc = SPF_RESULT_PERMERROR;
224
225 DEBUG(D_receive) debug_printf("spf_process\n");
226
227 if (!(spf_server && spf_request))
228 /* no global context, assume temp error and skip to evaluation */
229 rc = SPF_RESULT_PERMERROR;
230
231 else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender))
232 /* Invalid sender address. This should be a real rare occurrence */
233 rc = SPF_RESULT_PERMERROR;
234
235 else
236 {
237 /* get SPF result */
238 if (action == SPF_PROCESS_FALLBACK)
239 {
240 SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
241 spf_result_guessed = TRUE;
242 }
243 else
244 SPF_request_query_mailfrom(spf_request, &spf_response);
245
246 /* set up expansion items */
247 spf_header_comment = US SPF_response_get_header_comment(spf_response);
248 spf_received = US SPF_response_get_received_spf(spf_response);
249 spf_result = US SPF_strresult(SPF_response_result(spf_response));
250 spf_smtp_comment = US SPF_response_get_smtp_comment(spf_response);
251
252 rc = SPF_response_result(spf_response);
253 }
254
255 /* We got a result. Now see if we should return OK or FAIL for it */
256 DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
257
258 if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
259 return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
260
261 while ((spf_result_id = string_nextinlist(&list, &sep, NULL, 0)))
262 {
263 BOOL negate, result;
264
265 if ((negate = spf_result_id[0] == '!'))
266 spf_result_id++;
267
268 result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name) == 0;
269 if (negate != result) return OK;
270 }
271
272 /* no match */
273 return FAIL;
274 }
275
276
277
278 gstring *
279 authres_spf(gstring * g)
280 {
281 uschar * s;
282 if (!spf_result) return g;
283
284 g = string_append(g, 2, US";\n\tspf=", spf_result);
285 if (spf_result_guessed)
286 g = string_cat(g, US" (best guess record for domain)");
287
288 s = expand_string(US"$sender_address_domain");
289 return s && *s
290 ? string_append(g, 2, US" smtp.mailfrom=", s)
291 : string_cat(g, US" smtp.mailfrom=<>");
292 }
293
294
295 #endif