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