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