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