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