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