Merge branch '4.next'
[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
38 static SPF_dns_rr_t *
39 SPF_dns_exim_lookup(SPF_dns_server_t *spf_dns_server,
40 const char *domain, ns_type rr_type, int should_cache)
41 {
42 dns_answer * dnsa = store_get_dns_answer();
43 dns_scan dnss;
44 SPF_dns_rr_t * spfrr;
45
46 DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup\n");
47
48 if (dns_lookup(dnsa, US domain, rr_type, NULL) == DNS_SUCCEED)
49   for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
50        rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
51     if (  rr->type == rr_type
52        && Ustrncmp(rr->data+1, "v=spf1", 6) == 0)
53       {
54       gstring * g = NULL;
55       uschar chunk_len;
56       uschar * s;
57       SPF_dns_rr_t srr = {
58         .domain = CS rr->name,                  /* query information */
59         .domain_buf_len = DNS_MAXNAME,
60         .rr_type = rr->type,
61
62         .num_rr = 1,                            /* answer information */
63         .rr = NULL,
64         .rr_buf_len = 0,
65         .rr_buf_num = 0,
66         .ttl = rr->ttl,
67         .utc_ttl = 0,
68         .herrno = NETDB_SUCCESS,
69
70         .hook = NULL,                           /* misc information */
71         .source = spf_dns_server
72       };
73
74       for (int off = 0; off < rr->size; off += chunk_len)
75         {
76         chunk_len = (rr->data)[off++];
77         g = string_catn(g, US ((rr->data)+off), chunk_len);
78         }
79       if (!g)
80         {
81         HDEBUG(D_host_lookup) debug_printf("IP address lookup yielded an "
82           "empty name: treated as non-existent host name\n");
83         continue;
84         }
85       gstring_release_unused(g);
86       s = string_copy_malloc(string_from_gstring(g));
87       srr.rr = (void *) &s;
88
89       /* spfrr->rr must have been malloc()d for this */
90       SPF_dns_rr_dup(&spfrr, &srr);
91
92       return spfrr;
93       }
94
95 SPF_dns_rr_dup(&spfrr, spf_nxdomain);
96 return spfrr;
97 }
98
99
100
101 SPF_dns_server_t *
102 SPF_dns_exim_new(int debug)
103 {
104 SPF_dns_server_t *spf_dns_server;
105
106 DEBUG(D_receive) debug_printf("SPF_dns_exim_new\n");
107
108 if (!(spf_dns_server = malloc(sizeof(SPF_dns_server_t))))
109   return NULL;
110 memset(spf_dns_server, 0, sizeof(SPF_dns_server_t));
111
112 spf_dns_server->destroy      = NULL;
113 spf_dns_server->lookup       = SPF_dns_exim_lookup;
114 spf_dns_server->get_spf      = NULL;
115 spf_dns_server->get_exp      = NULL;
116 spf_dns_server->add_cache    = NULL;
117 spf_dns_server->layer_below  = NULL;
118 spf_dns_server->name         = "exim";
119 spf_dns_server->debug        = debug;
120
121 /* XXX This might have to return NO_DATA sometimes. */
122
123 spf_nxdomain = SPF_dns_rr_new_init(spf_dns_server,
124   "", ns_t_any, 24 * 60 * 60, HOST_NOT_FOUND);
125 if (!spf_nxdomain)
126   {
127   free(spf_dns_server);
128   return NULL;
129   }
130
131 return spf_dns_server;
132 }
133
134
135
136
137 /* Construct the SPF library stack.
138    Return: Boolean success.
139 */
140
141 BOOL
142 spf_init(void)
143 {
144 SPF_dns_server_t * dc;
145 int debug = 0;
146
147 DEBUG(D_receive) debug = 1;
148
149 /* We insert our own DNS access layer rather than letting the spf library
150 do it, so that our dns access path is used for debug tracing and for the
151 testsuite. */
152
153 if (!(dc = SPF_dns_exim_new(debug)))
154   {
155   DEBUG(D_receive) debug_printf("spf: SPF_dns_exim_new() failed\n");
156   return FALSE;
157   }
158 if (!(dc = SPF_dns_cache_new(dc, NULL, debug, 8)))
159   {
160   DEBUG(D_receive) debug_printf("spf: SPF_dns_cache_new() failed\n");
161   return FALSE;
162   }
163 if (!(spf_server = SPF_server_new_dns(dc, debug)))
164   {
165   DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n");
166   return FALSE;
167   }
168   /* Quick hack to override the outdated explanation URL.
169   See https://www.mail-archive.com/mailop@mailop.org/msg08019.html */
170   SPF_server_set_explanation(spf_server, "Please%_see%_http://www.open-spf.org/Why?id=%{S}&ip=%{C}&receiver=%{R}", &spf_response);
171   if (SPF_response_errcode(spf_response) != SPF_E_SUCCESS)
172     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", SPF_strerror(SPF_response_errcode(spf_response)));
173
174 return TRUE;
175 }
176
177
178 /* Set up a context that can be re-used for several
179    messages on the same SMTP connection (that come from the
180    same host with the same HELO string).
181
182 Return: Boolean success
183 */
184
185 BOOL
186 spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr)
187 {
188 DEBUG(D_receive)
189   debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);
190
191 if (!spf_server && !spf_init()) return FALSE;
192
193 if (SPF_server_set_rec_dom(spf_server, CS primary_hostname))
194   {
195   DEBUG(D_receive) debug_printf("spf: SPF_server_set_rec_dom(\"%s\") failed.\n",
196     primary_hostname);
197   spf_server = NULL;
198   return FALSE;
199   }
200
201 spf_request = SPF_request_new(spf_server);
202
203 if (  SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr)
204    && SPF_request_set_ipv6_str(spf_request, CS spf_remote_addr)
205    )
206   {
207   DEBUG(D_receive)
208     debug_printf("spf: SPF_request_set_ipv4_str() and "
209       "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr);
210   spf_server = NULL;
211   spf_request = NULL;
212   return FALSE;
213   }
214
215 if (SPF_request_set_helo_dom(spf_request, CS spf_helo_domain))
216   {
217   DEBUG(D_receive) debug_printf("spf: SPF_set_helo_dom(\"%s\") failed.\n",
218     spf_helo_domain);
219   spf_server = NULL;
220   spf_request = NULL;
221   return FALSE;
222   }
223
224 return TRUE;
225 }
226
227
228 /* spf_process adds the envelope sender address to the existing
229    context (if any), retrieves the result, sets up expansion
230    strings and evaluates the condition outcome.
231
232 Return: OK/FAIL  */
233
234 int
235 spf_process(const uschar **listptr, uschar *spf_envelope_sender, int action)
236 {
237 int sep = 0;
238 const uschar *list = *listptr;
239 uschar *spf_result_id;
240 int rc = SPF_RESULT_PERMERROR;
241
242 DEBUG(D_receive) debug_printf("spf_process\n");
243
244 if (!(spf_server && spf_request))
245   /* no global context, assume temp error and skip to evaluation */
246   rc = SPF_RESULT_PERMERROR;
247
248 else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender))
249   /* Invalid sender address. This should be a real rare occurrence */
250   rc = SPF_RESULT_PERMERROR;
251
252 else
253   {
254   /* get SPF result */
255   if (action == SPF_PROCESS_FALLBACK)
256     {
257     SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
258     spf_result_guessed = TRUE;
259     }
260   else
261     SPF_request_query_mailfrom(spf_request, &spf_response);
262
263   /* set up expansion items */
264   spf_header_comment     = US SPF_response_get_header_comment(spf_response);
265   spf_received           = US SPF_response_get_received_spf(spf_response);
266   spf_result             = US SPF_strresult(SPF_response_result(spf_response));
267   spf_smtp_comment       = US SPF_response_get_smtp_comment(spf_response);
268
269   rc = SPF_response_result(spf_response);
270   }
271
272 /* We got a result. Now see if we should return OK or FAIL for it */
273 DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
274
275 if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
276   return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
277
278 while ((spf_result_id = string_nextinlist(&list, &sep, NULL, 0)))
279   {
280   BOOL negate, result;
281
282   if ((negate = spf_result_id[0] == '!'))
283     spf_result_id++;
284
285   result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name) == 0;
286   if (negate != result) return OK;
287   }
288
289 /* no match */
290 return FAIL;
291 }
292
293
294
295 gstring *
296 authres_spf(gstring * g)
297 {
298 uschar * s;
299 if (!spf_result) return g;
300
301 g = string_append(g, 2, US";\n\tspf=", spf_result);
302 if (spf_result_guessed)
303   g = string_cat(g, US" (best guess record for domain)");
304
305 s = expand_string(US"$sender_address_domain");
306 return s && *s
307   ? string_append(g, 2, US" smtp.mailfrom=", s)
308   : string_cat(g, US" smtp.mailfrom=<>");
309 }
310
311
312 #endif