Doc: Minor changes (copyright year, http->https)
[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 return TRUE;
169 }
170
171
172 /* Set up a context that can be re-used for several
173 messages on the same SMTP connection (that come from the
174 same host with the same HELO string).
175
176 Return: Boolean success
177 */
178
179 BOOL
180 spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr)
181 {
182 DEBUG(D_receive)
183 debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);
184
185 if (!spf_server && !spf_init()) return FALSE;
186
187 if (SPF_server_set_rec_dom(spf_server, CS primary_hostname))
188 {
189 DEBUG(D_receive) debug_printf("spf: SPF_server_set_rec_dom(\"%s\") failed.\n",
190 primary_hostname);
191 spf_server = NULL;
192 return FALSE;
193 }
194
195 spf_request = SPF_request_new(spf_server);
196
197 if ( SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr)
198 && SPF_request_set_ipv6_str(spf_request, CS spf_remote_addr)
199 )
200 {
201 DEBUG(D_receive)
202 debug_printf("spf: SPF_request_set_ipv4_str() and "
203 "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr);
204 spf_server = NULL;
205 spf_request = NULL;
206 return FALSE;
207 }
208
209 if (SPF_request_set_helo_dom(spf_request, CS spf_helo_domain))
210 {
211 DEBUG(D_receive) debug_printf("spf: SPF_set_helo_dom(\"%s\") failed.\n",
212 spf_helo_domain);
213 spf_server = NULL;
214 spf_request = NULL;
215 return FALSE;
216 }
217
218 return TRUE;
219 }
220
221
222 /* spf_process adds the envelope sender address to the existing
223 context (if any), retrieves the result, sets up expansion
224 strings and evaluates the condition outcome.
225
226 Return: OK/FAIL */
227
228 int
229 spf_process(const uschar **listptr, uschar *spf_envelope_sender, int action)
230 {
231 int sep = 0;
232 const uschar *list = *listptr;
233 uschar *spf_result_id;
234 int rc = SPF_RESULT_PERMERROR;
235
236 DEBUG(D_receive) debug_printf("spf_process\n");
237
238 if (!(spf_server && spf_request))
239 /* no global context, assume temp error and skip to evaluation */
240 rc = SPF_RESULT_PERMERROR;
241
242 else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender))
243 /* Invalid sender address. This should be a real rare occurrence */
244 rc = SPF_RESULT_PERMERROR;
245
246 else
247 {
248 /* get SPF result */
249 if (action == SPF_PROCESS_FALLBACK)
250 {
251 SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
252 spf_result_guessed = TRUE;
253 }
254 else
255 SPF_request_query_mailfrom(spf_request, &spf_response);
256
257 /* set up expansion items */
258 spf_header_comment = US SPF_response_get_header_comment(spf_response);
259 spf_received = US SPF_response_get_received_spf(spf_response);
260 spf_result = US SPF_strresult(SPF_response_result(spf_response));
261 spf_smtp_comment = US SPF_response_get_smtp_comment(spf_response);
262
263 rc = SPF_response_result(spf_response);
264 }
265
266 /* We got a result. Now see if we should return OK or FAIL for it */
267 DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
268
269 if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
270 return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
271
272 while ((spf_result_id = string_nextinlist(&list, &sep, NULL, 0)))
273 {
274 BOOL negate, result;
275
276 if ((negate = spf_result_id[0] == '!'))
277 spf_result_id++;
278
279 result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name) == 0;
280 if (negate != result) return OK;
281 }
282
283 /* no match */
284 return FAIL;
285 }
286
287
288
289 gstring *
290 authres_spf(gstring * g)
291 {
292 uschar * s;
293 if (!spf_result) return g;
294
295 g = string_append(g, 2, US";\n\tspf=", spf_result);
296 if (spf_result_guessed)
297 g = string_cat(g, US" (best guess record for domain)");
298
299 s = expand_string(US"$sender_address_domain");
300 return s && *s
301 ? string_append(g, 2, US" smtp.mailfrom=", s)
302 : string_cat(g, US" smtp.mailfrom=<>");
303 }
304
305
306 #endif