SPF: split library init from per-connection init
[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