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