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