SPF: handle DNS NO_DATA return. 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 - 2019
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\n");
126           continue;
127           }
128
129         for (int off = 0; off < rr->size; off += chunk_len)
130           {
131           if (!(chunk_len = s[off++])) break;
132           g = string_catn(g, s+off, chunk_len);
133           }
134         if (!g)
135           continue;
136         gstring_release_unused(g);
137         s = string_copy_malloc(string_from_gstring(g));
138         break;
139         }
140
141       case T_A:
142       case T_AAAA:
143       default:
144         {
145         uschar * buf = store_malloc(dnsa->answerlen + 1);
146         s = memcpy(buf, s, dnsa->answerlen + 1);
147         break;
148         }
149       }
150     DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", s);
151     srr.rr[found++] = (void *) s;
152     }
153
154 srr.num_rr = found;
155 /* spfrr->rr must have been malloc()d for this */
156 SPF_dns_rr_dup(&spfrr, &srr);
157 return spfrr;
158 }
159
160
161
162 SPF_dns_server_t *
163 SPF_dns_exim_new(int debug)
164 {
165 SPF_dns_server_t * spf_dns_server = store_malloc(sizeof(SPF_dns_server_t));
166
167 DEBUG(D_receive) debug_printf("SPF_dns_exim_new\n");
168
169 memset(spf_dns_server, 0, sizeof(SPF_dns_server_t));
170 spf_dns_server->destroy      = NULL;
171 spf_dns_server->lookup       = SPF_dns_exim_lookup;
172 spf_dns_server->get_spf      = NULL;
173 spf_dns_server->get_exp      = NULL;
174 spf_dns_server->add_cache    = NULL;
175 spf_dns_server->layer_below  = NULL;
176 spf_dns_server->name         = "exim";
177 spf_dns_server->debug        = debug;
178
179 /* XXX This might have to return NO_DATA sometimes. */
180
181 spf_nxdomain = SPF_dns_rr_new_init(spf_dns_server,
182   "", ns_t_any, 24 * 60 * 60, HOST_NOT_FOUND);
183 if (!spf_nxdomain)
184   {
185   free(spf_dns_server);
186   return NULL;
187   }
188
189 return spf_dns_server;
190 }
191
192
193
194
195 /* Construct the SPF library stack.
196    Return: Boolean success.
197 */
198
199 BOOL
200 spf_init(void)
201 {
202 SPF_dns_server_t * dc;
203 int debug = 0;
204
205 DEBUG(D_receive) debug = 1;
206
207 /* We insert our own DNS access layer rather than letting the spf library
208 do it, so that our dns access path is used for debug tracing and for the
209 testsuite. */
210
211 if (!(dc = SPF_dns_exim_new(debug)))
212   {
213   DEBUG(D_receive) debug_printf("spf: SPF_dns_exim_new() failed\n");
214   return FALSE;
215   }
216 if (!(dc = SPF_dns_cache_new(dc, NULL, debug, 8)))
217   {
218   DEBUG(D_receive) debug_printf("spf: SPF_dns_cache_new() failed\n");
219   return FALSE;
220   }
221 if (!(spf_server = SPF_server_new_dns(dc, debug)))
222   {
223   DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n");
224   return FALSE;
225   }
226   /* Quick hack to override the outdated explanation URL.
227   See https://www.mail-archive.com/mailop@mailop.org/msg08019.html */
228   SPF_server_set_explanation(spf_server, "Please%_see%_http://www.open-spf.org/Why?id=%{S}&ip=%{C}&receiver=%{R}", &spf_response);
229   if (SPF_response_errcode(spf_response) != SPF_E_SUCCESS)
230     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", SPF_strerror(SPF_response_errcode(spf_response)));
231
232 return TRUE;
233 }
234
235
236 /* Set up a context that can be re-used for several
237    messages on the same SMTP connection (that come from the
238    same host with the same HELO string).
239
240 Return: Boolean success
241 */
242
243 BOOL
244 spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr)
245 {
246 DEBUG(D_receive)
247   debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);
248
249 if (!spf_server && !spf_init()) return FALSE;
250
251 if (SPF_server_set_rec_dom(spf_server, CS primary_hostname))
252   {
253   DEBUG(D_receive) debug_printf("spf: SPF_server_set_rec_dom(\"%s\") failed.\n",
254     primary_hostname);
255   spf_server = NULL;
256   return FALSE;
257   }
258
259 spf_request = SPF_request_new(spf_server);
260
261 if (  SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr)
262    && SPF_request_set_ipv6_str(spf_request, CS spf_remote_addr)
263    )
264   {
265   DEBUG(D_receive)
266     debug_printf("spf: SPF_request_set_ipv4_str() and "
267       "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr);
268   spf_server = NULL;
269   spf_request = NULL;
270   return FALSE;
271   }
272
273 if (SPF_request_set_helo_dom(spf_request, CS spf_helo_domain))
274   {
275   DEBUG(D_receive) debug_printf("spf: SPF_set_helo_dom(\"%s\") failed.\n",
276     spf_helo_domain);
277   spf_server = NULL;
278   spf_request = NULL;
279   return FALSE;
280   }
281
282 return TRUE;
283 }
284
285
286 /* spf_process adds the envelope sender address to the existing
287    context (if any), retrieves the result, sets up expansion
288    strings and evaluates the condition outcome.
289
290 Return: OK/FAIL  */
291
292 int
293 spf_process(const uschar **listptr, uschar *spf_envelope_sender, int action)
294 {
295 int sep = 0;
296 const uschar *list = *listptr;
297 uschar *spf_result_id;
298 int rc = SPF_RESULT_PERMERROR;
299
300 DEBUG(D_receive) debug_printf("spf_process\n");
301
302 if (!(spf_server && spf_request))
303   /* no global context, assume temp error and skip to evaluation */
304   rc = SPF_RESULT_PERMERROR;
305
306 else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender))
307   /* Invalid sender address. This should be a real rare occurrence */
308   rc = SPF_RESULT_PERMERROR;
309
310 else
311   {
312   /* get SPF result */
313   if (action == SPF_PROCESS_FALLBACK)
314     {
315     SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
316     spf_result_guessed = TRUE;
317     }
318   else
319     SPF_request_query_mailfrom(spf_request, &spf_response);
320
321   /* set up expansion items */
322   spf_header_comment     = US SPF_response_get_header_comment(spf_response);
323   spf_received           = US SPF_response_get_received_spf(spf_response);
324   spf_result             = US SPF_strresult(SPF_response_result(spf_response));
325   spf_smtp_comment       = US SPF_response_get_smtp_comment(spf_response);
326
327   rc = SPF_response_result(spf_response);
328   }
329
330 /* We got a result. Now see if we should return OK or FAIL for it */
331 DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
332
333 if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
334   return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
335
336 while ((spf_result_id = string_nextinlist(&list, &sep, NULL, 0)))
337   {
338   BOOL negate, result;
339
340   if ((negate = spf_result_id[0] == '!'))
341     spf_result_id++;
342
343   result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name) == 0;
344   if (negate != result) return OK;
345   }
346
347 /* no match */
348 return FAIL;
349 }
350
351
352
353 gstring *
354 authres_spf(gstring * g)
355 {
356 uschar * s;
357 if (!spf_result) return g;
358
359 g = string_append(g, 2, US";\n\tspf=", spf_result);
360 if (spf_result_guessed)
361   g = string_cat(g, US" (best guess record for domain)");
362
363 s = expand_string(US"$sender_address_domain");
364 return s && *s
365   ? string_append(g, 2, US" smtp.mailfrom=", s)
366   : string_cat(g, US" smtp.mailfrom=<>");
367 }
368
369
370 #endif