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