e720517086a494cf4b8ccc1b6ed8f807081e05b9
[exim.git] / src / src / spf.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* SPF support.
6    Copyright (c) The Exim Maintainers 2015 - 2023
7    Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 - 2014
8    License: GPL
9    SPDX-License-Identifier: GPL-2.0-or-later
10 */
11
12 /* Code for calling spf checks via libspf-alt. Called from acl.c. */
13
14 #include "exim.h"
15 #ifdef SUPPORT_SPF
16
17 /* must be kept in numeric order */
18 static spf_result_id spf_result_id_list[] = {
19   /* name               value */
20   { US"invalid",        0},
21   { US"neutral",        1 },
22   { US"pass",           2 },
23   { US"fail",           3 },
24   { US"softfail",       4 },
25   { US"none",           5 },
26   { US"temperror",      6 }, /* RFC 4408 defined */
27   { US"permerror",      7 }  /* RFC 4408 defined */
28 };
29
30 SPF_server_t    *spf_server = NULL;
31 SPF_request_t   *spf_request = NULL;
32 SPF_response_t  *spf_response = NULL;
33 SPF_response_t  *spf_response_2mx = NULL;
34
35 SPF_dns_rr_t  * spf_nxdomain = NULL;
36
37
38 gstring *
39 spf_lib_version_report(gstring * g)
40 {
41 int maj, min, patch;
42
43 SPF_get_lib_version(&maj, &min, &patch);
44 g = string_fmt_append(g, "Library version: spf2: Compile: %d.%d.%d\n",
45         SPF_LIB_VERSION_MAJOR, SPF_LIB_VERSION_MINOR, SPF_LIB_VERSION_PATCH);
46 g = string_fmt_append(g,    "                       Runtime: %d.%d.%d\n",
47          maj, min, patch);
48 return g;
49 }
50
51
52
53 static SPF_dns_rr_t *
54 SPF_dns_exim_lookup(SPF_dns_server_t *spf_dns_server,
55   const char *domain, ns_type rr_type, int should_cache)
56 {
57 dns_answer * dnsa = store_get_dns_answer();
58 dns_scan dnss;
59 SPF_dns_rr_t * spfrr;
60 unsigned found = 0;
61
62 SPF_dns_rr_t srr = {
63   .domain = CS domain,                  /* query information */
64   .domain_buf_len = 0,
65   .rr_type = rr_type,
66
67   .rr_buf_len = 0,                      /* answer information */
68   .rr_buf_num = 0, /* no free of s */
69   .utc_ttl = 0,
70
71   .hook = NULL,                         /* misc information */
72   .source = spf_dns_server
73 };
74
75 DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", domain);
76
77 /* Shortcircuit SPF RR lookups by returning NO_DATA.  They were obsoleted by
78 RFC 6686/7208 years ago. see bug #1294 */
79
80 if (rr_type == T_SPF)
81   {
82   HDEBUG(D_host_lookup) debug_printf("faking NO_DATA for SPF RR(99) lookup\n");
83   srr.herrno = NO_DATA;
84   SPF_dns_rr_dup(&spfrr, &srr);
85   store_free_dns_answer(dnsa);
86   return spfrr;
87   }
88
89 switch (dns_lookup(dnsa, US domain, rr_type, NULL))
90   {
91   case DNS_AGAIN:       srr.herrno = TRY_AGAIN;         break;
92   case DNS_NOMATCH:     srr.herrno = HOST_NOT_FOUND;    break;
93   case DNS_NODATA:      srr.herrno = NO_DATA;           break;
94   case DNS_FAIL:
95   default:              srr.herrno = NO_RECOVERY;       break;
96   case DNS_SUCCEED:
97     srr.herrno = NETDB_SUCCESS;
98     for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
99          rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
100       /* Need to alloc space for all records, so no early-out */
101       if (rr->type == rr_type) found++;
102     break;
103   }
104
105 if (found == 0)
106   {
107   SPF_dns_rr_dup(&spfrr, &srr);
108   store_free_dns_answer(dnsa);
109   return spfrr;
110   }
111
112 srr.rr = store_malloc(sizeof(SPF_dns_rr_data_t) * found);
113
114 found = 0;
115 for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
116    rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
117   if (rr->type == rr_type)
118     {
119     const uschar * s = rr->data;
120
121     srr.ttl = rr->ttl;
122     switch(rr_type)
123       {
124       case T_MX:
125         if (rr->size < 2) continue;
126         s += 2; /* skip the MX precedence field */
127       case T_PTR:
128         {
129         uschar * buf = store_malloc(256);
130         (void)dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, s,
131           (DN_EXPAND_ARG4_TYPE)buf, 256);
132         s = buf;
133         break;
134         }
135
136       case T_TXT:
137         {
138         gstring * g = NULL;
139         uschar chunk_len;
140
141         if (rr->size < 1+6) continue;           /* min for version str */
142         if (strncmpic(rr->data+1, US SPF_VER_STR, 6) != 0)
143           {
144           HDEBUG(D_host_lookup) debug_printf("not an spf record: %.*s\n",
145             (int) s[0], s+1);
146           continue;
147           }
148
149         /* require 1 byte for the chunk_len */
150         for (int off = 0; off < rr->size - 1; off += chunk_len)
151           {
152           if (  !(chunk_len = s[off++])
153              || rr->size < off + chunk_len      /* ignore bogus size chunks */
154              ) break;
155           g = string_catn(g, s+off, chunk_len);
156           }
157         if (!g)
158           continue;
159         gstring_release_unused(g);
160         s = string_copy_malloc(string_from_gstring(g));
161         DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", s);
162         break;
163         }
164
165       case T_A:
166       case T_AAAA:
167       default:
168         {
169         uschar * buf = store_malloc(dnsa->answerlen + 1);
170         s = memcpy(buf, s, dnsa->answerlen + 1);
171         break;
172         }
173       }
174     srr.rr[found++] = (void *) s;
175     }
176
177 /* Did we filter out all TXT RRs? Return NO_DATA instead of SUCCESS with
178 empty ANSWER section. */
179
180 if (!(srr.num_rr = found))
181   srr.herrno = NO_DATA;
182
183 /* spfrr->rr must have been malloc()d for this */
184 SPF_dns_rr_dup(&spfrr, &srr);
185 store_free_dns_answer(dnsa);
186 return spfrr;
187 }
188
189
190
191 SPF_dns_server_t *
192 SPF_dns_exim_new(int debug)
193 {
194 SPF_dns_server_t * spf_dns_server = store_malloc(sizeof(SPF_dns_server_t));
195
196 DEBUG(D_receive) debug_printf("SPF_dns_exim_new\n");
197
198 memset(spf_dns_server, 0, sizeof(SPF_dns_server_t));
199 spf_dns_server->destroy      = NULL;
200 spf_dns_server->lookup       = SPF_dns_exim_lookup;
201 spf_dns_server->get_spf      = NULL;
202 spf_dns_server->get_exp      = NULL;
203 spf_dns_server->add_cache    = NULL;
204 spf_dns_server->layer_below  = NULL;
205 spf_dns_server->name         = "exim";
206 spf_dns_server->debug        = debug;
207
208 /* XXX This might have to return NO_DATA sometimes. */
209
210 spf_nxdomain = SPF_dns_rr_new_init(spf_dns_server,
211   "", ns_t_any, 24 * 60 * 60, HOST_NOT_FOUND);
212 if (!spf_nxdomain)
213   {
214   store_free(spf_dns_server);
215   return NULL;
216   }
217
218 return spf_dns_server;
219 }
220
221
222
223
224 /* Construct the SPF library stack.
225    Return: Boolean success.
226 */
227
228 BOOL
229 spf_init(void)
230 {
231 SPF_dns_server_t * dc;
232 int debug = 0;
233 const uschar *s;
234
235 DEBUG(D_receive) debug = 1;
236
237 /* We insert our own DNS access layer rather than letting the spf library
238 do it, so that our dns access path is used for debug tracing and for the
239 testsuite. */
240
241 if (!(dc = SPF_dns_exim_new(debug)))
242   {
243   DEBUG(D_receive) debug_printf("spf: SPF_dns_exim_new() failed\n");
244   return FALSE;
245   }
246 if (!(dc = SPF_dns_cache_new(dc, NULL, debug, 8)))
247   {
248   DEBUG(D_receive) debug_printf("spf: SPF_dns_cache_new() failed\n");
249   return FALSE;
250   }
251 if (!(spf_server = SPF_server_new_dns(dc, debug)))
252   {
253   DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n");
254   return FALSE;
255   }
256   /* Override the outdated explanation URL.
257   See https://www.mail-archive.com/mailop@mailop.org/msg08019.html
258   Used to work as "Please%_see%_http://www.open-spf.org/Why?id=%{S}&ip=%{C}&receiver=%{R}",
259   but is broken now (May 18th, 2020) */
260 if (!(s = expand_string(spf_smtp_comment_template)))
261   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "expansion of spf_smtp_comment_template failed");
262
263 SPF_server_set_explanation(spf_server, CCS s, &spf_response);
264 if (SPF_response_errcode(spf_response) != SPF_E_SUCCESS)
265   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", SPF_strerror(SPF_response_errcode(spf_response)));
266
267 return TRUE;
268 }
269
270
271 /* Set up a context that can be re-used for several
272    messages on the same SMTP connection (that come from the
273    same host with the same HELO string).
274
275 Return: Boolean success
276 */
277
278 BOOL
279 spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr)
280 {
281 DEBUG(D_receive)
282   debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);
283
284 if (!spf_server && !spf_init()) return FALSE;
285
286 if (SPF_server_set_rec_dom(spf_server, CS primary_hostname))
287   {
288   DEBUG(D_receive) debug_printf("spf: SPF_server_set_rec_dom(\"%s\") failed.\n",
289     primary_hostname);
290   spf_server = NULL;
291   return FALSE;
292   }
293
294 spf_request = SPF_request_new(spf_server);
295
296 if (  SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr)
297    && SPF_request_set_ipv6_str(spf_request, CS spf_remote_addr)
298    )
299   {
300   DEBUG(D_receive)
301     debug_printf("spf: SPF_request_set_ipv4_str() and "
302       "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr);
303   spf_server = NULL;
304   spf_request = NULL;
305   return FALSE;
306   }
307
308 if (SPF_request_set_helo_dom(spf_request, CS spf_helo_domain))
309   {
310   DEBUG(D_receive) debug_printf("spf: SPF_set_helo_dom(\"%s\") failed.\n",
311     spf_helo_domain);
312   spf_server = NULL;
313   spf_request = NULL;
314   return FALSE;
315   }
316
317 return TRUE;
318 }
319
320
321 void
322 spf_response_debug(SPF_response_t * spf_response)
323 {
324 if (SPF_response_messages(spf_response) == 0)
325   debug_printf(" (no errors)\n");
326 else for (int i = 0; i < SPF_response_messages(spf_response); i++)
327   {
328   SPF_error_t * err = SPF_response_message(spf_response, i);
329   debug_printf( "%s_msg = (%d) %s\n",
330                   (SPF_error_errorp(err) ? "warn" : "err"),
331                   SPF_error_code(err),
332                   SPF_error_message(err));
333   }
334 }
335
336
337 /* spf_process adds the envelope sender address to the existing
338    context (if any), retrieves the result, sets up expansion
339    strings and evaluates the condition outcome.
340
341 Return: OK/FAIL  */
342
343 int
344 spf_process(const uschar **listptr, uschar *spf_envelope_sender, int action)
345 {
346 int sep = 0;
347 const uschar *list = *listptr;
348 uschar *spf_result_id;
349 int rc = SPF_RESULT_PERMERROR;
350
351 DEBUG(D_receive) debug_printf("spf_process\n");
352
353 if (!(spf_server && spf_request))
354   /* no global context, assume temp error and skip to evaluation */
355   rc = SPF_RESULT_PERMERROR;
356
357 else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender))
358   /* Invalid sender address. This should be a real rare occurrence */
359   rc = SPF_RESULT_PERMERROR;
360
361 else
362   {
363   /* get SPF result */
364   if (action == SPF_PROCESS_FALLBACK)
365     {
366     SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
367     spf_result_guessed = TRUE;
368     }
369   else
370     SPF_request_query_mailfrom(spf_request, &spf_response);
371
372   /* set up expansion items */
373   spf_header_comment     = US SPF_response_get_header_comment(spf_response);
374   spf_received           = US SPF_response_get_received_spf(spf_response);
375   spf_result             = US SPF_strresult(SPF_response_result(spf_response));
376   spf_smtp_comment       = US SPF_response_get_smtp_comment(spf_response);
377
378   rc = SPF_response_result(spf_response);
379
380   DEBUG(D_acl) spf_response_debug(spf_response);
381   }
382
383 /* We got a result. Now see if we should return OK or FAIL for it */
384 DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
385
386 if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
387   return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
388
389 while ((spf_result_id = string_nextinlist(&list, &sep, NULL, 0)))
390   {
391   BOOL negate, result;
392
393   if ((negate = spf_result_id[0] == '!'))
394     spf_result_id++;
395
396   result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name) == 0;
397   if (negate != result) return OK;
398   }
399
400 /* no match */
401 return FAIL;
402 }
403
404
405
406 gstring *
407 authres_spf(gstring * g)
408 {
409 uschar * s;
410 if (spf_result)
411   {
412   int start = 0;                /* Compiler quietening */
413   DEBUG(D_acl) start = gstring_length(g);
414
415   g = string_append(g, 2, US";\n\tspf=", spf_result);
416   if (spf_result_guessed)
417     g = string_cat(g, US" (best guess record for domain)");
418
419   s = expand_string(US"$sender_address_domain");
420   if (s && *s)
421     g = string_append(g, 2, US" smtp.mailfrom=", s);
422   else
423     {
424     s = sender_helo_name;
425     g = s && *s
426       ? string_append(g, 2, US" smtp.helo=", s)
427       : string_cat(g, US" smtp.mailfrom=<>");
428     }
429   DEBUG(D_acl) debug_printf("SPF:\tauthres '%.*s'\n",
430                   gstring_length(g) - start - 3, g->s + start + 3);
431   }
432 else
433   DEBUG(D_acl) debug_printf("SPF:\tno authres\n");
434 return g;
435 }
436
437
438 #endif