1 /* $Id: spf.c,v 1.40 2015/10/30 18:22:30 manu Exp $ */
2 
3 /*
4  * Copyright (c) 2004-2007 Emmanuel Dreyfus
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. All advertising materials mentioning features or use of this software
16  *    must display the following acknowledgement:
17  *        This product includes software developed by Emmanuel Dreyfus
18  *
19  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
20  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22  * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
23  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29  * OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #include "config.h"
33 
34 #ifdef HAVE_SYS_CDEFS_H
35 #include <sys/cdefs.h>
36 #ifdef __RCSID
37 __RCSID("$Id: spf.c,v 1.40 2015/10/30 18:22:30 manu Exp $");
38 #endif
39 #endif
40 
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <sysexits.h>
44 #include <errno.h>
45 #include <string.h>
46 #include <strings.h>
47 #include <syslog.h>
48 #include <sys/types.h>
49 #include <sys/socket.h>
50 #include <netinet/in.h>
51 #include <arpa/inet.h>
52 
53 #include "conf.h"
54 #include "spf.h"
55 #include "acl.h"
56 #include "milter-greylist.h"
57 
58 #ifdef USE_DMALLOC
59 #include <dmalloc.h>
60 #endif
61 
62 #if defined(__FreeBSD__) || defined(__DragonFly__)
63 #define HAVE_NS_TYPE
64 #include <arpa/nameser.h>
65 #endif
66 
67 
68 #if (defined(HAVE_SPF) || defined(HAVE_SPF_ALT) || \
69      defined(HAVE_SPF2_10) || defined(HAVE_SPF2))
70 static int spf_check_internal(acl_data_t *, acl_stage_t,
71 			      struct acl_param *, struct mlfi_priv *);
72 static int spf_check_self(acl_data_t *, acl_stage_t,
73 			  struct acl_param *, struct mlfi_priv *);
74 #endif
75 
76 #ifdef HAVE_SPF
77 #include <spf.h>
78 
79 #ifndef SPF_FALSE
80 #define SPF_FALSE 0
81 #endif
82 
83 
84 static int
spf_check_internal(ad,as,ap,priv)85 spf_check_internal(ad, as, ap, priv)
86 	acl_data_t *ad;
87 	acl_stage_t as;
88 	struct acl_param *ap;
89 	struct mlfi_priv *priv;
90 {
91 	struct sockaddr *sa = SA(&priv->priv_addr);
92 	socklen_t salen = priv->priv_addrlen;
93 	char *helo = priv->priv_helo;
94 	char *fromp = priv->priv_from;
95 	peer_info_t *p = NULL;
96 	char addr[IPADDRSTRLEN];
97 	int result = 0;
98 	struct timeval tv1, tv2, tv3;
99 	enum spf_status status;
100 
101 	if (conf.c_debug)
102 		gettimeofday(&tv1, NULL);
103 
104 	if (sa->sa_family != AF_INET)	/* libspf doesn't support IPv6 */
105 		return result;
106 	if (!iptostring(sa, salen, addr, sizeof(addr)))
107 		return result;
108 
109 	if ((p = SPF_init("milter-greylist", addr,
110 	    NULL, NULL, NULL, SPF_FALSE, SPF_FALSE)) == NULL) {
111 		mg_log(LOG_ERR, "SPF_Init failed");
112 		goto out1;
113 	}
114 	SPF_smtp_helo(p, helo);
115 	SPF_smtp_from(p, from);
116 	p->RES = SPF_policy_main(p);
117 
118 	status = ad ? *(enum spf_status *)ad : MGSPF_PASS;
119 	switch (p->RES) {
120 	case SPF_PASS:
121 		result = (status == MGSPF_PASS);
122 		strcpy(priv->priv_spf_result, "pass");
123 		break;
124 	case SPF_H_FAIL:
125 		result = (status == MGSPF_FAIL);
126 		strcpy(priv->priv_spf_result, "fail");
127 		break;
128 	case SPF_S_FAIL:
129 		result = (status == MGSPF_SOFTFAIL);
130 		strcpy(priv->priv_spf_result, "softfail");
131 		break;
132 	case SPF_NEUTRAL:
133 		result = (status == MGSPF_NEUTRAL);
134 		strcpy(priv->priv_spf_result, "neutral");
135 		break;
136 	case SPF_UNKNOWN:
137 	case SPF_UNMECH:
138 		result = (status == MGSPF_UNKNOWN);
139 		strcpy(priv->priv_spf_result, "unknown");
140 		break;
141 	case SPF_ERROR:
142 		result = (status == MGSPF_ERROR);
143 		strcpy(priv->priv_spf_result, "error");
144 		break;
145 	case SPF_NONE:
146 		result = (status == MGSPF_NONE);
147 		strcpy(priv->priv_spf_result, "none");
148 		break;
149 	default:
150 		mg_log(LOG_ERR, "Internal error: unexpected spf result %d",
151 			" spf test code %d", p->RES, status);
152 		exit(EX_SOFTWARE);
153 		break;
154 	}
155 
156 	if (conf.c_debug)
157 		mg_log(LOG_DEBUG, "SPF return %s (test code %d, result %d)",
158 			priv->priv_spf_result, status, result);
159 
160 	SPF_close(p);
161 
162 out1:
163 	if (conf.c_debug) {
164 		gettimeofday(&tv2, NULL);
165 		timersub(&tv2, &tv1, &tv3);
166 		mg_log(LOG_DEBUG, "SPF lookup performed in %ld.%06lds",
167 		    tv3.tv_sec, tv3.tv_usec);
168 	}
169 
170 	return result;
171 }
172 #endif /* HAVE_SPF */
173 
174 
175 #if defined(HAVE_SPF_ALT) || defined(HAVE_SPF2_10) || defined(HAVE_SPF2)
176 /* SMTP needs at least 64 chars for local part and 255 for doamin... */
177 #ifndef NS_MAXDNAME
178 #define NS_MAXDNAME 1025
179 #endif
180 #endif
181 
182 #ifdef HAVE_SPF_ALT
183 #include <spf_alt/spf.h>
184 #include <spf_alt/spf_dns_resolv.h>
185 #include <spf_alt/spf_lib_version.h>
186 #endif
187 
188 #ifdef HAVE_SPF2_10
189 #include <spf2/spf.h>
190 #include <spf2/spf_dns_resolv.h>
191 #include <spf2/spf_lib_version.h>
192 #endif
193 
194 #if defined(HAVE_SPF_ALT) || defined(HAVE_SPF2_10)
195 static int
spf_check_internal(ad,as,ap,priv)196 spf_check_internal(ad, as, ap, priv)
197 	acl_data_t *ad;
198 	acl_stage_t as;
199 	struct acl_param *ap;
200 	struct mlfi_priv *priv;
201 {
202 	struct sockaddr *sa = SA(&priv->priv_addr);
203 	socklen_t salen = priv->priv_addrlen;
204 	char *helo = priv->priv_helo;
205 	char *fromp = priv->priv_from;
206 	SPF_config_t spfconf;
207 	SPF_dns_config_t dnsconf;
208 	char addr[IPADDRSTRLEN];
209 	char from[NS_MAXDNAME + 1];
210 	SPF_output_t out;
211 	int result = 0;
212 	struct timeval tv1, tv2, tv3;
213 	size_t len;
214 	enum spf_status status;
215 
216 	if (conf.c_debug)
217 		gettimeofday(&tv1, NULL);
218 
219 	if ((spfconf = SPF_create_config()) == NULL) {
220 		mg_log(LOG_ERR, "SPF_create_config failed");
221 		goto out1;
222 	}
223 
224 	if ((dnsconf = SPF_dns_create_config_resolv(NULL, 0)) == NULL) {
225 		mg_log(LOG_ERR, "SPF_dns_create_config_resolv faile");
226 		goto out2;
227 	}
228 
229 	/*
230 	 * Get the IP address
231 	 */
232 	if (!iptostring(sa, salen, addr, sizeof(addr))) {
233 		mg_log(LOG_ERR, "SPF_set_ip_str failed");
234 		goto out3;
235 	}
236 	if (SPF_set_ip_str(spfconf, addr) != 0) {
237 		mg_log(LOG_ERR, "SPF_set_ip_str failed");
238 		goto out3;
239 	}
240 
241 	/* HELO string */
242 	if (SPF_set_helo_dom(spfconf, helo) != 0) {
243 		mg_log(LOG_ERR, "SPF_set_helo failed");
244 		goto out3;
245 	}
246 
247 	/*
248 	 * And the enveloppe source e-mail
249 	 */
250 	if (fromp[0] == '<')
251 		fromp++; /* strip leading < */
252 	strncpy(from, fromp, NS_MAXDNAME);
253 	from[NS_MAXDNAME] = '\0';
254 	len = strlen(from);
255 	if (fromp[len - 1] == '>')
256 		from[len - 1] = '\0'; /* strip trailing > */
257 
258 	if (SPF_set_env_from(spfconf, from) != 0) {
259 		mg_log(LOG_ERR, "SPF_set_env_from failed");
260 		goto out3;
261 	}
262 
263 	/*
264 	 * Get the SPF result
265 	 */
266 	SPF_init_output(&out);
267 #if 0 &&((SPF_LIB_VERSION_MAJOR == 0) && (SPF_LIB_VERSION_MINOR <= 3))
268 	out = SPF_result(spfconf, dnsconf, NULL);
269 #else
270 	out = SPF_result(spfconf, dnsconf);
271 #endif
272 
273 	status = ad ? *(enum spf_status *)ad : MGSPF_PASS;
274 	switch (out.result) {
275 	case SPF_RESULT_PASS:
276 		result = (status == MGSPF_PASS);
277 		strcpy(priv->priv_spf_result, "pass");
278 		break;
279 	case SPF_RESULT_FAIL:
280 		result = (status == MGSPF_FAIL);
281 		strcpy(priv->priv_spf_result, "fail");
282 		break;
283 	case SPF_RESULT_SOFTFAIL:
284 		result = (status == MGSPF_SOFTFAIL);
285 		strcpy(priv->priv_spf_result, "softfail");
286 		break;
287 	case SPF_RESULT_NEUTRAL:
288 		result = (status == MGSPF_NEUTRAL);
289 		strcpy(priv->priv_spf_result, "neutral");
290 		break;
291 	case SPF_RESULT_UNKNOWN:
292 		result = (status == MGSPF_UNKNOWN);
293 		strcpy(priv->priv_spf_result, "unknown");
294 		break;
295 	case SPF_RESULT_ERROR:
296 		result = (status == MGSPF_ERROR);
297 		strcpy(priv->priv_spf_result, "error");
298 		break;
299 	case SPF_RESULT_NONE:
300 		result = (status == MGSPF_NONE);
301 		strcpy(priv->priv_spf_result, "none");
302 		break;
303 	default:
304 		mg_log(LOG_ERR,
305 			"Internal error: unexpected spf_alt/spf_2_10 result %d",
306 			" spf test code %d", out.result, status);
307 		exit(EX_SOFTWARE);
308 		break;
309 	}
310 
311 	if (conf.c_debug)
312 		mg_log(LOG_DEBUG, "SPF return %s (test code %d, result %d)",
313 			priv->priv_spf_result, out.result, result);
314 
315 	SPF_free_output(&out);
316 out3:
317 	SPF_dns_destroy_config_resolv(dnsconf);
318 out2:
319 	SPF_destroy_config(spfconf);
320 out1:
321 	if (conf.c_debug) {
322 		gettimeofday(&tv2, NULL);
323 		timersub(&tv2, &tv1, &tv3);
324 		mg_log(LOG_DEBUG, "SPF lookup performed in %ld.%06lds",
325 		    tv3.tv_sec, tv3.tv_usec);
326 	}
327 
328 	return result;
329 }
330 
331 #endif /* HAVE_SPF_ALT */
332 
333 #ifdef HAVE_SPF2
334 #include <spf2/spf.h>
335 
336 static int
spf_check_internal(ad,as,ap,priv)337 spf_check_internal(ad, as, ap, priv)
338 	acl_data_t *ad;
339 	acl_stage_t as;
340 	struct acl_param *ap;
341 	struct mlfi_priv *priv;
342 {
343 	struct sockaddr *sa = SA(&priv->priv_addr);
344 	char *fqdn;
345 	char *helo = priv->priv_helo;
346 	char *fromp = priv->priv_from;
347 	SPF_server_t *spf_server;
348 	SPF_request_t *spf_request;
349 	SPF_response_t *spf_response;
350 	char from[NS_MAXDNAME + 1];
351 	int res, result = 0;
352 	struct timeval tv1, tv2, tv3;
353 	size_t len;
354 	enum spf_status status;
355 
356 	if (conf.c_debug)
357 		gettimeofday(&tv1, NULL);
358 
359 	if ((spf_server = SPF_server_new(SPF_DNS_CACHE, 0)) == NULL) {
360 		mg_log(LOG_ERR, "SPF_server_new failed");
361 		goto out1;
362 	}
363 
364 	if ((spf_request = SPF_request_new(spf_server)) == NULL) {
365 		mg_log(LOG_ERR, "SPF_request_new failed");
366 		goto out2;
367 	}
368 
369 	/* Local machine FQDN */
370 	fqdn = smfi_getsymval(priv->priv_ctx, "{j}");
371 	if (fqdn != NULL && SPF_server_set_rec_dom(spf_server, fqdn) != 0)
372 		mg_log(LOG_WARNING, "SPF_server_set_rec_dom failed");
373 
374 	/*
375 	 * Get the IP address
376 	 */
377 	switch (sa->sa_family) {
378 	case AF_INET:
379 		res = SPF_request_set_ipv4(spf_request, *SADDR4(sa));
380 		break;
381 #ifdef AF_INET6
382 	case AF_INET6:
383 		res = SPF_request_set_ipv6(spf_request, *SADDR6(sa));
384 		break;
385 #endif
386 	default:
387 		mg_log(LOG_ERR, "unknown address family %d", sa->sa_family);
388 		goto out3;
389 	}
390 	if (res != 0) {
391 		mg_log(LOG_ERR, "SPF_request_set_ip_str failed");
392 		goto out3;
393 	}
394 
395 	/* HELO string */
396 	if (SPF_request_set_helo_dom(spf_request, helo) != 0) {
397 		mg_log(LOG_ERR, "SPF_request_set_helo_dom failed");
398 		goto out3;
399 	}
400 
401 	/*
402 	 * And the enveloppe source e-mail
403 	 */
404 	if (fromp[0] == '<')
405 		fromp++; /* strip leading < */
406 	strncpy(from, fromp, NS_MAXDNAME);
407 	from[NS_MAXDNAME] = '\0';
408 	len = strlen(from);
409 	if (fromp[len - 1] == '>')
410 		from[len - 1] = '\0'; /* strip trailing > */
411 
412 	if (SPF_request_set_env_from(spf_request, from) != 0) {
413 		mg_log(LOG_ERR, "SPF_request_set_env_from failed");
414 		goto out3;
415 	}
416 
417 	/*
418 	 * Get the SPF result
419 	 */
420 	SPF_request_query_mailfrom(spf_request, &spf_response);
421 	res = SPF_response_result(spf_response);
422 
423 	status = ad ? *(enum spf_status *)ad : MGSPF_PASS;
424 	switch (res) {
425 	case SPF_RESULT_PASS:
426 		result = (status == MGSPF_PASS);
427 		strcpy(priv->priv_spf_result, "pass");
428 		break;
429 	case SPF_RESULT_FAIL:
430 		result = (status == MGSPF_FAIL);
431 		strcpy(priv->priv_spf_result, "fail");
432 		break;
433 	case SPF_RESULT_SOFTFAIL:
434 		result = (status == MGSPF_SOFTFAIL);
435 		strcpy(priv->priv_spf_result, "softfail");
436 		break;
437 	case SPF_RESULT_NEUTRAL:
438 		result = (status == MGSPF_NEUTRAL);
439 		strcpy(priv->priv_spf_result, "neutral");
440 		break;
441 	case SPF_RESULT_INVALID:
442 		result = (status == MGSPF_UNKNOWN);
443 		strcpy(priv->priv_spf_result, "unknown");
444 		break;
445 	case SPF_RESULT_PERMERROR:
446 		result = (status == MGSPF_ERROR);
447 		strcpy(priv->priv_spf_result, "permerror");
448 		break;
449 	case SPF_RESULT_TEMPERROR:
450 		result = (status == MGSPF_ERROR);
451 		strcpy(priv->priv_spf_result, "temperror");
452 		break;
453 	case SPF_RESULT_NONE:
454 		result = (status == MGSPF_NONE);
455 		strcpy(priv->priv_spf_result, "none");
456 		break;
457 	default:
458 		mg_log(LOG_ERR, "Internal error: unexpected spf2 result %d",
459 			" spf test code %d", res, status);
460 		exit(EX_SOFTWARE);
461 		break;
462 	}
463 
464 	if (conf.c_debug)
465 		mg_log(LOG_DEBUG, "SPF return %s (test code %d, result %d)",
466 			priv->priv_spf_result, res, result);
467 
468 	if (priv->priv_spf_header != NULL)
469 		free(priv->priv_spf_header);
470 
471 
472 	if (spf_response->received_spf_value)
473 		priv->priv_spf_header =
474 		    strdup(spf_response->received_spf_value);
475 	else
476 		priv->priv_spf_header = NULL;
477 
478 	SPF_response_free(spf_response);
479 out3:
480 	SPF_request_free(spf_request);
481 out2:
482 	SPF_server_free(spf_server);
483 out1:
484 	if (conf.c_debug) {
485 		gettimeofday(&tv2, NULL);
486 		timersub(&tv2, &tv1, &tv3);
487 		mg_log(LOG_DEBUG, "SPF lookup performed in %ld.%06lds",
488 		    tv3.tv_sec, tv3.tv_usec);
489 	}
490 
491 	return result;
492 }
493 
494 
495 #endif /* HAVE_SPF2 */
496 
497 
498 
499 #if (defined(HAVE_SPF) || defined(HAVE_SPF_ALT) || \
500      defined(HAVE_SPF2_10) || defined(HAVE_SPF2))
501 char *
acl_print_spf(ad,buf,len)502 acl_print_spf(ad, buf, len)
503 	acl_data_t *ad;
504 	char *buf;
505 	size_t len;
506 {
507 	char *tmpstr;
508 	enum spf_status status;
509 
510 	status = ad ? *(enum spf_status *)ad : MGSPF_PASS;
511 	switch (status) {
512 	case MGSPF_PASS:
513 		tmpstr = "pass";
514 		break;
515 	case MGSPF_FAIL:
516 		tmpstr = "fail";
517 		break;
518 	case MGSPF_SOFTFAIL:
519 		tmpstr = "softfail";
520 		break;
521 	case MGSPF_NEUTRAL:
522 		tmpstr = "neutral";
523 		break;
524 	case MGSPF_UNKNOWN:
525 		tmpstr = "unknown";
526 		break;
527 	case MGSPF_ERROR:
528 		tmpstr = "error";
529 		break;
530 	case MGSPF_NONE:
531 		tmpstr = "none";
532 		break;
533 	case MGSPF_SELF:
534 		tmpstr = "self";
535 		break;
536 	default:
537 		mg_log(LOG_ERR, "Internal error: unexpected spf_status %d",
538 			status);
539 		exit(EX_SOFTWARE);
540 		break;
541 	}
542 	snprintf(buf, len, "%s", tmpstr);
543 	return buf;
544 }
545 
546 void
acl_add_spf(ad,data)547 acl_add_spf(ad, data)
548 	acl_data_t *ad;
549 	void *data;
550 {
551 	ad->spf_status = *(enum spf_status *)data;
552 	return;
553 }
554 
555 int
spf_check(ad,as,ap,priv)556 spf_check(ad, as, ap, priv)
557 	acl_data_t *ad;
558 	acl_stage_t as;
559 	struct acl_param *ap;
560 	struct mlfi_priv *priv;
561 {
562 	enum spf_status status;
563 
564 	status = ad ? *(enum spf_status *)ad : MGSPF_PASS;
565 	switch (status) {
566 	case MGSPF_PASS:
567 	case MGSPF_FAIL:
568 	case MGSPF_SOFTFAIL:
569 	case MGSPF_NEUTRAL:
570 	case MGSPF_UNKNOWN:
571 	case MGSPF_ERROR:
572 	case MGSPF_NONE:
573 		return spf_check_internal(ad, as, ap, priv);
574 		break;
575 	case MGSPF_SELF:
576 		return spf_check_self(ad, as, ap, priv);
577 		break;
578 	default:
579 		break;
580 	}
581 
582 	mg_log(LOG_ERR, "Internal error: unexpected spf_status %d",
583 		status);
584 	exit(EX_SOFTWARE);
585 
586 	return 0;
587 }
588 
589 /*
590  * Check if the SPF record is wide open: the simpliest
591  * way of doing it is to check whether our own IP
592  * validates the record.
593  * That is a problem for Postfix and CommSuite installations,
594  * as the local IP is not available ({if_addr} milter macro).
595  * For now such MTAs must define "localaddr" in config.
596  */
597 static int
spf_check_self(ad,as,ap,priv)598 spf_check_self(ad, as, ap, priv)
599 	acl_data_t *ad;
600 	acl_stage_t as;
601 	struct acl_param *ap;
602 	struct mlfi_priv *priv;
603 {
604 	int retval = 0;
605 	sockaddr_t saved_addr;
606 	acl_data_t tmp_ad;
607 	char *ip;
608 
609 	(void)memcpy(&saved_addr, &priv->priv_addr, sizeof(saved_addr));
610 
611 	switch (conf.c_localaddr.ss_family) {
612 	case AF_INET:
613 		(void)memcpy(&priv->priv_addr, &conf.c_localaddr,
614 			     sizeof(struct sockaddr_in));
615 		break;
616 #ifdef AF_INET6
617 	case AF_INET6:
618 		(void)memcpy(&priv->priv_addr, &conf.c_localaddr,
619 			     sizeof(struct sockaddr_in6));
620 		break;
621 #endif /* AF_INET6 */
622 	default:	/* localaddr unspecified, try fallback to {if_addr} */
623 		ip = local_ipstr(priv);
624 		if (strcmp(ip, "0.0.0.0") == 0) {
625 			mg_log(LOG_ERR,
626 				"spf self used without localaddr specified "
627 				"and no {if_addr} macro is available");
628 			return 0;
629 		}
630 		if (strncmp(ip, "IPv6:", strlen("IPv6:")) == 0) {
631 #ifdef AF_INET6
632 			if (inet_pton(AF_INET6, ip + strlen("IPv6:"),
633 				    SADDR6(SA(&priv->priv_addr))) <= 0) {
634 				mg_log(LOG_ERR,
635 				       "Invalid IPv6 local address %s", ip);
636 				exit(EX_SOFTWARE);
637 			}
638 #else /* AF_INET6 */
639 			mg_log(LOG_ERR,
640 			    "IPv6 support not compiled but local IP is IPv6");
641 			exit(EX_SOFTWARE);
642 #endif /* AF_INET6 */
643 		} else {
644 			if (inet_pton(AF_INET, ip,
645 				    SADDR4(SA(&priv->priv_addr))) <= 0) {
646 				mg_log(LOG_ERR,
647 				       "Invalid IPv4 local address %s", ip);
648 				exit(EX_SOFTWARE);
649 			}
650 		}
651 		break;
652 	}
653 
654 	if (ad == NULL)
655 		(void)memset(&tmp_ad, 0, sizeof(tmp_ad));
656 	else
657 		(void)memcpy(&tmp_ad, ad, sizeof(tmp_ad));
658 
659 	tmp_ad.spf_status = MGSPF_PASS;
660 
661 	retval = spf_check_internal(&tmp_ad, as, ap, priv);
662 
663 	(void)memcpy(&priv->priv_addr, &saved_addr, sizeof(priv->priv_addr));
664 
665 	return retval;
666 }
667 
668 #endif
669