1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
6 Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 - 2014
8 Copyright (c) The Exim Maintainers 2015 - 2019
11 /* Code for calling spf checks via libspf-alt. Called from acl.c. */
16 /* must be kept in numeric order */
17 static spf_result_id spf_result_id_list[] = {
25 { US"temperror", 6 }, /* RFC 4408 defined */
26 { US"permerror", 7 } /* RFC 4408 defined */
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;
34 SPF_dns_rr_t * spf_nxdomain = NULL;
39 SPF_dns_exim_lookup(SPF_dns_server_t *spf_dns_server,
40 const char *domain, ns_type rr_type, int should_cache)
42 dns_answer * dnsa = store_get_dns_answer();
46 DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", domain);
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 && (rr_type != T_TXT || Ustrncmp(rr->data+1, "v=spf1", 6) == 0))
54 const uschar * s = rr->data;
56 .domain = CS rr->name, /* query information */
57 .domain_buf_len = DNS_MAXNAME,
60 .num_rr = 1, /* answer information */
66 .herrno = NETDB_SUCCESS,
68 .hook = NULL, /* misc information */
69 .source = spf_dns_server
75 s += 2; /* skip the MX precedence field */
78 uschar * buf = store_malloc(256);
79 (void)dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, s,
80 (DN_EXPAND_ARG4_TYPE)buf, 256);
89 for (int off = 0; off < rr->size; off += chunk_len)
91 if (!(chunk_len = s[off++])) break;
92 g = string_catn(g, s+off, chunk_len);
96 HDEBUG(D_host_lookup) debug_printf("IP address lookup yielded an "
97 "empty name: treated as non-existent host name\n");
100 gstring_release_unused(g);
101 s = string_copy_malloc(string_from_gstring(g));
109 uschar * buf = store_malloc(dnsa->answerlen + 1);
110 s = memcpy(buf, s, dnsa->answerlen + 1);
114 DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", s);
115 srr.rr = (void *) &s;
117 /* spfrr->rr must have been malloc()d for this */
118 SPF_dns_rr_dup(&spfrr, &srr);
123 SPF_dns_rr_dup(&spfrr, spf_nxdomain);
130 SPF_dns_exim_new(int debug)
132 SPF_dns_server_t *spf_dns_server;
134 DEBUG(D_receive) debug_printf("SPF_dns_exim_new\n");
136 if (!(spf_dns_server = malloc(sizeof(SPF_dns_server_t))))
138 memset(spf_dns_server, 0, sizeof(SPF_dns_server_t));
140 spf_dns_server->destroy = NULL;
141 spf_dns_server->lookup = SPF_dns_exim_lookup;
142 spf_dns_server->get_spf = NULL;
143 spf_dns_server->get_exp = NULL;
144 spf_dns_server->add_cache = NULL;
145 spf_dns_server->layer_below = NULL;
146 spf_dns_server->name = "exim";
147 spf_dns_server->debug = debug;
149 /* XXX This might have to return NO_DATA sometimes. */
151 spf_nxdomain = SPF_dns_rr_new_init(spf_dns_server,
152 "", ns_t_any, 24 * 60 * 60, HOST_NOT_FOUND);
155 free(spf_dns_server);
159 return spf_dns_server;
165 /* Construct the SPF library stack.
166 Return: Boolean success.
172 SPF_dns_server_t * dc;
175 DEBUG(D_receive) debug = 1;
177 /* We insert our own DNS access layer rather than letting the spf library
178 do it, so that our dns access path is used for debug tracing and for the
181 if (!(dc = SPF_dns_exim_new(debug)))
183 DEBUG(D_receive) debug_printf("spf: SPF_dns_exim_new() failed\n");
186 if (!(dc = SPF_dns_cache_new(dc, NULL, debug, 8)))
188 DEBUG(D_receive) debug_printf("spf: SPF_dns_cache_new() failed\n");
191 if (!(spf_server = SPF_server_new_dns(dc, debug)))
193 DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n");
196 /* Quick hack to override the outdated explanation URL.
197 See https://www.mail-archive.com/mailop@mailop.org/msg08019.html */
198 SPF_server_set_explanation(spf_server, "Please%_see%_http://www.open-spf.org/Why?id=%{S}&ip=%{C}&receiver=%{R}", &spf_response);
199 if (SPF_response_errcode(spf_response) != SPF_E_SUCCESS)
200 log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", SPF_strerror(SPF_response_errcode(spf_response)));
206 /* Set up a context that can be re-used for several
207 messages on the same SMTP connection (that come from the
208 same host with the same HELO string).
210 Return: Boolean success
214 spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr)
217 debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);
219 if (!spf_server && !spf_init()) return FALSE;
221 if (SPF_server_set_rec_dom(spf_server, CS primary_hostname))
223 DEBUG(D_receive) debug_printf("spf: SPF_server_set_rec_dom(\"%s\") failed.\n",
229 spf_request = SPF_request_new(spf_server);
231 if ( SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr)
232 && SPF_request_set_ipv6_str(spf_request, CS spf_remote_addr)
236 debug_printf("spf: SPF_request_set_ipv4_str() and "
237 "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr);
243 if (SPF_request_set_helo_dom(spf_request, CS spf_helo_domain))
245 DEBUG(D_receive) debug_printf("spf: SPF_set_helo_dom(\"%s\") failed.\n",
256 /* spf_process adds the envelope sender address to the existing
257 context (if any), retrieves the result, sets up expansion
258 strings and evaluates the condition outcome.
263 spf_process(const uschar **listptr, uschar *spf_envelope_sender, int action)
266 const uschar *list = *listptr;
267 uschar *spf_result_id;
268 int rc = SPF_RESULT_PERMERROR;
270 DEBUG(D_receive) debug_printf("spf_process\n");
272 if (!(spf_server && spf_request))
273 /* no global context, assume temp error and skip to evaluation */
274 rc = SPF_RESULT_PERMERROR;
276 else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender))
277 /* Invalid sender address. This should be a real rare occurrence */
278 rc = SPF_RESULT_PERMERROR;
283 if (action == SPF_PROCESS_FALLBACK)
285 SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
286 spf_result_guessed = TRUE;
289 SPF_request_query_mailfrom(spf_request, &spf_response);
291 /* set up expansion items */
292 spf_header_comment = US SPF_response_get_header_comment(spf_response);
293 spf_received = US SPF_response_get_received_spf(spf_response);
294 spf_result = US SPF_strresult(SPF_response_result(spf_response));
295 spf_smtp_comment = US SPF_response_get_smtp_comment(spf_response);
297 rc = SPF_response_result(spf_response);
300 /* We got a result. Now see if we should return OK or FAIL for it */
301 DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
303 if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
304 return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
306 while ((spf_result_id = string_nextinlist(&list, &sep, NULL, 0)))
310 if ((negate = spf_result_id[0] == '!'))
313 result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name) == 0;
314 if (negate != result) return OK;
324 authres_spf(gstring * g)
327 if (!spf_result) return g;
329 g = string_append(g, 2, US";\n\tspf=", spf_result);
330 if (spf_result_guessed)
331 g = string_cat(g, US" (best guess record for domain)");
333 s = expand_string(US"$sender_address_domain");
335 ? string_append(g, 2, US" smtp.mailfrom=", s)
336 : string_cat(g, US" smtp.mailfrom=<>");