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