Fix reporting of 2-phase queue-runner daemon, in daemon start log line and in exiwhat
[users/jgh/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   /* Quick hack to override the outdated explanation URL.
244   See https://www.mail-archive.com/mailop@mailop.org/msg08019.html */
245   SPF_server_set_explanation(spf_server, "Please%_see%_http://www.open-spf.org/Why?id=%{S}&ip=%{C}&receiver=%{R}", &spf_response);
246   if (SPF_response_errcode(spf_response) != SPF_E_SUCCESS)
247     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", SPF_strerror(SPF_response_errcode(spf_response)));
248
249 return TRUE;
250 }
251
252
253 /* Set up a context that can be re-used for several
254    messages on the same SMTP connection (that come from the
255    same host with the same HELO string).
256
257 Return: Boolean success
258 */
259
260 BOOL
261 spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr)
262 {
263 DEBUG(D_receive)
264   debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);
265
266 if (!spf_server && !spf_init()) return FALSE;
267
268 if (SPF_server_set_rec_dom(spf_server, CS primary_hostname))
269   {
270   DEBUG(D_receive) debug_printf("spf: SPF_server_set_rec_dom(\"%s\") failed.\n",
271     primary_hostname);
272   spf_server = NULL;
273   return FALSE;
274   }
275
276 spf_request = SPF_request_new(spf_server);
277
278 if (  SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr)
279    && SPF_request_set_ipv6_str(spf_request, CS spf_remote_addr)
280    )
281   {
282   DEBUG(D_receive)
283     debug_printf("spf: SPF_request_set_ipv4_str() and "
284       "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr);
285   spf_server = NULL;
286   spf_request = NULL;
287   return FALSE;
288   }
289
290 if (SPF_request_set_helo_dom(spf_request, CS spf_helo_domain))
291   {
292   DEBUG(D_receive) debug_printf("spf: SPF_set_helo_dom(\"%s\") failed.\n",
293     spf_helo_domain);
294   spf_server = NULL;
295   spf_request = NULL;
296   return FALSE;
297   }
298
299 return TRUE;
300 }
301
302
303 void
304 spf_response_debug(SPF_response_t * spf_response)
305 {
306 if (SPF_response_messages(spf_response) == 0)
307   debug_printf(" (no errors)\n");
308 else for (int i = 0; i < SPF_response_messages(spf_response); i++)
309   {
310   SPF_error_t * err = SPF_response_message(spf_response, i);
311   debug_printf( "%s_msg = (%d) %s\n",
312                   (SPF_error_errorp(err) ? "warn" : "err"),
313                   SPF_error_code(err),
314                   SPF_error_message(err));
315   }
316 }
317
318
319 /* spf_process adds the envelope sender address to the existing
320    context (if any), retrieves the result, sets up expansion
321    strings and evaluates the condition outcome.
322
323 Return: OK/FAIL  */
324
325 int
326 spf_process(const uschar **listptr, uschar *spf_envelope_sender, int action)
327 {
328 int sep = 0;
329 const uschar *list = *listptr;
330 uschar *spf_result_id;
331 int rc = SPF_RESULT_PERMERROR;
332
333 DEBUG(D_receive) debug_printf("spf_process\n");
334
335 if (!(spf_server && spf_request))
336   /* no global context, assume temp error and skip to evaluation */
337   rc = SPF_RESULT_PERMERROR;
338
339 else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender))
340   /* Invalid sender address. This should be a real rare occurrence */
341   rc = SPF_RESULT_PERMERROR;
342
343 else
344   {
345   /* get SPF result */
346   if (action == SPF_PROCESS_FALLBACK)
347     {
348     SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
349     spf_result_guessed = TRUE;
350     }
351   else
352     SPF_request_query_mailfrom(spf_request, &spf_response);
353
354   /* set up expansion items */
355   spf_header_comment     = US SPF_response_get_header_comment(spf_response);
356   spf_received           = US SPF_response_get_received_spf(spf_response);
357   spf_result             = US SPF_strresult(SPF_response_result(spf_response));
358   spf_smtp_comment       = US SPF_response_get_smtp_comment(spf_response);
359
360   rc = SPF_response_result(spf_response);
361
362   DEBUG(D_acl) spf_response_debug(spf_response);
363   }
364
365 /* We got a result. Now see if we should return OK or FAIL for it */
366 DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
367
368 if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
369   return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
370
371 while ((spf_result_id = string_nextinlist(&list, &sep, NULL, 0)))
372   {
373   BOOL negate, result;
374
375   if ((negate = spf_result_id[0] == '!'))
376     spf_result_id++;
377
378   result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name) == 0;
379   if (negate != result) return OK;
380   }
381
382 /* no match */
383 return FAIL;
384 }
385
386
387
388 gstring *
389 authres_spf(gstring * g)
390 {
391 uschar * s;
392 if (!spf_result) return g;
393
394 g = string_append(g, 2, US";\n\tspf=", spf_result);
395 if (spf_result_guessed)
396   g = string_cat(g, US" (best guess record for domain)");
397
398 s = expand_string(US"$sender_address_domain");
399 return s && *s
400   ? string_append(g, 2, US" smtp.mailfrom=", s)
401   : string_cat(g, US" smtp.mailfrom=<>");
402 }
403
404
405 #endif