SPF: split library init from per-connection init
[exim.git] / src / src / spf.c
CommitLineData
8523533c
TK
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
8e669ac1 4
b8e97684 5/* SPF support.
5a66c31b 6 Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 - 2014
80fea873 7 License: GPL
b8e97684 8 Copyright (c) The Exim Maintainers 2015 - 2019
80fea873 9*/
8e669ac1 10
8523533c
TK
11/* Code for calling spf checks via libspf-alt. Called from acl.c. */
12
13#include "exim.h"
7952eef9 14#ifdef SUPPORT_SPF
8523533c 15
6d06cf48
TK
16/* must be kept in numeric order */
17static spf_result_id spf_result_id_list[] = {
f2ed27cf
JH
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 },
f2ed27cf
JH
25 { US"temperror", 6 }, /* RFC 4408 defined */
26 { US"permerror", 7 } /* RFC 4408 defined */
6d06cf48
TK
27};
28
384152a6
TK
29SPF_server_t *spf_server = NULL;
30SPF_request_t *spf_request = NULL;
31SPF_response_t *spf_response = NULL;
32SPF_response_t *spf_response_2mx = NULL;
8523533c 33
b8e97684
JH
34SPF_dns_rr_t * spf_nxdomain = NULL;
35
36
37
38static SPF_dns_rr_t *
39SPF_dns_exim_lookup(SPF_dns_server_t *spf_dns_server,
40const char *domain, ns_type rr_type, int should_cache)
41{
8743d3ac 42dns_answer * dnsa = store_get_dns_answer();
b8e97684
JH
43dns_scan dnss;
44SPF_dns_rr_t * spfrr;
45
46DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup\n");
47
8743d3ac
JH
48if (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))
b8e97684
JH
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
95SPF_dns_rr_dup(&spfrr, spf_nxdomain);
96return spfrr;
97}
98
99
100
101SPF_dns_server_t *
102SPF_dns_exim_new(int debug)
103{
104SPF_dns_server_t *spf_dns_server;
105
106DEBUG(D_receive) debug_printf("SPF_dns_exim_new\n");
107
108if (!(spf_dns_server = malloc(sizeof(SPF_dns_server_t))))
109 return NULL;
110memset(spf_dns_server, 0, sizeof(SPF_dns_server_t));
111
112spf_dns_server->destroy = NULL;
113spf_dns_server->lookup = SPF_dns_exim_lookup;
114spf_dns_server->get_spf = NULL;
115spf_dns_server->get_exp = NULL;
116spf_dns_server->add_cache = NULL;
117spf_dns_server->layer_below = NULL;
118spf_dns_server->name = "exim";
119spf_dns_server->debug = debug;
120
121/* XXX This might have to return NO_DATA sometimes. */
122
123spf_nxdomain = SPF_dns_rr_new_init(spf_dns_server,
124 "", ns_t_any, 24 * 60 * 60, HOST_NOT_FOUND);
125if (!spf_nxdomain)
126 {
127 free(spf_dns_server);
128 return NULL;
129 }
130
131return spf_dns_server;
132}
133
134
eb52e2cb 135
8e669ac1 136
73ec116f
JH
137/* Construct the SPF library stack.
138 Return: Boolean success.
139*/
8e669ac1 140
eb52e2cb 141BOOL
73ec116f 142spf_init(void)
eb52e2cb 143{
b8e97684 144SPF_dns_server_t * dc;
73ec116f 145int debug = 0;
b8e97684 146
73ec116f 147DEBUG(D_receive) debug = 1;
b8e97684
JH
148
149/* We insert our own DNS access layer rather than letting the spf library
150do it, so that our dns access path is used for debug tracing and for the
151testsuite. */
8e669ac1 152
b8e97684
JH
153if (!(dc = SPF_dns_exim_new(debug)))
154 {
155 DEBUG(D_receive) debug_printf("spf: SPF_dns_exim_new() failed\n");
156 return FALSE;
157 }
158if (!(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 }
163if (!(spf_server = SPF_server_new_dns(dc, debug)))
eb52e2cb
JH
164 {
165 DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n");
166 return FALSE;
8523533c 167 }
73ec116f
JH
168return 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
176Return: Boolean success
177*/
178
179BOOL
180spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr)
181{
182DEBUG(D_receive)
183 debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);
184
185if (!spf_server && !spf_init()) return FALSE;
8523533c 186
eb52e2cb
JH
187if (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;
7b3a77e5
TK
193 }
194
eb52e2cb
JH
195spf_request = SPF_request_new(spf_server);
196
197if ( 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;
8523533c
TK
207 }
208
eb52e2cb
JH
209if (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;
8523533c 216 }
8e669ac1 217
eb52e2cb 218return TRUE;
8523533c
TK
219}
220
221
222/* spf_process adds the envelope sender address to the existing
223 context (if any), retrieves the result, sets up expansion
eb52e2cb 224 strings and evaluates the condition outcome.
f9ba5e22 225
eb52e2cb
JH
226Return: OK/FAIL */
227
228int
229spf_process(const uschar **listptr, uschar *spf_envelope_sender, int action)
230{
231int sep = 0;
232const uschar *list = *listptr;
233uschar *spf_result_id;
234int rc = SPF_RESULT_PERMERROR;
235
b8e97684
JH
236DEBUG(D_receive) debug_printf("spf_process\n");
237
eb52e2cb
JH
238if (!(spf_server && spf_request))
239 /* no global context, assume temp error and skip to evaluation */
240 rc = SPF_RESULT_PERMERROR;
241
242else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender))
aded2255 243 /* Invalid sender address. This should be a real rare occurrence */
eb52e2cb
JH
244 rc = SPF_RESULT_PERMERROR;
245
246else
247 {
8523533c 248 /* get SPF result */
65a7d8c3 249 if (action == SPF_PROCESS_FALLBACK)
87e9d061 250 {
7156b1ef 251 SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
87e9d061
JH
252 spf_result_guessed = TRUE;
253 }
65a7d8c3
NM
254 else
255 SPF_request_query_mailfrom(spf_request, &spf_response);
8523533c
TK
256
257 /* set up expansion items */
5903c6ff
JH
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);
8523533c 262
384152a6 263 rc = SPF_response_result(spf_response);
eb52e2cb
JH
264 }
265
266/* We got a result. Now see if we should return OK or FAIL for it */
267DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
268
269if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
270 return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
271
272while ((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 }
8523533c 282
eb52e2cb
JH
283/* no match */
284return FAIL;
8523533c
TK
285}
286
dfbcb5ac
JH
287
288
289gstring *
290authres_spf(gstring * g)
291{
87e9d061 292uschar * s;
dfbcb5ac
JH
293if (!spf_result) return g;
294
87e9d061
JH
295g = string_append(g, 2, US";\n\tspf=", spf_result);
296if (spf_result_guessed)
297 g = string_cat(g, US" (best guess record for domain)");
298
299s = expand_string(US"$sender_address_domain");
300return s && *s
301 ? string_append(g, 2, US" smtp.mailfrom=", s)
302 : string_cat(g, US" smtp.mailfrom=<>");
dfbcb5ac
JH
303}
304
305
8523533c 306#endif