ef55520b10041a5d010540aebb5ae528e0260efb
[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 - 2022
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         s += 2; /* skip the MX precedence field */
126       case T_PTR:
127         {
128         uschar * buf = store_malloc(256);
129         (void)dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, s,
130           (DN_EXPAND_ARG4_TYPE)buf, 256);
131         s = buf;
132         break;
133         }
134
135       case T_TXT:
136         {
137         gstring * g = NULL;
138         uschar chunk_len;
139
140         if (strncmpic(rr->data+1, US SPF_VER_STR, 6) != 0)
141           {
142           HDEBUG(D_host_lookup) debug_printf("not an spf record: %.*s\n",
143             (int) s[0], s+1);
144           continue;
145           }
146
147         for (int off = 0; off < rr->size; off += chunk_len)
148           {
149           if (!(chunk_len = s[off++])) break;
150           g = string_catn(g, s+off, chunk_len);
151           }
152         if (!g)
153           continue;
154         gstring_release_unused(g);
155         s = string_copy_malloc(string_from_gstring(g));
156         DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", s);
157         break;
158         }
159
160       case T_A:
161       case T_AAAA:
162       default:
163         {
164         uschar * buf = store_malloc(dnsa->answerlen + 1);
165         s = memcpy(buf, s, dnsa->answerlen + 1);
166         break;
167         }
168       }
169     srr.rr[found++] = (void *) s;
170     }
171
172 /* Did we filter out all TXT RRs? Return NO_DATA instead of SUCCESS with
173 empty ANSWER section. */
174
175 if (!(srr.num_rr = found))
176   srr.herrno = NO_DATA;
177
178 /* spfrr->rr must have been malloc()d for this */
179 SPF_dns_rr_dup(&spfrr, &srr);
180 store_free_dns_answer(dnsa);
181 return spfrr;
182 }
183
184
185
186 SPF_dns_server_t *
187 SPF_dns_exim_new(int debug)
188 {
189 SPF_dns_server_t * spf_dns_server = store_malloc(sizeof(SPF_dns_server_t));
190
191 DEBUG(D_receive) debug_printf("SPF_dns_exim_new\n");
192
193 memset(spf_dns_server, 0, sizeof(SPF_dns_server_t));
194 spf_dns_server->destroy      = NULL;
195 spf_dns_server->lookup       = SPF_dns_exim_lookup;
196 spf_dns_server->get_spf      = NULL;
197 spf_dns_server->get_exp      = NULL;
198 spf_dns_server->add_cache    = NULL;
199 spf_dns_server->layer_below  = NULL;
200 spf_dns_server->name         = "exim";
201 spf_dns_server->debug        = debug;
202
203 /* XXX This might have to return NO_DATA sometimes. */
204
205 spf_nxdomain = SPF_dns_rr_new_init(spf_dns_server,
206   "", ns_t_any, 24 * 60 * 60, HOST_NOT_FOUND);
207 if (!spf_nxdomain)
208   {
209   store_free(spf_dns_server);
210   return NULL;
211   }
212
213 return spf_dns_server;
214 }
215
216
217
218
219 /* Construct the SPF library stack.
220    Return: Boolean success.
221 */
222
223 BOOL
224 spf_init(void)
225 {
226 SPF_dns_server_t * dc;
227 int debug = 0;
228 const uschar *s;
229
230 DEBUG(D_receive) debug = 1;
231
232 /* We insert our own DNS access layer rather than letting the spf library
233 do it, so that our dns access path is used for debug tracing and for the
234 testsuite. */
235
236 if (!(dc = SPF_dns_exim_new(debug)))
237   {
238   DEBUG(D_receive) debug_printf("spf: SPF_dns_exim_new() failed\n");
239   return FALSE;
240   }
241 if (!(dc = SPF_dns_cache_new(dc, NULL, debug, 8)))
242   {
243   DEBUG(D_receive) debug_printf("spf: SPF_dns_cache_new() failed\n");
244   return FALSE;
245   }
246 if (!(spf_server = SPF_server_new_dns(dc, debug)))
247   {
248   DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n");
249   return FALSE;
250   }
251   /* Override the outdated explanation URL.
252   See https://www.mail-archive.com/mailop@mailop.org/msg08019.html
253   Used to work as "Please%_see%_http://www.open-spf.org/Why?id=%{S}&ip=%{C}&receiver=%{R}",
254   but is broken now (May 18th, 2020) */
255 if (!(s = expand_string(spf_smtp_comment_template)))
256   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "expansion of spf_smtp_comment_template failed");
257
258 SPF_server_set_explanation(spf_server, CCS s, &spf_response);
259 if (SPF_response_errcode(spf_response) != SPF_E_SUCCESS)
260   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", SPF_strerror(SPF_response_errcode(spf_response)));
261
262 return TRUE;
263 }
264
265
266 /* Set up a context that can be re-used for several
267    messages on the same SMTP connection (that come from the
268    same host with the same HELO string).
269
270 Return: Boolean success
271 */
272
273 BOOL
274 spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr)
275 {
276 DEBUG(D_receive)
277   debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);
278
279 if (!spf_server && !spf_init()) return FALSE;
280
281 if (SPF_server_set_rec_dom(spf_server, CS primary_hostname))
282   {
283   DEBUG(D_receive) debug_printf("spf: SPF_server_set_rec_dom(\"%s\") failed.\n",
284     primary_hostname);
285   spf_server = NULL;
286   return FALSE;
287   }
288
289 spf_request = SPF_request_new(spf_server);
290
291 if (  SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr)
292    && SPF_request_set_ipv6_str(spf_request, CS spf_remote_addr)
293    )
294   {
295   DEBUG(D_receive)
296     debug_printf("spf: SPF_request_set_ipv4_str() and "
297       "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr);
298   spf_server = NULL;
299   spf_request = NULL;
300   return FALSE;
301   }
302
303 if (SPF_request_set_helo_dom(spf_request, CS spf_helo_domain))
304   {
305   DEBUG(D_receive) debug_printf("spf: SPF_set_helo_dom(\"%s\") failed.\n",
306     spf_helo_domain);
307   spf_server = NULL;
308   spf_request = NULL;
309   return FALSE;
310   }
311
312 return TRUE;
313 }
314
315
316 void
317 spf_response_debug(SPF_response_t * spf_response)
318 {
319 if (SPF_response_messages(spf_response) == 0)
320   debug_printf(" (no errors)\n");
321 else for (int i = 0; i < SPF_response_messages(spf_response); i++)
322   {
323   SPF_error_t * err = SPF_response_message(spf_response, i);
324   debug_printf( "%s_msg = (%d) %s\n",
325                   (SPF_error_errorp(err) ? "warn" : "err"),
326                   SPF_error_code(err),
327                   SPF_error_message(err));
328   }
329 }
330
331
332 /* spf_process adds the envelope sender address to the existing
333    context (if any), retrieves the result, sets up expansion
334    strings and evaluates the condition outcome.
335
336 Return: OK/FAIL  */
337
338 int
339 spf_process(const uschar **listptr, uschar *spf_envelope_sender, int action)
340 {
341 int sep = 0;
342 const uschar *list = *listptr;
343 uschar *spf_result_id;
344 int rc = SPF_RESULT_PERMERROR;
345
346 DEBUG(D_receive) debug_printf("spf_process\n");
347
348 if (!(spf_server && spf_request))
349   /* no global context, assume temp error and skip to evaluation */
350   rc = SPF_RESULT_PERMERROR;
351
352 else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender))
353   /* Invalid sender address. This should be a real rare occurrence */
354   rc = SPF_RESULT_PERMERROR;
355
356 else
357   {
358   /* get SPF result */
359   if (action == SPF_PROCESS_FALLBACK)
360     {
361     SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
362     spf_result_guessed = TRUE;
363     }
364   else
365     SPF_request_query_mailfrom(spf_request, &spf_response);
366
367   /* set up expansion items */
368   spf_header_comment     = US SPF_response_get_header_comment(spf_response);
369   spf_received           = US SPF_response_get_received_spf(spf_response);
370   spf_result             = US SPF_strresult(SPF_response_result(spf_response));
371   spf_smtp_comment       = US SPF_response_get_smtp_comment(spf_response);
372
373   rc = SPF_response_result(spf_response);
374
375   DEBUG(D_acl) spf_response_debug(spf_response);
376   }
377
378 /* We got a result. Now see if we should return OK or FAIL for it */
379 DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
380
381 if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
382   return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
383
384 while ((spf_result_id = string_nextinlist(&list, &sep, NULL, 0)))
385   {
386   BOOL negate, result;
387
388   if ((negate = spf_result_id[0] == '!'))
389     spf_result_id++;
390
391   result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name) == 0;
392   if (negate != result) return OK;
393   }
394
395 /* no match */
396 return FAIL;
397 }
398
399
400
401 gstring *
402 authres_spf(gstring * g)
403 {
404 uschar * s;
405 if (spf_result)
406   {
407   int start = 0;                /* Compiler quietening */
408   DEBUG(D_acl) start = gstring_length(g);
409
410   g = string_append(g, 2, US";\n\tspf=", spf_result);
411   if (spf_result_guessed)
412     g = string_cat(g, US" (best guess record for domain)");
413
414   s = expand_string(US"$sender_address_domain");
415   if (s && *s)
416     g = string_append(g, 2, US" smtp.mailfrom=", s);
417   else
418     {
419     s = sender_helo_name;
420     g = s && *s
421       ? string_append(g, 2, US" smtp.helo=", s)
422       : string_cat(g, US" smtp.mailfrom=<>");
423     }
424   DEBUG(D_acl) debug_printf("SPF:\tauthres '%.*s'\n",
425                   gstring_length(g) - start - 3, g->s + start + 3);
426   }
427 else
428   DEBUG(D_acl) debug_printf("SPF:\tno authres\n");
429 return g;
430 }
431
432
433 #endif