1 /********************************************************************
2 ** OPENDMARC_SPF.C --  Process the spf record of the inbound message
3 **********************************************************************/
4 # include "opendmarc_internal.h"
5 
6 /* libbsd if found */
7 #ifdef USE_BSD_H
8 # include <bsd/string.h>
9 #endif /* USE_BSD_H */
10 
11 /* libstrl if needed */
12 #ifdef USE_STRL_H
13 # include <strl.h>
14 #endif /* USE_STRL_H */
15 
16 /* opendmarc_strl if needed */
17 #ifdef USE_DMARCSTRL_H
18 # include <opendmarc_strl.h>
19 #endif /* USE_DMARCSTRL_H */
20 
21 # include "dmarc.h"
22 
23 #if WITH_SPF
24 
25 #if HAVE_SPF2_H
26 // Here we have spf.h, so libspf2 is available.
27 
28 SPF_CTX_T *
opendmarc_spf2_alloc_ctx()29 opendmarc_spf2_alloc_ctx()
30 {
31 	SPF_CTX_T *spfctx = NULL;
32 
33 	spfctx = malloc(sizeof(SPF_CTX_T));
34 	if (spfctx == NULL)
35 		return NULL;
36 	(void) memset(spfctx, '\0', sizeof(SPF_CTX_T));
37 	spfctx->spf_server = SPF_server_new(SPF_DNS_CACHE, 0);
38 	spfctx->spf_request = SPF_request_new(spfctx->spf_server);
39 	return spfctx;
40 }
41 
42 SPF_CTX_T *
opendmarc_spf2_free_ctx(SPF_CTX_T * spfctx)43 opendmarc_spf2_free_ctx(SPF_CTX_T *spfctx)
44 {
45 	if (spfctx == NULL)
46 		return spfctx;
47 
48 	if (spfctx->spf_response != NULL)
49 		SPF_response_free(spfctx->spf_response);
50 	if (spfctx->spf_request != NULL)
51 		SPF_request_free(spfctx->spf_request);
52 	if (spfctx->spf_server != NULL)
53 		SPF_server_free(spfctx->spf_server);
54 	(void) free(spfctx);
55 	spfctx = NULL;
56 	return spfctx;
57 }
58 
59 int
opendmarc_spf2_find_mailfrom_domain(SPF_CTX_T * spfctx,char * raw_address,char * mailfrom,size_t mailfrom_len,int * use_flag)60 opendmarc_spf2_find_mailfrom_domain(SPF_CTX_T *spfctx, char *raw_address, char *mailfrom, size_t mailfrom_len, int *use_flag)
61 {
62 	char copy[sizeof spfctx->mailfrom_addr];
63 	char *cp;
64 	char *ep;
65 
66 	if (use_flag != NULL)
67 		*use_flag = FALSE;
68 
69 	if (spfctx == NULL)
70 		return EINVAL;
71 
72 	if (mailfrom == NULL || raw_address == NULL)
73 		return EINVAL;
74 
75 	(void) memset(copy, '\0', sizeof copy);
76 	(void) strlcpy(copy, raw_address, sizeof copy);
77 
78 	cp = strrchr(copy, '<');
79 	if (cp == NULL)
80 		cp = copy;
81 	else
82 		++cp;
83 	ep = strchr(cp, '>');
84 	if (ep != NULL)
85 		*ep = '\0';
86 
87 	ep = strchr(cp, '@');
88 	if (ep != NULL)
89 	{
90 		cp = ep+1;
91 		if (use_flag != NULL)
92 			*use_flag = TRUE;
93 	}
94 
95 	if (strcasecmp(cp, "MAILER_DAEMON") == 0)
96 		cp = "";
97 
98 	(void) memset(mailfrom, '\0', mailfrom_len);
99 	(void) strlcpy(mailfrom, cp, mailfrom_len);
100 	return 0;
101 }
102 
103 int
opendmarc_spf2_specify_ip_address(SPF_CTX_T * spfctx,char * ip_address,size_t ip_address_len)104 opendmarc_spf2_specify_ip_address(SPF_CTX_T *spfctx, char *ip_address, size_t ip_address_len)
105 {
106 	if (spfctx == NULL)
107 		return EINVAL;
108 
109 	if (ip_address == NULL)
110 		return EINVAL;
111 
112 	/*
113 	 * we don't care at this point if it is ipv6 or ipv4
114 	 */
115 	SPF_request_set_ipv4_str(spfctx->spf_request, ip_address);
116 	SPF_request_set_ipv6_str(spfctx->spf_request, ip_address);
117 	return 0;
118 }
119 
120 int
opendmarc_spf2_test(char * ip_address,char * mail_from_domain,char * helo_domain,char * spf_record,int softfail_okay_flag,char * human_readable,size_t human_readable_len,int * used_mfrom)121 opendmarc_spf2_test(char *ip_address, char *mail_from_domain, char *helo_domain, char *spf_record, int softfail_okay_flag, char *human_readable, size_t human_readable_len, int *used_mfrom)
122 {
123 	SPF_CTX_T *	ctx;
124 	int		ret;
125 	char 		xbuf[BUFSIZ];
126 	char		helo[512];
127 	char		mfrom[512];
128 
129 	if (used_mfrom != NULL)
130 		*used_mfrom = FALSE;
131 
132 	(void) memset(xbuf, '\0', sizeof xbuf);
133 	ctx = opendmarc_spf2_alloc_ctx();
134 	if (ctx == NULL)
135 	{
136 		if (human_readable != NULL)
137 			(void) strlcpy(human_readable, strerror(errno), human_readable_len);
138 		return DMARC_POLICY_SPF_OUTCOME_TMPFAIL;
139 	}
140 
141 	if (ip_address == NULL)
142 	{
143 		if (human_readable != NULL)
144 			(void) strlcpy(human_readable, "No IP address available", human_readable_len);
145 		ctx = opendmarc_spf2_free_ctx(ctx);
146 		return DMARC_POLICY_SPF_OUTCOME_FAIL;
147 	}
148 
149 	if (mail_from_domain == NULL && helo_domain == NULL)
150 	{
151 		if (human_readable != NULL)
152 			(void) strlcpy(human_readable, "No Domain name available to check", human_readable_len);
153 		ctx = opendmarc_spf2_free_ctx(ctx);
154 		return DMARC_POLICY_SPF_OUTCOME_FAIL;
155 	}
156 
157 	ret = opendmarc_spf2_specify_ip_address(ctx, ip_address, strlen(ip_address));
158 	if (ret != 0)
159 	{
160 		if (human_readable != NULL)
161 			(void) strlcpy(human_readable, strerror(errno), human_readable_len);
162 		ctx = opendmarc_spf2_free_ctx(ctx);
163 		return DMARC_POLICY_SPF_OUTCOME_TMPFAIL;
164 	}
165 
166 	ret = opendmarc_spf2_find_mailfrom_domain(ctx, mail_from_domain, mfrom, sizeof mfrom, used_mfrom);
167 	if (ret != 0 || *used_mfrom == FALSE)
168 	{
169 		(void) strlcpy(helo, helo_domain, sizeof helo);
170 		SPF_request_set_helo_dom(ctx->spf_request, helo);
171 	}
172 	else
173 	{
174 		SPF_request_set_env_from(ctx->spf_request, mfrom);
175 	}
176 	ctx->spf_response = NULL;
177 	SPF_request_query_mailfrom(ctx->spf_request, &(ctx->spf_response));
178 
179 	if (human_readable != NULL)
180 		(void) strlcpy(human_readable, SPF_strresult(SPF_response_result(ctx->spf_response)), human_readable_len);
181 	ctx->spf_result = SPF_response_result(ctx->spf_response);
182 	ret = (int) ctx->spf_result;
183 	ctx = opendmarc_spf2_free_ctx(ctx);
184 
185 	if (ret != SPF_RESULT_PASS)
186 	{
187 		switch (ret)
188 		{
189 		    case SPF_RESULT_NONE:
190 			return DMARC_POLICY_SPF_OUTCOME_NONE;
191 
192 		    case SPF_RESULT_NEUTRAL:
193 		    case SPF_RESULT_SOFTFAIL:
194 			if (softfail_okay_flag == TRUE)
195 				return DMARC_POLICY_SPF_OUTCOME_PASS;
196 			else
197 				return DMARC_POLICY_SPF_OUTCOME_FAIL;
198 			break;
199 		    case SPF_RESULT_TEMPERROR:
200 			return DMARC_POLICY_SPF_OUTCOME_TMPFAIL;
201 		}
202 		return DMARC_POLICY_SPF_OUTCOME_FAIL;
203 	}
204 	return DMARC_POLICY_SPF_OUTCOME_PASS;
205 }
206 
207 #else /* HAVE_SPF2_H */
208 
209 // No spf.h so no libspf2 to use so we use the internal spf check.
210 #ifndef TRUE
211 # define TRUE (1)
212 #endif
213 
214 #ifndef FALSE
215 # define FALSE (0)
216 #endif
217 
218 #ifndef MAXDNSHOSTNAME
219 # define MAXDNSHOSTNAME (256)
220 #endif
221 #define SPF_MAX_SPF_RECORD_LEN  (4096)
222 
223 #define SPF_IN_TOKEN_NONE	(0)
224 #define SPF_IN_TOKEN_VERSION	(1)
225 #define SPF_IN_TOKEN_A		(2)
226 #define SPF_IN_TOKEN_MX	(3)
227 #define SPF_IN_TOKEN_IP4	(4)
228 #define SPF_IN_TOKEN_IP6	(5)
229 #define SPF_IN_TOKEN_PTR	(6)
230 #define SPF_IN_TOKEN_INCLUDE	(7)
231 #define SPF_IN_TOKEN_REDIRECT	(8)
232 #define SPF_IN_TOKEN_EXISTS	(9)
233 #define SPF_IN_TOKEN_EXP	(10)
234 
235 const char *
opendmarc_spf_status_to_msg(SPF_CTX_T * spfctx,int status)236 opendmarc_spf_status_to_msg(SPF_CTX_T *spfctx, int status)
237 {
238 	const char *r;
239 
240 	if (status != 0 && spfctx != NULL && spfctx->did_get_exp)
241 		return spfctx->exp_buf;
242 
243 	switch (status)
244 	{
245 #define SPF_RETURN_UNDECIDED            (-1)
246 	    case SPF_RETURN_UNDECIDED:
247 		r = "Undecided";
248 		break;
249 
250 #define SPF_RETURN_OK_PASSED            (0)
251 	    case SPF_RETURN_OK_PASSED:
252 		r = "Passed";
253 		break;
254 
255 #define SPF_RETURN_INTERNAL	       (5)
256 	    case SPF_RETURN_INTERNAL:
257 		r = "No Domain To Check";
258 		break;
259 
260 #define SPF_RETURN_RECORD_TOOLONG       (6)
261 	    case SPF_RETURN_RECORD_TOOLONG:
262 		r = "Record Too Big";
263 		break;
264 #define SPF_RETURN_BAD_SYNTAX_VERSION		(7)
265 	    case SPF_RETURN_BAD_SYNTAX_VERSION:
266 		r = "Bad Version";
267 		break;
268 #define SPF_RETURN_A_BUT_NO_A_RECORD    (8)
269 	    case SPF_RETURN_A_BUT_NO_A_RECORD:
270 		r = "Required 'A' lookup failed to find 'A' records";
271 		break;
272 #define SPF_RETURN_DASH_FORCED_HARD_FAIL    (9)
273 	    case SPF_RETURN_DASH_FORCED_HARD_FAIL:
274 		r = "Required 'A' bu no 'A' records with -a specified";
275 		break;
276 #define SPF_RETURN_A_BUT_BAD_SYNTAX     (10)
277 	    case SPF_RETURN_A_BUT_BAD_SYNTAX:
278 		r = "IP Address Badly Formed";
279 		break;
280 #define SPF_RETURN_BAD_SYNTAX_INCLUDE   (11)
281 	    case SPF_RETURN_BAD_SYNTAX_INCLUDE:
282 		r = "'INCLUDE' Syntax Error";
283 		break;
284 #define SPF_RETURN_INCLUDE_NO_DOMAIN    (12)
285 	    case SPF_RETURN_INCLUDE_NO_DOMAIN:
286 		r = "'INCLUDE' Domain Lookup Failed";
287 		break;
288 #define SPF_RETURN_BAD_SYNTAX_REDIRECT  (13)
289 	    case SPF_RETURN_BAD_SYNTAX_REDIRECT:
290 		r = "'REDIRECT' Syntax Error";
291 		break;
292 #define SPF_RETURN_REDIRECT_NO_DOMAIN   (14)
293 	    case SPF_RETURN_REDIRECT_NO_DOMAIN:
294 		r = "'REDIRECT' Domain Lookup Failed";
295 		break;
296 #define SPF_RETURN_DASH_ALL_HARD_FAIL   (15)
297 	    case SPF_RETURN_DASH_ALL_HARD_FAIL:
298 		r = "Hard Fail: Reject";
299 		break;
300 #define SPF_RETURN_TILDE_ALL_SOFT_FAIL  (16)
301 	    case SPF_RETURN_TILDE_ALL_SOFT_FAIL:
302 		r = "Soft Fail: Subject to Policy";
303 		break;
304 #define SPF_RETURN_QMARK_ALL_NEUTRAL    (17)
305 	    case SPF_RETURN_QMARK_ALL_NEUTRAL:
306 		r = "Neutral Fail: Subject to Policy";
307 		break;
308 #define SPF_RETURN_UNKNOWN_KEYWORD      (18)
309 	    case SPF_RETURN_UNKNOWN_KEYWORD:
310 		r = "Unrecognized Keyword";
311 		break;
312 #define SPF_RETURN_BAD_MACRO_SYNTAX     (19)
313 	    case SPF_RETURN_BAD_MACRO_SYNTAX:
314 		r = "Macros Used But Syntax Bad";
315 		break;
316 #define SPF_RETURN_NOT_EXISTS_HARDFAIL     (20)
317 	    case SPF_RETURN_NOT_EXISTS_HARDFAIL:
318 		r = "'A' Record lookup, No Such Host";
319 		break;
320 #define SPF_RETURN_BAD_SYNTAX_EXISTS     (21)
321 	    case SPF_RETURN_BAD_SYNTAX_EXISTS:
322 		r = "'EXISTS' Omitted A domain";
323 		break;
324 #define SPF_RETURN_BAD_SYNTAX_EXP     (22)
325 	    case SPF_RETURN_BAD_SYNTAX_EXP:
326 		r = "'EXP' Bad Syntax";
327 		break;
328 #define SPF_RETURN_NOT_EXP_HARDFAIL     (23)
329 	    case SPF_RETURN_NOT_EXP_HARDFAIL:
330 		r = "'-EXP' Hard Failure";
331 		break;
332 #define SPF_RETURN_TOO_MANY_DNS_QUERIES     (24)
333 	    case SPF_RETURN_TOO_MANY_DNS_QUERIES:
334 		r = "Too Many DNS Lookups Without Success.";
335 		break;
336 	    default:
337 #define SPF_RETURN_INTERNAL_ERROR	(25)
338 		r = "Undefined Internal Error";
339 		break;
340 	}
341 	return r;
342 }
343 
344 /****************************************************************
345 ** SPF_STATUS_TO_PASS -- convert ctx->status into a decision
346 **	Returns 1 for pass
347 **	Returns 0 for fail
348 **	Returns -1 for maybe fail (~all means you decide)
349 ****************************************************************/
350 int
opendmarc_spf_status_to_pass(int status,int none_pass)351 opendmarc_spf_status_to_pass(int status, int none_pass)
352 {
353 	int r;
354 
355 	switch (status)
356 	{
357 	    case SPF_RETURN_UNDECIDED:
358 		if (none_pass == 1)
359 			r = 1;
360 		else
361 			r = 0;
362 		break;
363 	    case SPF_RETURN_OK_PASSED:
364 		r = 1;
365 		break;
366 	    case SPF_RETURN_INTERNAL:
367 		r = 0;
368 		break;
369 	    case SPF_RETURN_RECORD_TOOLONG:
370 		r = 0;
371 		break;
372 	    case SPF_RETURN_BAD_SYNTAX_VERSION:
373 		r = 0;
374 		break;
375 	    case SPF_RETURN_A_BUT_NO_A_RECORD:
376 		r = 0;
377 		break;
378 	    case SPF_RETURN_DASH_FORCED_HARD_FAIL:
379 		r = 0;
380 		break;
381 	    case SPF_RETURN_A_BUT_BAD_SYNTAX:
382 		r = 0;
383 		break;
384 	    case SPF_RETURN_BAD_SYNTAX_INCLUDE:
385 		r = 0;
386 		break;
387 	    case SPF_RETURN_INCLUDE_NO_DOMAIN:
388 		r = 0;
389 		break;
390 	    case SPF_RETURN_BAD_SYNTAX_REDIRECT:
391 		r = 0;
392 		break;
393 	    case SPF_RETURN_REDIRECT_NO_DOMAIN:
394 		r = 0;
395 		break;
396 	    case SPF_RETURN_DASH_ALL_HARD_FAIL:
397 		r = 0;
398 		break;
399 	    case SPF_RETURN_TILDE_ALL_SOFT_FAIL:
400 		r = -1;
401 		break;
402 	    case SPF_RETURN_QMARK_ALL_NEUTRAL:
403 		r = 1;
404 		break;
405 	    case SPF_RETURN_UNKNOWN_KEYWORD:
406 		r = 0;
407 		break;
408 	    case SPF_RETURN_BAD_MACRO_SYNTAX:
409 		r = 0;
410 		break;
411 	    case SPF_RETURN_NOT_EXISTS_HARDFAIL:
412 		r = 0;
413 		break;
414 	    case SPF_RETURN_BAD_SYNTAX_EXISTS:
415 		r = 0;
416 		break;
417 	    case SPF_RETURN_BAD_SYNTAX_EXP:
418 		r = 0;
419 		break;
420 	    case SPF_RETURN_TOO_MANY_DNS_QUERIES:
421 		r = 0;
422 		break;
423 	    case SPF_RETURN_INTERNAL_ERROR:
424 		r = 0;
425 		break;
426 	    default:
427 		r = 0;
428 		break;
429 	}
430 	return r;
431 }
432 
433 /*
434 **  OPENDMARC_SPF_CIDR_ADDRESS -- see if an IP address is covered by a CIDR
435 **  	expression
436 **
437 **  Parameters:
438 **  	ip -- IP address to test, in network byte order
439 **  	cidr_addr -- CIDR expression to which to compare it
440 **
441 **  Return value:
442 **  	TRUE iff "ip" is inside (or equal to) "cidr_addr".
443 */
444 
445 int
opendmarc_spf_cidr_address(uint32_t ip,char * cidr_addr)446 opendmarc_spf_cidr_address(uint32_t ip, char *cidr_addr)
447 {
448 	char *cidr;
449 	char *cp, *ep;
450 	char buf[BUFSIZ];
451 	uint32_t i;
452 	uint32_t bits;
453 	uint32_t mask;
454 	uint32_t high, low;
455 	struct sockaddr_in sin;
456 
457 	if (cidr_addr == NULL)
458 		return FALSE;
459 
460 	(void) memset(buf, '\0', sizeof buf);
461 	(void) strlcpy(buf, cidr_addr, sizeof buf);
462 
463 	cidr = strchr(buf, '/');
464 	if (cidr == NULL)
465 	{
466 		if (inet_aton(cidr_addr, &sin.sin_addr) != 0)
467 		{
468 			(void)memcpy(&low, &sin.sin_addr.s_addr, sizeof(sin.sin_addr.s_addr));
469 			(void)memcpy(&high, &sin.sin_addr.s_addr, sizeof(sin.sin_addr.s_addr));
470 			if (ip >= low && ip <= high)
471 				return TRUE;
472 		}
473 		return FALSE;
474 	}
475 	*cidr++ = '\0';
476 	bits = strtoul(cidr, NULL, 10);
477 
478 	cp = buf;
479 	ep = strchr(buf, '.');
480 	if (ep == NULL)
481 		return FALSE;
482 	*ep++ = '\0';
483 	i = strtoul(cp, NULL, 10) << 24;
484 
485 	cp = ep;
486 	ep = strchr(cp, '.');
487 	if (ep == NULL)
488 		return FALSE;
489 	*ep++ = '\0';
490 	i += strtoul(cp, NULL, 10) << 16;
491 
492 	cp = ep;
493 	ep = strchr(cp, '.');
494 	if (ep == NULL)
495 		return FALSE;
496 	*ep++ = '\0';
497 	i += strtoul(cp, NULL, 10) << 8;
498 
499 	cp = ep;
500 	i += strtoul(cp, NULL, 10);
501 
502 	mask = (bits == 0) ? 0 : ~(u_long)0 << (32 - bits);
503 
504 	low = i & mask;
505 	high = i | (~mask & 0xFFFFFFFF);
506 
507 	ip = ntohl(ip);
508 	if (ip >= low && ip <= high)
509 		return TRUE;
510 	return FALSE;
511 }
512 
513 /**************************************************************
514 ** opendmarc_spf_reverse -- Reverse doman name on dot or ip address
515 **			on dot or colon	boundaries
516 **			   e.g. a.b.c becomes c.b.a
517 **			   and FFFF::EEEE becomes EEEE::FFFF
518 **			   and 12.34.56.78 becomes 78.56.34.12
519 ** Input:
520 **	str	-- the string to reverse
521 **	buf	-- buffer to hold the reversed string
522 **	buflen	-- size in bytes of the buffer
523 ** Returns:
524 **	NULL	On error
525 **	buf	On success
526 ** Side Effects:
527 **	Overwrites previous contents of buf
528 ****************************************************************/
529 static char *
opendmarc_spf_reverse(char * str,char * buf,size_t buflen)530 opendmarc_spf_reverse(char *str, char *buf, size_t buflen)
531 {
532 	char *	sp;
533 	char *	ep;
534 	char *	dotp;
535 	int	dotorcolon = 0;
536 	char	dotorcolon_str[2];
537 	char	dupe[BUFSIZ];
538 
539 	if (str == NULL || buf == NULL || buflen == 0)
540 		return NULL;
541 	if (buflen > BUFSIZ)
542 		buflen = BUFSIZ;
543 
544 	(void) memset(buf, '\0', buflen);
545 	(void) memset(dupe, '\0', buflen + 1);
546 	(void) strlcpy(dupe, str, buflen + 1);
547 
548 	dotp = strchr(dupe, '.');
549 	if (dotp != NULL)
550 		dotorcolon = '.';
551 	else
552 	{
553 		dotp = strchr(dupe, ':');
554 		if (dotp != NULL)
555 			dotorcolon = ':';
556 	}
557 	if (dotorcolon == 0)
558 		return NULL;
559 
560 	dotorcolon_str[0] = dotorcolon;
561 	dotorcolon_str[1] = '\0';
562 
563 	ep = dupe + strlen(dupe);
564 	/*
565 	 * strip tailing dotorcolons.
566 	 */
567 	do
568 	{
569 		for (sp = ep; sp >= dupe; --sp)
570 			if (*sp == dotorcolon)
571 				break;
572 		if (sp < dupe)
573 		{
574 			strlcat(buf, sp+1, buflen);
575 			break;
576 		}
577 		ep = sp;
578 		if (*sp == dotorcolon)
579 			++sp;
580 		if (*sp != '\0')
581 			strlcat(buf, sp, buflen);
582 		if (*ep == dotorcolon)
583 		{
584 			strlcat(buf, dotorcolon_str, buflen);
585 			*ep = '\0';
586 			--ep;
587 		}
588 	} while (sp >= dupe);
589 	return buf;
590 }
591 
592 typedef struct {
593 	u_int   values[8];
594 	int     nvalues;
595 } INT_ARY_T;
596 
597 typedef struct {
598         char    strs[8][32];
599 	int     nstrs;
600 } TXT_ARY_T;
601 
602 
603 static int
opendmarc_spf_ipv6_cidr_populate(TXT_ARY_T * lp,INT_ARY_T * ap)604 opendmarc_spf_ipv6_cidr_populate(TXT_ARY_T *lp, INT_ARY_T *ap)
605 {
606 	int i, f;
607 	int dots;
608 
609 	/*
610 	 * Populate the base, expected variations are:
611 	 *	::ipv4
612 	 *	:FFFF:ipv4
613 	 *	:hex::hex: ...
614 	 */
615 
616 	/* Clear to zero in case we got an auto array */
617 	for (i = 0; i < 8; ++i)
618 		ap->values[i] = 0;
619 
620 	f = 0; /* index into the output array of values. */
621 	for (i = 0; i < lp->nstrs; ++i)
622 	{
623 		int value;
624 		int value2;
625 		char *cp = NULL;
626 
627 		dots = 0;
628 		if (i == 0)
629 		{
630 			char *dp;
631 
632 			/*
633 			 * The rightmost address might be IPv4
634 			 */
635 			for (dp = lp->strs[0]; *dp != '\0'; ++dp)
636 				if (*dp == '.')
637 					++dots;
638 		}
639 
640 		if (i >= 0 || dots == 0)
641 		{
642 			/*
643 			 * No dots or not the rightmost value, then
644 			 * a hexedecimal value.
645 			 */
646 			if (lp->strs[i] != NULL)
647 			{
648 			ap->values[f] = strtoul(lp->strs[i], NULL, 16);
649 			f = f + 1;
650 			}
651 			continue;
652 		}
653 
654 		/*
655 		 * From here down deals with the special case of
656 		 * an ipv4 address at the righthand side.
657 		 */
658 		if (dots > 3)
659 		{
660 			return errno = EINVAL;
661 		}
662 		if (dots)
663 		{
664 			cp = strrchr(lp->strs[0], '.');
665 			value2 = strtoul(cp+1, NULL, 10);
666 
667 			for (cp = cp-1; cp > lp->strs[0]; --cp)
668 				if (*cp == '.')
669 					break;
670 			if (*cp == '.')
671 				value = strtoul(cp+1, NULL, 10);
672 			else
673 				value = strtoul(cp, NULL, 10);
674 			value <<= 8;
675 			value &= 0xFFFF;
676 			value += value2;
677 			ap->values[0] = value;
678 			ap->values[1] = 0;
679 		}
680 		if (dots > 1)
681 		{
682 			for (cp = cp-1; cp > lp->strs[0]; --cp)
683 				if (*cp == '.')
684 					break;
685 			if (*cp == '.')
686 				value = strtoul(cp+1, NULL, 10);
687 			else
688 				value = strtoul(cp+1, NULL, 10);
689 			ap->values[1] = value;
690 		}
691 		if (dots > 2)
692 		{
693 			cp = lp->strs[0];
694 			value = strtoul(cp, NULL, 10);
695 			value <<= 8;
696 			ap->values[1] += value;
697 		}
698 		f += 2;
699 		continue;
700 	}
701 	ap->nvalues = f;
702 	return 0;
703 }
704 
705 static int
opendmarc_spf_ipv6_explode(char * str,TXT_ARY_T * ap)706 opendmarc_spf_ipv6_explode(char *str, TXT_ARY_T *ap)
707 {
708 	char *cp, *ep;
709 	int i;
710 	int ncolons;
711 	char copy[128];
712 
713 	if (str == NULL || ap == NULL)
714 		return errno  = EINVAL;
715 
716 	(void) memset(ap, '\0', sizeof(TXT_ARY_T));
717 	(void) memset(copy, '\0', sizeof copy);
718 	(void) strlcpy(copy, str, sizeof copy);
719 
720 	ncolons = 0;
721 	for (cp = copy; *cp != '\0'; ++cp)
722 		if (*cp == ':')
723 			++ncolons;
724 	ncolons = 7 - ncolons;
725 
726 	cp = copy;
727 	for (i = 7; i >= 0; i--)
728 	{
729 		ep = strchr(cp, ':');
730 		if (ep != NULL)
731 			*ep = '\0';
732 		if (strlen(cp) == 0)
733 		{
734 			(void) strlcpy((char *)ap->strs[i], "0", sizeof ap->strs[i]);
735 		}
736 		else
737 		{
738 			(void) strlcpy((char *)ap->strs[i], cp, sizeof ap->strs[i]);
739 		}
740 		if (ep && *(ep + 1) == ':' && ncolons > 0)
741 		{
742 			for (i--; i >= 0 && ncolons != 0; --ncolons, --i)
743 			{
744 				(void) strlcpy((char *)ap->strs[i], "0", sizeof ap->strs[i]);
745 			}
746 			i+= 1;
747 		}
748 		cp = ep+1;
749 	}
750 	ap->nstrs = 8 - i;
751 	return 0;
752 }
753 
754 int
opendmarc_spf_ipv6_cidr_check(char * ipv6_str,char * cidr_string)755 opendmarc_spf_ipv6_cidr_check(char *ipv6_str, char *cidr_string)
756 {
757 	int cidr_bits;
758 	TXT_ARY_T ipv6_ary;
759 	TXT_ARY_T cidr_ary;
760 	INT_ARY_T base_iary;
761 	INT_ARY_T low_iary;
762 	INT_ARY_T hi_iary;
763 	INT_ARY_T ip_iary;
764 	char *	cp;
765 	int ret;
766 	int i;
767 	int taghi, taglo;
768 	char cidr_str[256];
769 
770 	if (ipv6_str == NULL || cidr_string == NULL)
771 	{
772 		return FALSE;
773 	}
774 
775 	if (strchr(ipv6_str, ':') == NULL)
776 		return FALSE;
777 	if (strchr(cidr_string, ':') == NULL)
778 		return FALSE;
779 
780 	(void) strlcpy(cidr_str, cidr_string, sizeof cidr_str);
781 
782 	cp = strchr(cidr_str, '/');
783 	if (cp == NULL)
784 	{
785 		cidr_bits = 0;
786 	}
787 	else
788 	{
789 		cidr_bits = strtoul(cp+1, NULL, 10);
790 		*cp = '\0';
791 		cp = strchr(cidr_str, ':');
792 		if (cp == NULL)
793 			cidr_bits = 32 - cidr_bits;
794 		else
795 			cidr_bits = 128 - cidr_bits;
796 	}
797 
798 	ret = opendmarc_spf_ipv6_explode(ipv6_str, &ipv6_ary);
799 	if (ret != 0)
800 	{
801 		return FALSE;
802 	}
803 
804 	ret = opendmarc_spf_ipv6_explode(cidr_str, &cidr_ary);
805 	if (ret != 0)
806 	{
807 		return FALSE;
808 	}
809 
810 	ret = opendmarc_spf_ipv6_cidr_populate(&cidr_ary, &base_iary);
811 	if (ret != 0)
812 	{
813 		return FALSE;
814 	}
815 	ret = opendmarc_spf_ipv6_cidr_populate(&ipv6_ary, &ip_iary);
816 	if (ret != 0)
817 	{
818 		return FALSE;
819 	}
820 
821 	if (cidr_bits == 0)
822 	{
823 		/*
824 		 * Requre an exact match.
825 		 */
826 		for (i = 0; i < base_iary.nvalues; i++)
827 		{
828 			if (base_iary.values[i] != ip_iary.values[i])
829 			{
830 				return FALSE;
831 			}
832 		}
833 		return TRUE;
834 	}
835 
836 	(void) memcpy(&low_iary, &base_iary, sizeof(INT_ARY_T));
837 	(void) memcpy(&hi_iary,  &base_iary, sizeof(INT_ARY_T));
838 
839 	for (i = 0; i < 8; i++)
840 	{
841 		int twobyte_mask, tmp_mask;
842 
843 		if (cidr_bits >= 16)
844 		{
845 			low_iary.values[i] = 0;
846 			hi_iary.values[i] = 0xFFFF;
847 			cidr_bits = cidr_bits - 16;
848 			continue;
849 		}
850 		twobyte_mask = cidr_bits % 16;
851 		tmp_mask = (0xFFFF << twobyte_mask);
852 		low_iary.values[i] = low_iary.values[i] & tmp_mask;
853 
854 		tmp_mask = ((~tmp_mask) & 0xFFFF);
855 		hi_iary.values[i]  = hi_iary.values[i] | tmp_mask;
856 		if (cidr_bits < 16)
857 			break;
858 		cidr_bits = cidr_bits - 16;
859 	}
860 
861 	taghi = FALSE;
862 	taglo = FALSE;
863 
864 	for (i = 7; i >= 0; --i)
865 	{
866 		if (ip_iary.values[i] == low_iary.values[i] && ip_iary.values[i] == hi_iary.values[i])
867 		{
868 			continue;
869 		}
870 		if (ip_iary.values[i] == hi_iary.values[i])
871 		{
872 			taghi = TRUE;
873 			continue;
874 		}
875 		if (ip_iary.values[i] == low_iary.values[i])
876 		{
877 			taglo = TRUE;
878 			continue;
879 		}
880 		if (taghi == TRUE)
881 		{
882 			if (ip_iary.values[i] > hi_iary.values[i])
883 			{
884 				return FALSE;
885 			}
886 			continue;
887 		}
888 		if (taglo == TRUE)
889 		{
890 			if (ip_iary.values[i] < low_iary.values[i])
891 			{
892 				return FALSE;
893 			}
894 			continue;
895 		}
896 		if (ip_iary.values[i] < low_iary.values[i] || ip_iary.values[i] > hi_iary.values[i])
897 		{
898 			return FALSE;
899 		}
900 	}
901 	return TRUE;
902 }
903 
904 
905 /******************************************************************
906 ** SPF_STRIP_DOTS -- Remove trailing and leading dots from
907 **			a domain name.
908 ******************************************************************/
909 static char *
opendmarc_spf_strip_dots(char * str,char * dot,char * buf,size_t buflen)910 opendmarc_spf_strip_dots(char *str, char *dot, char *buf, size_t buflen)
911 {
912 	char *cp;
913 	char  dupe[BUFSIZ];
914 
915 	if (buflen > BUFSIZ)
916 		buflen = BUFSIZ;
917 	if (str == NULL || buf == NULL || buflen == 0)
918 		return NULL;
919 	(void) memset(buf, '\0', buflen);
920 	(void) memset(dupe, '\0', buflen);
921 	(void) strlcpy(dupe, str, buflen);
922 
923 	for (cp = dupe + strlen(dupe) - 1; cp > dupe; --cp)
924 	{
925 		if (*cp == '.')
926 			*cp = '\0';
927 		else
928 			break;
929 	}
930 	for (cp = dupe; *cp != '\0'; ++cp)
931 	{
932 		if (*cp != '.')
933 			break;
934 	}
935 	(void) strlcpy(buf, cp, buflen);
936 	return buf;
937 }
938 
939 
940 
941 int
opendmarc_spf_subdomain(char * dom,char * sub)942 opendmarc_spf_subdomain(char *dom, char *sub)
943 {
944 	char dcopy[MAXDNSHOSTNAME];
945 	char scopy[MAXDNSHOSTNAME];
946 	char scratch[MAXDNSHOSTNAME];
947 	char *cp;
948 	int   dlen;
949 	int   slen;
950 
951 	if (dom == NULL || sub == NULL)
952 		return FALSE;
953 	(void) memset(dcopy, '\0', sizeof dcopy);
954 	(void) memset(scopy, '\0', sizeof scopy);
955 	(void) memset(scratch, '\0', sizeof scratch);
956 
957 	cp = opendmarc_spf_strip_dots(dom, ".", scratch, sizeof scratch);
958 	if (cp == NULL)
959 		return FALSE;
960 	cp = opendmarc_spf_reverse(scratch, dcopy, sizeof dcopy);
961 	if (cp == NULL)
962 		return FALSE;
963 
964 	cp = opendmarc_spf_strip_dots(sub, ".", scratch, sizeof scratch);
965 	if (cp == NULL)
966 		return FALSE;
967 	cp = opendmarc_spf_reverse(scratch, scopy, sizeof scopy);
968 	if (cp == NULL)
969 		return FALSE;
970 
971 	(void) strlcat(dcopy, ".", sizeof dcopy);
972 	(void) strlcat(scopy, ".", sizeof scopy);
973 
974 	dlen = strlen(dcopy);
975 	slen = strlen(scopy);
976 
977 	if (dlen == slen)
978 	{
979 		if (strcasecmp(dcopy, scopy) == 0)
980 			return TRUE;
981 		return FALSE;
982 	}
983 	if (strncasecmp(dcopy, scopy, dlen) == 0)
984 		return TRUE;
985 	return FALSE;
986 }
987 static int
opendmarc_spf_ptr_domain(SPF_CTX_T * spfctx,char * domain)988 opendmarc_spf_ptr_domain(SPF_CTX_T *spfctx, char *domain)
989 {
990 	char **	dry = NULL;
991 	int	dry_len = 0;
992 	char **	dpp;
993 	char **	ary = NULL;
994 	int	ary_len = 0;
995 	char **	app;
996 	char **	nary = NULL;
997 	int	nary_len = 0;
998 	char **	npp;
999 	int	good = FALSE;;
1000 
1001 	if (spfctx->validated_domain[0] != '\0')
1002 		return TRUE;
1003 	dry = opendmarc_spf_dns_lookup_ptr(spfctx->ip_address, dry, &dry_len);
1004 
1005 	/*
1006 	 * There can be muiltple host names returned.
1007 	 */
1008 	for (dpp = dry; dpp != NULL && *dpp != NULL; ++dpp)
1009 	{
1010 		ary = opendmarc_spf_dns_lookup_a(*dpp, ary, &ary_len);
1011 		if (ary == NULL)
1012 			continue;
1013 
1014 		/*
1015 		 * Compare the addresses returned for that host name
1016 		 * to the specified IP address and if it compares
1017 		 * save the host name for later.
1018 		 */
1019 		for (app = ary; app != NULL && *app != NULL; ++app)
1020 		{
1021 			if (strcasecmp(*app, spfctx->ip_address) == 0)
1022 			{
1023 				nary = opendmarc_util_pushnargv(*dpp, nary, &nary_len);
1024 				break;
1025 			}
1026 		}
1027 		if (nary == NULL)
1028 			break;
1029 		for (npp = nary; *npp != NULL; ++npp)
1030 		{
1031 			char *dp;
1032 
1033 			if (domain == NULL)
1034 				dp = spfctx->mailfrom_domain;
1035 			else
1036 				dp = domain;
1037 			if (opendmarc_spf_subdomain(dp, *dpp) == TRUE)
1038 			{
1039 				(void) strlcpy(spfctx->validated_domain, *dpp, sizeof spfctx->validated_domain);
1040 				good = TRUE;
1041 				break;
1042 			}
1043 		}
1044 		nary = opendmarc_util_freenargv(nary, &nary_len);
1045 		if (good == TRUE)
1046 			break;
1047 	}
1048 	dry = opendmarc_util_freenargv(dry, &dry_len);
1049 	return good;
1050 }
1051 
1052 static char *
opendmarc_spf_macro_expand(SPF_CTX_T * spfctx,char * str,char * buf,size_t buflen,int is_exp)1053 opendmarc_spf_macro_expand(SPF_CTX_T *spfctx, char *str, char *buf, size_t buflen, int is_exp)
1054 {
1055 	char *sp;
1056 	char *xp;
1057 	char *ep;
1058 	char *bp;
1059 	char  scratch[MAXDNSHOSTNAME];
1060 	time_t t;
1061 	int   num;
1062 	int   rev;
1063 
1064 	if (spfctx == NULL || str == NULL || buf == NULL || strlen(str) > buflen)
1065 	{
1066 		return NULL;
1067 	}
1068 	sp = str;
1069 	ep = str + strlen(str);
1070 	(void) memset(buf, '\0', buflen);
1071 	bp = buf;
1072 
1073 	for (sp = str; sp < ep; )
1074 	{
1075 		if (*sp != '%')
1076 		{
1077 			*bp++ = *sp++;
1078 			continue;
1079 		}
1080 		++sp;
1081 		switch ((int)*sp)
1082 		{
1083 		    case '%':
1084 			*bp++ = *sp++;
1085 			continue;
1086 		    case '_':
1087 			*bp++ = ' ';
1088 			++sp;
1089 			continue;
1090 		    case '-':
1091 			*bp++ = '%';
1092 			*bp++ = '2';
1093 			*bp++ = '0';
1094 			++sp;
1095 			continue;
1096 		    case '{':
1097 			break;
1098 		    default:
1099 			return NULL;
1100 		}
1101 		++sp;
1102 		num = 0;
1103 		rev = FALSE;
1104 		xp = sp+1;
1105 		if (*xp == 'r')
1106 		{
1107 			rev = TRUE;
1108 			++xp;
1109 		}
1110 		if (isdigit((int)*xp))
1111 		{
1112 			num = strtoul(xp, &xp, 10);
1113 		}
1114 		char * cp;
1115 		switch ((int)*sp)
1116 		{
1117 		    case 's':
1118 			if (rev == TRUE)
1119 				(void) opendmarc_spf_reverse(spfctx->mailfrom_domain, scratch, MAXDNSHOSTNAME);
1120 			else
1121 				(void) strlcpy(scratch, spfctx->mailfrom_domain, MAXDNSHOSTNAME);
1122 			if (num > 0 && num < MAXDNSHOSTNAME)
1123 				scratch[num] = '\0';
1124 			for (cp = scratch; *cp != '\0'; )
1125 				*bp++ = *cp++;
1126 			break;
1127 		    case 'l':
1128 			(void) strlcpy(scratch, spfctx->mailfrom_addr, MAXDNSHOSTNAME);
1129 			cp = strchr(scratch, '@');
1130 			if (cp != NULL)
1131 				*cp = '\0';
1132 			if (num > 0 && num < MAXDNSHOSTNAME)
1133 				scratch[num] = '\0';
1134 			for (cp = scratch; *cp != '\0'; )
1135 				*bp++ = *cp++;
1136 			break;
1137 		    case 'o':
1138 			(void) strlcpy(scratch, spfctx->mailfrom_addr, MAXDNSHOSTNAME);
1139 			cp = strchr(scratch, '@');
1140 			if (cp != NULL)
1141 				++cp;
1142 			else
1143 				cp = scratch;
1144 			if (num > 0 && num < (MAXDNSHOSTNAME - (cp - scratch)))
1145 				cp[num] = '\0';
1146 			for (; *cp != '\0'; )
1147 				*bp++ = *cp++;
1148 			break;
1149 		    case 'd':
1150 			if (rev == TRUE)
1151 				(void) opendmarc_spf_reverse(spfctx->mailfrom_domain, scratch, MAXDNSHOSTNAME);
1152 			else
1153 				(void) strlcpy(scratch, spfctx->mailfrom_domain, MAXDNSHOSTNAME);
1154 			if (num > 0 && num < MAXDNSHOSTNAME)
1155 				scratch[num] = '\0';
1156 			for (cp = scratch; *cp != '\0'; )
1157 				*bp++ = *cp++;
1158 			break;
1159 		    case 'i':
1160 			if (rev == TRUE)
1161 				(void) opendmarc_spf_reverse(spfctx->ip_address, scratch, MAXDNSHOSTNAME);
1162 			else
1163 			if (num > 0 && num < MAXDNSHOSTNAME)
1164 				scratch[num] = '\0';
1165 			for (cp = scratch; *cp != '\0'; )
1166 				*bp++ = *cp++;
1167 			break;
1168 		    case 'h':
1169 			if (rev == TRUE)
1170 				(void) opendmarc_spf_reverse(spfctx->helo_domain, scratch, MAXDNSHOSTNAME);
1171 			else
1172 				(void) strlcpy(scratch, spfctx->helo_domain, MAXDNSHOSTNAME);
1173 			if (num > 0 && num < MAXDNSHOSTNAME)
1174 				scratch[num] = '\0';
1175 			for (cp = scratch; *cp != '\0'; )
1176 				*bp++ = *cp++;
1177 			break;
1178 		    case 'v':
1179 			/* if ip is ipv6 use "ip6" instead */
1180 			for (cp = "in-addr"; *cp != '\0'; )
1181 				*bp++ = *cp++;
1182 			break;
1183 		    case 'p':
1184 			if (spfctx->validated_domain[0] == '\0')
1185 				(void) opendmarc_spf_ptr_domain(spfctx, NULL);
1186 			if (rev == TRUE)
1187 				(void) opendmarc_spf_reverse(spfctx->validated_domain, scratch, MAXDNSHOSTNAME);
1188 			else
1189 				(void) strlcpy(scratch, spfctx->validated_domain, MAXDNSHOSTNAME);
1190 			if (num > 0 && num < MAXDNSHOSTNAME)
1191 				scratch[num] = '\0';
1192 			for (cp = scratch; *cp != '\0'; )
1193 				*bp++ = *cp++;
1194 			break;
1195 		    case 'c':
1196 			if (is_exp == FALSE)
1197 				return NULL;
1198 			if (rev == TRUE)
1199 				(void) opendmarc_spf_reverse(spfctx->ip_address, scratch, MAXDNSHOSTNAME);
1200 			else
1201 				(void) strlcpy(scratch, spfctx->ip_address, MAXDNSHOSTNAME);
1202 			if (num > 0 && num < MAXDNSHOSTNAME)
1203 				scratch[num] = '\0';
1204 			for (cp = scratch;  *cp != '\0'; )
1205 				*bp++ = *cp++;
1206 			break;
1207 		    case 'r':
1208 			/* do rev and num apply to this one? */
1209 			if (is_exp == FALSE)
1210 				return NULL;
1211 			if (gethostname(scratch, sizeof scratch) == 0)
1212 			{
1213 				for (cp = scratch;  *cp != '\0'; )
1214 					*bp++ = *cp++;
1215 			}
1216 			break;
1217 		    case 't':
1218 			/* do rev and num apply to this one? */
1219 			if (is_exp == FALSE)
1220 				return NULL;
1221 			t = time(NULL);
1222 			(void) opendmarc_util_ultoa(t, scratch, sizeof scratch);
1223 			for (cp = scratch;  *cp != '\0'; )
1224 				*bp++ = *cp++;
1225 			break;
1226 		}
1227 		if (*xp != '}')
1228 			return NULL;
1229 		sp = xp+1;
1230 		continue;
1231 	}
1232 	*bp++ = '\0';
1233 	return buf;
1234 }
1235 
1236 
1237 /***************************************************************
1238 ** libspf_parse -- parse the record
1239 **
1240 ** Arguments:
1241 **	ctx		-- SPF_CTX_T
1242 **	xbuf		-- buffer into which errors are written.
1243 **	xbuf_len	-- size of buffer
1244 **
1245 ** Returns:
1246 **	spfctx->status
1247 **
1248 ** Side Effects:
1249 **	Makes a connections to the local name server and blocks
1250 **	on each waiting for a reply.
1251 **
1252 ***************************************************************/
1253 #define MAX_SPF_STACK_DEPTH (10)
1254 #define MAX_SPF_DNS_LOOKUPS (10)
1255 typedef struct {
1256 	char domain[MAXDNSHOSTNAME];
1257 	char spf[SPF_MAX_SPF_RECORD_LEN];
1258 	char *sp;
1259 	char *ep;
1260 	char *esp;
1261 } SPF_STACK_T;
1262 
1263 #define SPF_SP  (stack[s].sp)
1264 #define SPF_EP  (stack[s].ep)
1265 #define SPF_ESP (stack[s].esp)
1266 #define PUSHLINE i = spfctx->nlines; if (i < MAX_SPF_STACK_DEPTH) { spfctx->lines[i] = strdup(xbuf); spfctx->nlines = ++i; }
1267 int
opendmarc_spf_parse(SPF_CTX_T * spfctx,int dns_count,char * xbuf,size_t xbuf_len)1268 opendmarc_spf_parse(SPF_CTX_T *spfctx, int dns_count, char *xbuf, size_t xbuf_len)
1269 {
1270 	char ipnum[64];
1271 	char *vp	= NULL;
1272 	int   i;
1273 	size_t len;
1274 	int	prefix;
1275 	u_long ip	= 0;
1276 	int split	= 0;
1277 #define SPLIT_COLON (1)
1278 #define SPLIT_EQUAL (2)
1279 #define SPLIT_SLASH (3)
1280 	SPF_STACK_T stack[MAX_SPF_STACK_DEPTH];
1281 	int s = 0;
1282 	int up = FALSE;
1283 	int ret;
1284 
1285 	spfctx->in_token = SPF_IN_TOKEN_NONE;
1286 	if (spfctx == NULL)
1287 	{
1288 		(void) strlcat(xbuf, "Oddly the context was NULL: FAILED", xbuf_len);
1289 		PUSHLINE
1290 		return spfctx->status = SPF_RETURN_INTERNAL;
1291 	}
1292 	if (spfctx->mailfrom_domain[0] == '\0')
1293 	{
1294 		if (spfctx->helo_domain[0] == '\0')
1295 		{
1296 			(void) strlcat(xbuf, "Spf present but oddly no domain specified: FAILED", xbuf_len);
1297 			PUSHLINE
1298 			return spfctx->status = SPF_RETURN_INTERNAL;
1299 		}
1300 		(void) strlcpy(spfctx->mailfrom_domain, spfctx->helo_domain, sizeof spfctx->mailfrom_domain);
1301 	}
1302 	if (spfctx->spf_record[0] == '\0')
1303 	{
1304 		(void) strlcat(xbuf, "Spf TXT record existed, but was empty: FAILED", xbuf_len);
1305 		PUSHLINE
1306 		return spfctx->status = SPF_RETURN_INTERNAL;
1307 	}
1308 	if (spfctx->ip_address[0] == '\0')
1309 	{
1310 		(void) strlcat(xbuf, "Spf present but no IP address to check: FAILED", xbuf_len);
1311 		PUSHLINE
1312 		return spfctx->status = SPF_RETURN_INTERNAL;
1313 	}
1314 	len = strlen(spfctx->spf_record);
1315 	if (len >= SPF_MAX_SPF_RECORD_LEN -1)
1316 	{
1317 		(void) strlcat(xbuf, "Spf TXT record existed, but was absurdly large: FAILED", xbuf_len);
1318 		PUSHLINE
1319 		return spfctx->status = SPF_RETURN_RECORD_TOOLONG;
1320 	}
1321 
1322 	(void) memset(stack[s].domain, '\0', MAXDNSHOSTNAME);
1323 	(void) strlcpy(stack[s].domain, spfctx->mailfrom_domain, MAXDNSHOSTNAME);
1324 
1325 	(void) memset(ipnum, '\0', sizeof ipnum);
1326 	(void) strlcpy(ipnum, spfctx->ip_address, sizeof ipnum);
1327 	ip = inet_addr(ipnum);
1328 
1329 	(void) strlcpy(stack[s].spf, spfctx->spf_record, SPF_MAX_SPF_RECORD_LEN);
1330 	SPF_SP  = stack[s].spf;
1331 	SPF_EP  = stack[s].spf + strlen(stack[s].spf);
1332 	SPF_ESP = stack[s].spf - 1;
1333 	up = TRUE;
1334 
1335 	while (s >= 0)
1336 	{
1337 		if (up == TRUE)
1338 		{
1339 			(void) memset(xbuf, '\0', xbuf_len);
1340 			(void) strlcpy(xbuf, stack[s].domain, xbuf_len);
1341 			(void) strlcat(xbuf, ": ", xbuf_len);
1342 			(void) strlcat(xbuf, stack[s].spf, xbuf_len);
1343 			PUSHLINE
1344 		}
1345 
1346 		for (;;)
1347 		{
1348 			if (dns_count > MAX_SPF_DNS_LOOKUPS)
1349 			{
1350 				(void) strlcat(xbuf, " Too Many DNS queries (10 max): FAILED", xbuf_len);
1351 				PUSHLINE
1352 				return spfctx->status = SPF_RETURN_TOO_MANY_DNS_QUERIES;
1353 			}
1354 			if (SPF_ESP >= SPF_EP-1)
1355 			{
1356 				--s;
1357 				up = FALSE;
1358 				break;
1359 			}
1360 			SPF_SP = SPF_ESP + 1;
1361 
1362 			while (isspace((int)*SPF_SP) && *SPF_SP != '\0' && SPF_SP < SPF_EP)
1363 				++SPF_SP;
1364 			if (*SPF_SP == '\0' || SPF_SP >= SPF_EP)
1365 			{
1366 				--s;
1367 				up = FALSE;
1368 				break;
1369 			}
1370 
1371 			/* find the next space delimit point */
1372 			SPF_ESP = SPF_SP;
1373 			while(! isspace((int)*SPF_ESP) && *SPF_ESP != '\0' && SPF_ESP < SPF_EP)
1374 				++SPF_ESP;
1375 			if (SPF_ESP > SPF_EP)
1376 			{
1377 				--s;
1378 				up = FALSE;
1379 				break;
1380 			}
1381 			*SPF_ESP = '\0';
1382 
1383 			/* show each step */
1384 			(void) memset(xbuf, '\0', xbuf_len);
1385 			(void) strlcpy(xbuf, stack[s].domain, xbuf_len);
1386 			(void) strlcat(xbuf, ": ", xbuf_len);
1387 
1388 			/* ignore the qualifiers for now */
1389 			prefix = '\0';
1390 			if (*SPF_SP == '+' || *SPF_SP == '?' || *SPF_SP == '~' || *SPF_SP == '-')
1391 			{
1392 				prefix = *SPF_SP;
1393 				++SPF_SP;
1394 			}
1395 
1396 			/* split at any =, /, or : into name=sp, value=vp */
1397 			vp = strchr(SPF_SP, '=');
1398 			if (vp != NULL)
1399 			{
1400 				split = SPLIT_EQUAL;
1401 				*vp++ = '\0';
1402 			}
1403 			else
1404 			{
1405 				vp = strchr(SPF_SP, ':');
1406 				if (vp != NULL)
1407 				{
1408 					split = SPLIT_COLON;
1409 					*vp++ = '\0';
1410 				}
1411 				else
1412 				{
1413 					vp = strchr(SPF_SP, '/');
1414 					if (vp != NULL)
1415 					{
1416 						split = SPLIT_SLASH;
1417 						*vp++ = '\0';
1418 					}
1419 					else
1420 					{
1421 						vp = NULL;
1422 					}
1423 				}
1424 			}
1425 
1426 			if (strcasecmp(SPF_SP, "v") == 0)
1427 			{
1428 				spfctx->in_token = SPF_IN_TOKEN_VERSION;
1429 				if (vp == NULL || strcasecmp(vp, "spf1") != 0)
1430 				{
1431 					(void) strlcat(xbuf, " Expected \"v=spf1\": FAILED", xbuf_len);
1432 					PUSHLINE
1433 					return spfctx->status = SPF_RETURN_BAD_SYNTAX_VERSION;
1434 				}
1435 				/* version was okay */
1436 				continue;
1437 			}
1438 			if (strncasecmp(SPF_SP, "spf2.0", 6) == 0)
1439 				continue;
1440 
1441 			if (strcasecmp("a", SPF_SP) == 0 || strcasecmp("ip4", SPF_SP) == 0)
1442 			{
1443 				char **	ary = NULL;
1444 				int	ary_len = 0;
1445 				char **	app;
1446 				char 	abuf[BUFSIZ];
1447 
1448 				if (vp == NULL || split == SPLIT_SLASH)
1449 				{
1450 
1451 					/*
1452 					 * Don't know what to do with a/24.
1453 					 * look up the a, and do each address/24?
1454 					 */
1455 					if (ary != NULL)
1456 						ary = opendmarc_util_freenargv(ary, &ary_len);
1457 					++dns_count;
1458 					ary = opendmarc_spf_dns_lookup_a(stack[s].domain, ary, &ary_len);
1459 					if (ary != NULL)
1460 					{
1461 						for (app = ary; *app != NULL; ++app)
1462 						{
1463 							(void) memset(abuf, '\0', sizeof abuf);
1464 							(void) strlcpy(abuf, *app, sizeof abuf);
1465 							if (vp != NULL)
1466 							{
1467 								(void) strlcat(abuf, "/", sizeof abuf);
1468 								(void) strlcat(abuf, vp, sizeof abuf);
1469 							}
1470 							spfctx->iplist = opendmarc_util_pushnargv(abuf, spfctx->iplist, &(spfctx->ipcount));
1471 							if (opendmarc_spf_cidr_address(ip, abuf) == TRUE)
1472 							{
1473 								(void) strlcat(xbuf, " ", xbuf_len);
1474 								(void) strlcat(xbuf, ipnum, xbuf_len);
1475 								(void) strlcat(xbuf, " was found in ", xbuf_len);
1476 								(void) strlcat(xbuf, abuf, xbuf_len);
1477 								(void) strlcat(xbuf, ": PASSED", xbuf_len);
1478 								PUSHLINE
1479 								ary = opendmarc_util_freenargv(ary, &ary_len);
1480 								return spfctx->status = SPF_RETURN_OK_PASSED;
1481 							}
1482 						}
1483 						ary = opendmarc_util_freenargv(ary, &ary_len);
1484 						continue;
1485 					}
1486 					(void) strlcat(xbuf, " ", xbuf_len);
1487 					(void) strlcat(xbuf, stack[s].domain, xbuf_len);
1488 					(void) strlcat(xbuf, " had no A records: FAILED", xbuf_len);
1489 					if (s == 0 && prefix == '-')
1490 					{
1491 						return spfctx->status = SPF_RETURN_DASH_FORCED_HARD_FAIL;
1492 					}
1493 					PUSHLINE
1494 					return spfctx->status = SPF_RETURN_A_BUT_NO_A_RECORD;
1495 				}
1496 				if (strcasecmp("a", SPF_SP) == 0 && vp != NULL)
1497 				{
1498 					char *  slashp  = NULL;
1499 					char ** a_ary   = NULL;
1500 					int	a_ary_len = 0;
1501 					char ** a_app   = NULL;
1502 					char    a_abuf[BUFSIZ];
1503 					(void) opendmarc_spf_macro_expand(spfctx, vp, a_abuf, sizeof a_abuf, FALSE);
1504 					++dns_count;
1505 					a_ary = (char **)opendmarc_spf_dns_lookup_a(a_abuf, a_ary, &a_ary_len);
1506 					if (a_ary == NULL)
1507 					{
1508 						(void) strlcat(xbuf, " ", xbuf_len);
1509 						(void) strlcat(xbuf, vp, xbuf_len);
1510 						(void) strlcat(xbuf, " had no A records: FAILED", xbuf_len);
1511 						if (s == 0 && prefix == '-')
1512 						{
1513 							return spfctx->status = SPF_RETURN_DASH_FORCED_HARD_FAIL;
1514 						}
1515 						PUSHLINE
1516 						return spfctx->status = SPF_RETURN_A_BUT_NO_A_RECORD;
1517 					}
1518 
1519 					for (a_app = a_ary; *a_app != NULL; ++a_app)
1520 					{
1521 						(void) memset(a_abuf, '\0', sizeof a_abuf);
1522 						(void) strlcpy(a_abuf, *a_app, sizeof a_abuf);
1523 						if (slashp != NULL)
1524 						{
1525 							(void) strlcat(a_abuf, "/", sizeof a_abuf);
1526 							(void) strlcat(a_abuf, slashp+1, sizeof a_abuf);
1527 						}
1528 						spfctx->iplist = opendmarc_util_pushnargv(a_abuf, spfctx->iplist, &(spfctx->ipcount));
1529 						if (opendmarc_spf_cidr_address(ip, a_abuf) == TRUE)
1530 						{
1531 							(void) strlcat(xbuf, " ", xbuf_len);
1532 							(void) strlcat(xbuf, ipnum, xbuf_len);
1533 							(void) strlcat(xbuf, " was found: PASSED", xbuf_len);
1534 							PUSHLINE
1535 							a_ary = opendmarc_util_freenargv(a_ary, &ary_len);
1536 							return spfctx->status = SPF_RETURN_OK_PASSED;
1537 						}
1538 						if (s == 0 && prefix == '-')
1539 						{
1540 							return spfctx->status = SPF_RETURN_DASH_FORCED_HARD_FAIL;
1541 						}
1542 					}
1543 					a_ary = opendmarc_util_freenargv(a_ary, &ary_len);
1544 					continue;
1545 				}
1546 				if (strcasecmp("ip4", SPF_SP) == 0 && vp != NULL)
1547 				{
1548 					spfctx->iplist = opendmarc_util_pushnargv(vp, spfctx->iplist, &(spfctx->ipcount));
1549 					ret = opendmarc_spf_cidr_address(ip, vp);
1550 					if (ret == TRUE)
1551 					{
1552 						(void) strlcat(xbuf, " ", xbuf_len);
1553 						(void) strlcat(xbuf, ipnum, xbuf_len);
1554 						(void) strlcat(xbuf, " was found: PASSED", xbuf_len);
1555 						PUSHLINE
1556 						return spfctx->status = SPF_RETURN_OK_PASSED;
1557 					}
1558 					if (s == 0 && prefix == '-')
1559 					{
1560 						return spfctx->status = SPF_RETURN_DASH_FORCED_HARD_FAIL;
1561 					}
1562 					continue;
1563 				}
1564 				if (vp == NULL)
1565 					vp = "<nil>";
1566 				(void) strlcat(xbuf, " ", xbuf_len);
1567 				(void) strlcat(xbuf, vp, xbuf_len);
1568 				(void) strlcat(xbuf, " Badly formed: FAILED", xbuf_len);
1569 				PUSHLINE
1570 				return spfctx->status = SPF_RETURN_A_BUT_BAD_SYNTAX;
1571 			}
1572 			if (strcasecmp("mx", SPF_SP) == 0)
1573 			{
1574 				char **	ary = NULL;
1575 				int	ary_len = 0;
1576 				char **	app = NULL;
1577 				char   	mxbuf[BUFSIZ];
1578 
1579 				if (vp != NULL && split != SPLIT_SLASH)
1580 					(void) opendmarc_spf_macro_expand(spfctx, vp, mxbuf, sizeof mxbuf, FALSE);
1581 				else
1582 					(void) opendmarc_spf_macro_expand(spfctx, stack[s].domain, mxbuf, sizeof mxbuf, FALSE);
1583 				++dns_count;
1584 				ary = opendmarc_spf_dns_lookup_mx(mxbuf, ary, &ary_len);
1585 				if (ary == NULL)
1586 				{
1587 					(void) strlcat(xbuf, mxbuf, xbuf_len);
1588 					(void) strlcat(xbuf, ": MX listed but no MX records", xbuf_len);
1589 					if (s == 0 && prefix == '-')
1590 					{
1591 						return spfctx->status = SPF_RETURN_DASH_FORCED_HARD_FAIL;
1592 					}
1593 					PUSHLINE
1594 					continue;
1595 				}
1596 				for (app = ary; *app != NULL; ++app)
1597 				{
1598 					spfctx->iplist = opendmarc_util_pushnargv(*app, spfctx->iplist, &(spfctx->ipcount));
1599 					if (opendmarc_spf_cidr_address(ip, *app) == TRUE)
1600 					{
1601 						(void) strlcat(xbuf, " ", xbuf_len);
1602 						(void) strlcat(xbuf, ipnum, xbuf_len);
1603 						(void) strlcat(xbuf, " was found: PASSED", xbuf_len);
1604 						PUSHLINE
1605 						ary = opendmarc_util_freenargv(ary, &ary_len);
1606 						return spfctx->status = SPF_RETURN_OK_PASSED;
1607 					}
1608 				}
1609 				ary = opendmarc_util_freenargv(ary, &ary_len);
1610 				if (s == 0 && prefix == '-')
1611 				{
1612 					return spfctx->status = SPF_RETURN_DASH_FORCED_HARD_FAIL;
1613 				}
1614 				continue;
1615 			}
1616 			if (strcasecmp("include", SPF_SP) == 0)
1617 			{
1618 				char	*spf_ret;
1619 				char	spfbuf[SPF_MAX_SPF_RECORD_LEN];
1620 				char	cname[MAXDNSHOSTNAME];
1621 				char	query[MAXDNSHOSTNAME];
1622 				int	reply;
1623 
1624 				if (vp == NULL || strlen(vp) == 0)
1625 				{
1626 					(void) strlcat(xbuf, "\"include:\" Lacked a domain specification.", xbuf_len);
1627 					if (s == 0 && prefix == '-')
1628 					{
1629 						return spfctx->status = SPF_RETURN_DASH_FORCED_HARD_FAIL;
1630 					}
1631 					PUSHLINE
1632 					return spfctx->status = SPF_RETURN_BAD_SYNTAX_INCLUDE;
1633 				}
1634 				(void) memset(query, '\0', sizeof query);
1635 				(void) strlcpy(query, vp, sizeof query);
1636 				(void) memset(cname, '\0', sizeof cname);
1637 				(void) memset(spfbuf, '\0', sizeof spfbuf);
1638 				++dns_count;
1639 				spf_ret = opendmarc_spf_dns_get_record(query, &reply, spfbuf, sizeof spfbuf, cname, sizeof cname, TRUE);
1640 				if (spf_ret == NULL)
1641 				{
1642 					(void) strlcat(xbuf, vp, xbuf_len);
1643 					(void) strlcat(xbuf, " Lacked lacked an SPF record: FAILED", xbuf_len);
1644 					if (s == 0 && prefix == '-')
1645 					{
1646 						return spfctx->status = SPF_RETURN_DASH_FORCED_HARD_FAIL;
1647 					}
1648 					PUSHLINE
1649 					return spfctx->status = SPF_RETURN_INCLUDE_NO_DOMAIN;
1650 				}
1651 				if (s+1 >= MAX_SPF_STACK_DEPTH)
1652 				{
1653 					char nbuf[16];
1654 
1655 					(void) strlcat(xbuf, stack[s].domain, xbuf_len);
1656 					(void) strlcat(xbuf, " Too many levels of includes, ", xbuf_len);
1657 					(void) opendmarc_util_ultoa(MAX_SPF_STACK_DEPTH, nbuf, sizeof nbuf);
1658 					(void) strlcat(xbuf, nbuf, xbuf_len);
1659 					(void) strlcat(xbuf, " Max", xbuf_len);
1660 					if (s == 0 && prefix == '-')
1661 					{
1662 						return spfctx->status = SPF_RETURN_DASH_FORCED_HARD_FAIL;
1663 					}
1664 					PUSHLINE
1665 					continue;
1666 				}
1667 				if (s > 0)
1668 				{
1669 					for (i = 0; i < s; i++)
1670 					{
1671 						if (strcasecmp(vp, stack[i].domain) == 0)
1672 							break;
1673 					}
1674 					if (i < s)
1675 					{
1676 						(void) strlcat(xbuf, query, xbuf_len);
1677 						(void) strlcat(xbuf, " Include LOOP detected and supressed", xbuf_len);
1678 						PUSHLINE
1679 						continue;
1680 					}
1681 				}
1682 				s += 1;
1683 				up = TRUE;
1684 				(void) memset(stack[s].domain, '\0', MAXDNSHOSTNAME);
1685 				(void) strlcpy(stack[s].domain, vp, MAXDNSHOSTNAME);
1686 				(void) memset(stack[s].spf, '\0', SPF_MAX_SPF_RECORD_LEN);
1687 				(void) strlcpy(stack[s].spf, spfbuf, SPF_MAX_SPF_RECORD_LEN);
1688 				SPF_SP  = stack[s].spf;
1689 				SPF_EP  = stack[s].spf + strlen(stack[s].spf);
1690 				SPF_ESP = stack[s].spf - 1;
1691 				if (s == 0 && prefix == '-')
1692 				{
1693 					return spfctx->status = SPF_RETURN_DASH_FORCED_HARD_FAIL;
1694 				}
1695 				break;
1696 			}
1697 			if (strcasecmp("all", SPF_SP) == 0)
1698 			{
1699 				char p[2];
1700 
1701 				p[0] = prefix;
1702 				p[1] = '\0';
1703 				(void) memset(xbuf, '\0', xbuf_len);
1704 				(void) strlcpy(xbuf, stack[s].domain, xbuf_len);
1705 				(void) strlcat(xbuf, ": ", xbuf_len);
1706 				(void) strlcat(xbuf, p, xbuf_len);
1707 				(void) strlcat(xbuf, "all, status=", xbuf_len);
1708 
1709 				if (s == 0)
1710 				{
1711 					if (prefix == '-')
1712 					{
1713 						(void) strlcat(xbuf, " ", xbuf_len);
1714 						(void) strlcat(xbuf, ipnum, xbuf_len);
1715 						(void) strlcat(xbuf, " Not found, so: FAILED", xbuf_len);
1716 						PUSHLINE
1717 						return spfctx->status = SPF_RETURN_DASH_ALL_HARD_FAIL;
1718 					}
1719 					if (prefix == '~')
1720 					{
1721 						(void) strlcat(xbuf, " ", xbuf_len);
1722 						(void) strlcat(xbuf, ipnum, xbuf_len);
1723 						(void) strlcat(xbuf, " Not found, so: SOFT-FAILED", xbuf_len);
1724 						PUSHLINE
1725 						return spfctx->status = SPF_RETURN_TILDE_ALL_SOFT_FAIL;
1726 					}
1727 					if (prefix == '?')
1728 					{
1729 						(void) strlcat(xbuf, " ", xbuf_len);
1730 						(void) strlcat(xbuf, ipnum, xbuf_len);
1731 						(void) strlcat(xbuf, " Not found, but: NEUTRAL", xbuf_len);
1732 						PUSHLINE
1733 						return spfctx->status = SPF_RETURN_QMARK_ALL_NEUTRAL;
1734 					}
1735 					else
1736 					{
1737 						(void) strlcat(xbuf, " ", xbuf_len);
1738 						(void) strlcat(xbuf, ipnum, xbuf_len);
1739 						(void) strlcat(xbuf, " Not found, but: PASS", xbuf_len);
1740 						PUSHLINE
1741 						return spfctx->status = SPF_RETURN_OK_PASSED;
1742 					}
1743 				}
1744 				continue;
1745 			}
1746 			if (strcasecmp("ip6", SPF_SP) == 0)
1747 			{
1748 				int ret;
1749 				/*
1750 				 * Open issue: Should we convert an ipv4 address in spfctx->ip_address
1751 				 * into ipv6 for this check? e.g. 1.2.3.4 -> :FFFF:1.2.3.4
1752 				 */
1753 				spfctx->iplist = opendmarc_util_pushnargv(vp, spfctx->iplist, &(spfctx->ipcount));
1754 				ret = opendmarc_spf_ipv6_cidr_check(spfctx->ip_address, vp);
1755 				if (ret == TRUE)
1756 				{
1757 					return spfctx->status = SPF_RETURN_OK_PASSED;
1758 				}
1759 				if (s == 0 && prefix == '-')
1760 				{
1761 					return spfctx->status = SPF_RETURN_DASH_FORCED_HARD_FAIL;
1762 				}
1763 				continue;
1764 			}
1765 			if (strcasecmp("ptr", SPF_SP) == 0)
1766 			{
1767 				int	good;
1768 
1769 				good = opendmarc_spf_ptr_domain(spfctx, vp);
1770 				if (good == TRUE)
1771 				{
1772 					return spfctx->status = SPF_RETURN_OK_PASSED;
1773 				}
1774 				if (s == 0 && prefix == '-')
1775 				{
1776 					return spfctx->status = SPF_RETURN_DASH_FORCED_HARD_FAIL;
1777 				}
1778 				continue;
1779 			}
1780 			if (strcasecmp("exists", SPF_SP) == 0)
1781 			{
1782 				char *	xp;
1783 				char **	ary	 = NULL;
1784 				int	ary_len	 = 0;
1785 				char **	app;
1786 
1787 				if (vp == NULL || strlen(vp) == 0)
1788 				{
1789 					(void) strlcpy(xbuf, "\"exists:\" Lacked a domain specification.", xbuf_len);
1790 					PUSHLINE
1791 					return spfctx->status = SPF_RETURN_BAD_SYNTAX_EXISTS;
1792 				}
1793 				/* see http://old.openspf.org/macros.html for macros */
1794 				/* altavista.net uses +exists:CL.%{i}.FR.%{s}.HE.%{h}.null.spf.altavista.com */
1795 				xp = opendmarc_spf_macro_expand(spfctx, vp, xbuf, xbuf_len, TRUE);
1796 				if (xp == NULL)
1797 				{
1798 					(void) strlcpy(xbuf, "\"exists:\" record had syntactially bad macros:" , xbuf_len);
1799 					(void) strlcat(xbuf, vp, xbuf_len);
1800 					(void) strlcat(xbuf, ": FAILED", xbuf_len);
1801 					PUSHLINE
1802 					return spfctx->status = SPF_RETURN_BAD_MACRO_SYNTAX;
1803 				}
1804 				++dns_count;
1805 				if (ary != NULL)
1806 					ary = opendmarc_util_freenargv(ary, &ary_len);
1807 				ary = opendmarc_spf_dns_lookup_a(xbuf, ary, &ary_len);
1808 				if (ary == NULL)
1809 				{
1810 					/* lookup failed */
1811 					if (prefix != '-')
1812 						continue;
1813 					(void) strlcpy(xbuf, "\"exists:\" record lookup: ", xbuf_len);
1814 					(void) strlcat(xbuf, vp, xbuf_len);
1815 					(void) strlcat(xbuf, ": FAILED", xbuf_len);
1816 					PUSHLINE
1817 					return spfctx->status = SPF_RETURN_NOT_EXISTS_HARDFAIL;
1818 				}
1819 				for (app = ary; *app != NULL; ++app)
1820 				{
1821 					if (strcmp(spfctx->ip_address, *app) == 0)
1822 						return spfctx->status = SPF_RETURN_OK_PASSED;
1823 				}
1824 				ary = opendmarc_util_freenargv(ary, &ary_len);
1825 				continue;
1826 			}
1827 			if (strcasecmp("exp", SPF_SP) == 0)
1828 			{
1829 				char *	xp;
1830 
1831 				if (vp == NULL || strlen(vp) == 0)
1832 				{
1833 					(void) strlcpy(xbuf, "\"exp:\" Lacked a domain specification.", xbuf_len);
1834 					PUSHLINE
1835 					return spfctx->status = SPF_RETURN_BAD_SYNTAX_EXP;
1836 				}
1837 				xp = opendmarc_spf_macro_expand(spfctx, vp, xbuf, xbuf_len, FALSE);
1838 				if (xp == NULL)
1839 				{
1840 					(void) strlcpy(xbuf, "\"exists:\" record had syntactially bad macros:" , xbuf_len);
1841 					(void) strlcat(xbuf, vp, xbuf_len);
1842 					(void) strlcat(xbuf, ": FAILED", xbuf_len);
1843 					PUSHLINE
1844 					return spfctx->status = SPF_RETURN_BAD_MACRO_SYNTAX;
1845 				}
1846 				(void) memset(spfctx->exp_buf, '\0', sizeof spfctx->exp_buf);
1847 				(void) strlcpy(spfctx->exp_buf, xp, sizeof spfctx->exp_buf);
1848 				spfctx->did_get_exp = TRUE;
1849 				continue;
1850 			}
1851 			if (strcasecmp("redirect", SPF_SP) == 0)
1852 			{
1853 				/*
1854 				 * Some people think that redirect and include are the same.
1855 				 * Rather than fail due to that belief, there is really no harm
1856 				 * in treating them the same.
1857 				 */
1858 				int	reply;
1859 				char *	xp;
1860 				char	query[MAXDNSHOSTNAME];
1861 				char *  spf_ret;
1862 				char	cname[128];
1863 				char	spfbuf[BUFSIZ];
1864 
1865 				if (vp == NULL)
1866 				{
1867 					(void) strlcat(xbuf, " Lacked a domain specification: FAILED", xbuf_len);
1868 					PUSHLINE
1869 					return spfctx->status = SPF_RETURN_REDIRECT_NO_DOMAIN;
1870 				}
1871 				(void) memset(query, '\0', sizeof query);
1872 				xp = opendmarc_spf_macro_expand(spfctx, vp, query, sizeof query, TRUE);
1873 				if (xp == NULL)
1874 				{
1875 					(void) strlcpy(xbuf, "\"redirect:\" record had syntactially bad macros:" , xbuf_len);
1876 					(void) strlcat(xbuf, vp, xbuf_len);
1877 					(void) strlcat(xbuf, ": FAILED", xbuf_len);
1878 					PUSHLINE
1879 					return spfctx->status = SPF_RETURN_BAD_MACRO_SYNTAX;
1880 				}
1881 				++dns_count;
1882 				spf_ret = opendmarc_spf_dns_get_record(query, &reply, spfbuf, sizeof spfbuf, cname, sizeof cname, TRUE);
1883 				if (spf_ret == NULL)
1884 				{
1885 					(void) strlcat(xbuf, vp, xbuf_len);
1886 					(void) strlcat(xbuf, " Lacked lacked an SPF record: FAILED", xbuf_len);
1887 					if (s == 0 && prefix == '-')
1888 					{
1889 						return spfctx->status = SPF_RETURN_DASH_FORCED_HARD_FAIL;
1890 					}
1891 					PUSHLINE
1892 					return spfctx->status = SPF_RETURN_BAD_SYNTAX_REDIRECT;
1893 				}
1894 				(void) memset(stack[s].domain, '\0', MAXDNSHOSTNAME);
1895 				(void) strlcpy(stack[s].domain, vp, MAXDNSHOSTNAME);
1896 				(void) memset(stack[s].spf, '\0', SPF_MAX_SPF_RECORD_LEN);
1897 				(void) strlcpy(stack[s].spf, spfbuf, SPF_MAX_SPF_RECORD_LEN);
1898 				SPF_SP  = stack[s].spf;
1899 				SPF_EP  = stack[s].spf + strlen(stack[s].spf);
1900 				SPF_ESP = stack[s].spf - 1;
1901 				up = TRUE;
1902 				break;
1903 			}
1904 			if (strlen(SPF_SP) > 0)
1905 			{
1906 				(void) strlcat(xbuf, "\"", xbuf_len);
1907 				(void) strlcat(xbuf, SPF_SP, xbuf_len);
1908 				(void) strlcat(xbuf, "\": Unrecognized SPF keyword, WARNING", xbuf_len);
1909 				PUSHLINE
1910 				/* return spfctx->status = SPF_RETURN_UNKNOWN_KEYWORD; */
1911 				continue;
1912 			}
1913 		}
1914 	}
1915 	return spfctx->status;
1916 }
1917 
1918 SPF_CTX_T *
opendmarc_spf_alloc_ctx()1919 opendmarc_spf_alloc_ctx()
1920 {
1921 	SPF_CTX_T *spfctx = NULL;
1922 
1923 	spfctx = malloc(sizeof(SPF_CTX_T));
1924 	if (spfctx == NULL)
1925 		return NULL;
1926 
1927 	(void) memset(spfctx, '\0', sizeof(SPF_CTX_T));
1928 	spfctx->status = SPF_RETURN_UNDECIDED;
1929 	return spfctx;
1930 }
1931 
1932 SPF_CTX_T *
opendmarc_spf_free_ctx(SPF_CTX_T * spfctx)1933 opendmarc_spf_free_ctx(SPF_CTX_T *spfctx)
1934 {
1935 	int i;
1936 
1937 	if (spfctx == NULL)
1938 		return spfctx;
1939 
1940 	for (i = 0; i < spfctx->nlines; i++)
1941 	{
1942 		if (spfctx->lines[i] != NULL)
1943 			(void) free(spfctx->lines[i]);
1944 	}
1945 	spfctx->iplist = opendmarc_util_freenargv(spfctx->iplist, &(spfctx->ipcount));
1946 	(void) free(spfctx);
1947 	spfctx = NULL;
1948 	return spfctx;
1949 }
1950 
1951 int
opendmarc_spf_specify_ip_address(SPF_CTX_T * spfctx,char * ip_address,size_t ip_address_len)1952 opendmarc_spf_specify_ip_address(SPF_CTX_T *spfctx, char *ip_address, size_t ip_address_len)
1953 {
1954 	if (spfctx == NULL)
1955 		return EINVAL;
1956 
1957 	if (ip_address == NULL)
1958 		return EINVAL;
1959 
1960 	/*
1961 	 * we don't care at this point if it is ipv6 or ipv4
1962 	 */
1963 	(void) memset(spfctx->ip_address, '\0', sizeof spfctx->ip_address);
1964 	(void) strlcpy(spfctx->ip_address, ip_address, sizeof spfctx->ip_address);
1965 	return 0;
1966 }
1967 
1968 int
opendmarc_spf_specify_helo_domain(SPF_CTX_T * spfctx,char * helo_domain,size_t helo_domain_len)1969 opendmarc_spf_specify_helo_domain(SPF_CTX_T *spfctx, char *helo_domain, size_t helo_domain_len)
1970 {
1971 	char copy[sizeof spfctx->mailfrom_addr];
1972 	char *cp;
1973 	char *ep;
1974 
1975 	if (spfctx == NULL)
1976 		return EINVAL;
1977 
1978 	if (helo_domain == NULL)
1979 		return 0;
1980 
1981 	(void) memset(copy, '\0', sizeof copy);
1982 	(void) strlcpy(copy, helo_domain, sizeof copy);
1983 	cp = strrchr(copy, '<');
1984 	if (cp == NULL)
1985 		cp = copy;
1986 	ep = strchr(cp, '>');
1987 	if (ep != NULL)
1988 		*ep = '\0';
1989 	ep = strchr(cp, '@');
1990 	if (ep != NULL)
1991 		cp = ep+1;
1992 
1993 	(void) memset(spfctx->helo_domain, '\0', sizeof spfctx->helo_domain);
1994 	(void) strlcpy(spfctx->helo_domain, cp, sizeof spfctx->helo_domain);
1995 	return 0;
1996 }
1997 
1998 int
opendmarc_spf_specify_mailfrom(SPF_CTX_T * spfctx,char * mailfrom,size_t mailfrom_len,int * use_flag)1999 opendmarc_spf_specify_mailfrom(SPF_CTX_T *spfctx, char *mailfrom, size_t mailfrom_len, int *use_flag)
2000 {
2001 	char copy[sizeof spfctx->mailfrom_addr];
2002 	char *cp;
2003 	char *ep;
2004 
2005 	if (use_flag != NULL)
2006 		*use_flag = FALSE;
2007 
2008 	if (spfctx == NULL)
2009 		return EINVAL;
2010 
2011 	if (mailfrom == NULL)
2012 		return EINVAL;
2013 
2014 	(void) memset(copy, '\0', sizeof copy);
2015 	(void) strlcpy(copy, mailfrom, sizeof copy);
2016 
2017 	cp = strrchr(copy, '<');
2018 	if (cp == NULL)
2019 		cp = copy;
2020 	else
2021 		++cp;
2022 	ep = strchr(cp, '>');
2023 	if (ep != NULL)
2024 		*ep = '\0';
2025 
2026 	(void) memset(spfctx->mailfrom_addr, '\0', sizeof spfctx->mailfrom_addr);
2027 	(void) strlcpy(spfctx->mailfrom_addr, cp, sizeof spfctx->mailfrom_addr);
2028 
2029 	ep = strchr(cp, '@');
2030 	if (ep != NULL)
2031 	{
2032 		cp = ep+1;
2033 		if (use_flag != NULL)
2034 			*use_flag = TRUE;
2035 	}
2036 
2037 	if (strcasecmp(cp, "MAILER_DAEMON") == 0)
2038 		cp = "";
2039 
2040 	(void) memset(spfctx->mailfrom_domain, '\0', sizeof spfctx->mailfrom_domain);
2041 	(void) strlcpy(spfctx->mailfrom_domain, cp, sizeof spfctx->mailfrom_domain);
2042 	return 0;
2043 }
2044 
2045 int
opendmarc_spf_specify_record(SPF_CTX_T * spfctx,char * spf_record,size_t spf_record_length)2046 opendmarc_spf_specify_record(SPF_CTX_T *spfctx, char *spf_record, size_t spf_record_length)
2047 {
2048 	if (spfctx == NULL)
2049 	{
2050 		return EINVAL;
2051 	}
2052 	(void) memset(spfctx->spf_record, '\0', sizeof spfctx->spf_record);
2053 	if (spf_record == NULL)
2054 	{
2055 		char *  spf_ret;
2056 		int	reply;
2057 		char	cname[256];
2058 		char	spfbuf[BUFSIZ];
2059 
2060 		/* look it up */
2061 		spf_ret = opendmarc_spf_dns_get_record(spfctx->mailfrom_domain, &reply, spfbuf, sizeof spfbuf, cname, sizeof cname, TRUE);
2062 		if (spf_ret == NULL)
2063 		{
2064 			switch(reply)
2065 			{
2066 			    case HOST_NOT_FOUND:
2067 			    case NO_DATA:
2068 				return DMARC_POLICY_SPF_OUTCOME_NONE;
2069 				break;
2070 			    case NO_RECOVERY:
2071 			    case TRY_AGAIN:
2072 				return DMARC_POLICY_SPF_OUTCOME_TMPFAIL;
2073 				break;
2074 			}
2075 			return DMARC_POLICY_SPF_OUTCOME_NONE;
2076 		}
2077 		(void) strlcpy(spfctx->spf_record, spfbuf, sizeof spfctx->spf_record);
2078 		return 0;
2079 	}
2080 	(void) strlcpy(spfctx->spf_record, spf_record, sizeof spfctx->spf_record);
2081 	return 0;
2082 }
2083 
2084 int
opendmarc_spf_test(char * ip_address,char * mail_from_domain,char * helo_domain,char * spf_record,int softfail_okay_flag,char * human_readable,size_t human_readable_len,int * used_mfrom)2085 opendmarc_spf_test(char *ip_address, char *mail_from_domain, char *helo_domain, char *spf_record, int softfail_okay_flag, char *human_readable, size_t human_readable_len, int *used_mfrom)
2086 {
2087 	SPF_CTX_T *	ctx;
2088 	int		ret;
2089 	int		len;
2090 	char 		xbuf[BUFSIZ];
2091 
2092 	if (used_mfrom != NULL)
2093 		*used_mfrom = FALSE;
2094 
2095 	(void) memset(xbuf, '\0', sizeof xbuf);
2096 	ctx = opendmarc_spf_alloc_ctx();
2097 	if (ctx == NULL)
2098 	{
2099 		if (human_readable != NULL)
2100 			(void) strlcpy(human_readable, strerror(errno), human_readable_len);
2101 		return DMARC_POLICY_SPF_OUTCOME_TMPFAIL;
2102 	}
2103 
2104 	if (ip_address == NULL)
2105 	{
2106 		if (human_readable != NULL)
2107 			(void) strlcpy(human_readable, "No IP address available", human_readable_len);
2108 		ctx = opendmarc_spf_free_ctx(ctx);
2109 		return DMARC_POLICY_SPF_OUTCOME_FAIL;
2110 	}
2111 
2112 	if (mail_from_domain == NULL && helo_domain == NULL)
2113 	{
2114 		if (human_readable != NULL)
2115 			(void) strlcpy(human_readable, "No Domain name available to check", human_readable_len);
2116 		ctx = opendmarc_spf_free_ctx(ctx);
2117 		return DMARC_POLICY_SPF_OUTCOME_FAIL;
2118 	}
2119 
2120 	ret = opendmarc_spf_specify_mailfrom(ctx, mail_from_domain, strlen(mail_from_domain), used_mfrom);
2121 	if (ret != 0)
2122 	{
2123 		if (human_readable != NULL)
2124 			(void) strlcpy(human_readable, strerror(errno), human_readable_len);
2125 		ctx = opendmarc_spf_free_ctx(ctx);
2126 		return DMARC_POLICY_SPF_OUTCOME_TMPFAIL;
2127 	}
2128 
2129 	ret = opendmarc_spf_specify_helo_domain(ctx, helo_domain, strlen(helo_domain));
2130 	if (ret != 0)
2131 	{
2132 		if (human_readable != NULL)
2133 			(void) strlcpy(human_readable, strerror(errno), human_readable_len);
2134 		ctx = opendmarc_spf_free_ctx(ctx);
2135 		return DMARC_POLICY_SPF_OUTCOME_TMPFAIL;
2136 	}
2137 
2138 	ret = opendmarc_spf_specify_ip_address(ctx, ip_address, strlen(ip_address));
2139 	if (ret != 0)
2140 	{
2141 		if (human_readable != NULL)
2142 			(void) strlcpy(human_readable, strerror(errno), human_readable_len);
2143 		ctx = opendmarc_spf_free_ctx(ctx);
2144 		return DMARC_POLICY_SPF_OUTCOME_TMPFAIL;
2145 	}
2146 
2147 	if (spf_record == NULL)
2148 		len = 0;
2149 	else
2150 		len = strlen(spf_record);
2151 	ret = opendmarc_spf_specify_record(ctx, spf_record, len);
2152 	if (ret != 0)
2153 	{
2154 		if (human_readable != NULL)
2155 			(void) strlcpy(human_readable, hstrerror(h_errno), human_readable_len);
2156 		ctx = opendmarc_spf_free_ctx(ctx);
2157 		return ret;
2158 	}
2159 
2160 	ret = opendmarc_spf_parse(ctx, 0, xbuf, sizeof xbuf);
2161 	if (human_readable != NULL)
2162 		(void) strlcpy(human_readable, opendmarc_spf_status_to_msg(ctx, ret), human_readable_len);
2163 	ctx = opendmarc_spf_free_ctx(ctx);
2164 
2165 	if (ret != SPF_RETURN_OK_PASSED)
2166 	{
2167 		switch (ret)
2168 		{
2169 		    case SPF_RETURN_UNDECIDED:
2170 		    case SPF_RETURN_QMARK_ALL_NEUTRAL:
2171 		    case SPF_RETURN_TILDE_ALL_SOFT_FAIL:
2172 			if (softfail_okay_flag == TRUE)
2173 				return DMARC_POLICY_SPF_OUTCOME_PASS;
2174 			else
2175 				return DMARC_POLICY_SPF_OUTCOME_FAIL;
2176 			break;
2177 		    case SPF_RETURN_INTERNAL:
2178 			return DMARC_POLICY_SPF_OUTCOME_TMPFAIL;
2179 		}
2180 		return DMARC_POLICY_SPF_OUTCOME_FAIL;
2181 	}
2182 	return DMARC_POLICY_SPF_OUTCOME_PASS;
2183 }
2184 
2185 #endif /* HAVE_SPF2_H */
2186 
2187 #endif /* WITH_SPF */
2188