Fix GNU/Hurd build. Bug 3044
[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
257 /* Override the outdated explanation URL.
258 See https://www.mail-archive.com/mailop@mailop.org/msg08019.html
259 Used to work as "Please%_see%_http://www.open-spf.org/Why?id=%{S}&ip=%{C}&receiver=%{R}",
260 but is broken now (May 18th, 2020) */
261
262 GET_OPTION("spf_smtp_comment_template");
263 if (!(s = expand_string(spf_smtp_comment_template)))
264   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "expansion of spf_smtp_comment_template failed");
265
266 SPF_server_set_explanation(spf_server, CCS s, &spf_response);
267 if (SPF_response_errcode(spf_response) != SPF_E_SUCCESS)
268   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", SPF_strerror(SPF_response_errcode(spf_response)));
269
270 return TRUE;
271 }
272
273
274 /* Set up a context that can be re-used for several
275    messages on the same SMTP connection (that come from the
276    same host with the same HELO string).
277
278 Return: Boolean success
279 */
280
281 BOOL
282 spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr)
283 {
284 DEBUG(D_receive)
285   debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);
286
287 if (!spf_server && !spf_init()) return FALSE;
288
289 if (SPF_server_set_rec_dom(spf_server, CS primary_hostname))
290   {
291   DEBUG(D_receive) debug_printf("spf: SPF_server_set_rec_dom(\"%s\") failed.\n",
292     primary_hostname);
293   spf_server = NULL;
294   return FALSE;
295   }
296
297 spf_request = SPF_request_new(spf_server);
298
299 if (  SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr)
300    && SPF_request_set_ipv6_str(spf_request, CS spf_remote_addr)
301    )
302   {
303   DEBUG(D_receive)
304     debug_printf("spf: SPF_request_set_ipv4_str() and "
305       "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr);
306   spf_server = NULL;
307   spf_request = NULL;
308   return FALSE;
309   }
310
311 if (SPF_request_set_helo_dom(spf_request, CS spf_helo_domain))
312   {
313   DEBUG(D_receive) debug_printf("spf: SPF_set_helo_dom(\"%s\") failed.\n",
314     spf_helo_domain);
315   spf_server = NULL;
316   spf_request = NULL;
317   return FALSE;
318   }
319
320 return TRUE;
321 }
322
323
324 void
325 spf_response_debug(SPF_response_t * spf_response)
326 {
327 if (SPF_response_messages(spf_response) == 0)
328   debug_printf(" (no errors)\n");
329 else for (int i = 0; i < SPF_response_messages(spf_response); i++)
330   {
331   SPF_error_t * err = SPF_response_message(spf_response, i);
332   debug_printf( "%s_msg = (%d) %s\n",
333                   (SPF_error_errorp(err) ? "warn" : "err"),
334                   SPF_error_code(err),
335                   SPF_error_message(err));
336   }
337 }
338
339
340 /* spf_process adds the envelope sender address to the existing
341    context (if any), retrieves the result, sets up expansion
342    strings and evaluates the condition outcome.
343
344 Return: OK/FAIL  */
345
346 int
347 spf_process(const uschar ** listptr, const uschar * spf_envelope_sender,
348   int action)
349 {
350 int sep = 0;
351 const uschar *list = *listptr;
352 uschar *spf_result_id;
353 int rc = SPF_RESULT_PERMERROR;
354
355 DEBUG(D_receive) debug_printf("spf_process\n");
356
357 if (!(spf_server && spf_request))
358   /* no global context, assume temp error and skip to evaluation */
359   rc = SPF_RESULT_PERMERROR;
360
361 else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender))
362   /* Invalid sender address. This should be a real rare occurrence */
363   rc = SPF_RESULT_PERMERROR;
364
365 else
366   {
367   /* get SPF result */
368   if (action == SPF_PROCESS_FALLBACK)
369     {
370     SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
371     spf_result_guessed = TRUE;
372     }
373   else
374     SPF_request_query_mailfrom(spf_request, &spf_response);
375
376   /* set up expansion items */
377   spf_header_comment     = US SPF_response_get_header_comment(spf_response);
378   spf_received           = US SPF_response_get_received_spf(spf_response);
379   spf_result             = US SPF_strresult(SPF_response_result(spf_response));
380   spf_smtp_comment       = US SPF_response_get_smtp_comment(spf_response);
381
382   rc = SPF_response_result(spf_response);
383
384   DEBUG(D_acl) spf_response_debug(spf_response);
385   }
386
387 /* We got a result. Now see if we should return OK or FAIL for it */
388 DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
389
390 if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
391   return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
392
393 while ((spf_result_id = string_nextinlist(&list, &sep, NULL, 0)))
394   {
395   BOOL negate, result;
396
397   if ((negate = spf_result_id[0] == '!'))
398     spf_result_id++;
399
400   result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name) == 0;
401   if (negate != result) return OK;
402   }
403
404 /* no match */
405 return FAIL;
406 }
407
408
409
410 gstring *
411 authres_spf(gstring * g)
412 {
413 uschar * s;
414 if (spf_result)
415   {
416   int start = 0;                /* Compiler quietening */
417   DEBUG(D_acl) start = gstring_length(g);
418
419   g = string_append(g, 2, US";\n\tspf=", spf_result);
420   if (spf_result_guessed)
421     g = string_cat(g, US" (best guess record for domain)");
422
423   s = expand_string(US"$sender_address_domain");
424   if (s && *s)
425     g = string_append(g, 2, US" smtp.mailfrom=", s);
426   else
427     {
428     s = sender_helo_name;
429     g = s && *s
430       ? string_append(g, 2, US" smtp.helo=", s)
431       : string_cat(g, US" smtp.mailfrom=<>");
432     }
433   DEBUG(D_acl) debug_printf("SPF:\tauthres '%.*s'\n",
434                   gstring_length(g) - start - 3, g->s + start + 3);
435   }
436 else
437   DEBUG(D_acl) debug_printf("SPF:\tno authres\n");
438 return g;
439 }
440
441
442 #endif