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