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