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