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