1 /*************************************************************************
2 ** The user interface to the rest of this library.
3 **
4 **  Copyright (c) 2012-2016, 2018, 2021, The Trusted Domain Project.
5 **    All rights reserved.
6 **************************************************************************/
7 
8 #include <ctype.h>
9 
10 #include "opendmarc_internal.h"
11 #include "dmarc.h"
12 
13 /* libbsd if found */
14 #ifdef USE_BSD_H
15 # include <bsd/string.h>
16 #endif /* USE_BSD_H */
17 
18 /* libstrl if needed */
19 #ifdef USE_STRL_H
20 # include <strl.h>
21 #endif /* USE_STRL_H */
22 
23 /* opendmarc_strl if needed */
24 #ifdef USE_DMARCSTRL_H
25 # include <opendmarc_strl.h>
26 #endif /* USE_DMARCSTRL_H */
27 
28 /*
29 **  CHECK_DOMAIN -- check for syntactical validity of a domain name
30 **
31 **  Parameters:
32 **  	domain -- domain name to check
33 **
34 **  Return value:
35 **  	TRUE if the syntax was fine, FALSE otherwise.
36 */
37 
check_domain(u_char * domain)38 bool check_domain(u_char *domain)
39 {
40 	u_char *dp;
41 
42 	for (dp = domain; *dp != '\0'; dp++)
43 	{
44 		if (!(isalpha(*dp) ||
45 		      isdigit(*dp) ||
46 		      *dp == '.' ||
47 		      *dp == '-' ||
48 		      *dp == '_'))
49 			return FALSE;
50 	}
51 
52 	return TRUE;
53 }
54 
55 /**************************************************************************
56 ** OPENDMARC_POLICY_LIBRARY_INIT -- Initialize The Library
57 **	Parameters:
58 **		lib_init	-- Address of a filled in DMARC_LIB_T structure
59 **	Returns:
60 **		DMARC_PARSE_OKAY		-- on success
61 **		DMARC_PARSE_ERROR_NULL_CTX	-- if lib_init is null
62 **		DMARC_TLD_ERROR_UNKNOWN		-- If lip_init->tld_type is undefined
63 **	Side Effects:
64 **		Sets a global pointer
65 **	Warning:
66 **		This function is not thread safe so only call once on
67 **		startup.
68 ***************************************************************************/
69 static OPENDMARC_LIB_T *Opendmarc_Libp = NULL;
70 static OPENDMARC_LIB_T Opendmarc_Lib;
71 
72 OPENDMARC_STATUS_T
opendmarc_policy_library_init(OPENDMARC_LIB_T * lib_init)73 opendmarc_policy_library_init(OPENDMARC_LIB_T *lib_init)
74 {
75 	int ret = DMARC_PARSE_OKAY;
76 
77 	if (lib_init == NULL)
78 		return DMARC_PARSE_ERROR_NULL_CTX;
79 	(void) memcpy(&Opendmarc_Lib, lib_init, sizeof(OPENDMARC_LIB_T));
80 	Opendmarc_Libp = &Opendmarc_Lib;
81 	errno = 0;
82 	if ((Opendmarc_Libp->tld_source_file)[0] != '\0')
83 	{
84 		switch (Opendmarc_Libp->tld_type)
85 		{
86 			case OPENDMARC_TLD_TYPE_MOZILLA:
87 				ret = opendmarc_tld_read_file(Opendmarc_Libp->tld_source_file,
88 						"//", "*.", "!");
89 				if (ret != 0)
90 					ret = errno;
91 				break;
92 			default:
93 				return DMARC_TLD_ERROR_UNKNOWN;
94 		}
95 	}
96 	return ret;
97 }
98 
99 /**************************************************************************
100 ** OPENDMARC_POLICY_LIBRARY_SHUTDOWN -- Shutdown The Libarary
101 **	Parameters:
102 **		lib_init	-- The prior DMARC_LIB_T strucgture
103 **	Returns:
104 **		DMARC_PARSE_OKAY		-- always
105 **	Side Effects:
106 **		May free memory
107 **	Warning:
108 ***************************************************************************/
109 OPENDMARC_STATUS_T
opendmarc_policy_library_shutdown(OPENDMARC_LIB_T * lib_init)110 opendmarc_policy_library_shutdown(OPENDMARC_LIB_T *lib_init)
111 {
112 	(void) opendmarc_tld_shutdown();
113 	return DMARC_PARSE_OKAY;
114 }
115 
116 /**************************************************************************
117 ** OPENDMARC_POLICY_CONNECT_INIT -- Get policy context for connection
118 **	Parameters:
119 **		ip_addr	-- An IP addresss in string form.
120 **		is_ipv6 -- Zero for IPv4, non-zero for IPv6
121 **	Returns:
122 **		pctx 	-- An allocated and initialized context pointer.
123 **		NULL	-- On failure and sets errno
124 **	Side Effects:
125 **		Allocates memory.
126 ***************************************************************************/
127 DMARC_POLICY_T *
opendmarc_policy_connect_init(u_char * ip_addr,int is_ipv6)128 opendmarc_policy_connect_init(u_char *ip_addr, int is_ipv6)
129 {
130 	DMARC_POLICY_T *pctx;
131 	int		xerrno;
132 
133 	if (ip_addr == NULL)
134 	{
135 		errno = EINVAL;
136 		return NULL;
137 	}
138 	pctx = malloc(sizeof(DMARC_POLICY_T));
139 	if (pctx == NULL)
140 	{
141 		return NULL;
142 	}
143 	(void) memset(pctx, '\0', sizeof(DMARC_POLICY_T));
144 	pctx->p = DMARC_RECORD_P_UNSPECIFIED;
145 	pctx->sp = DMARC_RECORD_P_UNSPECIFIED;
146 	pctx->ip_addr = (u_char *)strdup((char *)ip_addr);
147 	if (pctx->ip_addr == NULL)
148 	{
149 		xerrno = errno;
150 		(void) free(pctx);
151 		errno = xerrno;
152 		return NULL;
153 	}
154 	if (is_ipv6 == 0)
155 		pctx->ip_type = DMARC_POLICY_IP_TYPE_IPV4;
156 	else
157 		pctx->ip_type = DMARC_POLICY_IP_TYPE_IPV6;
158 	return pctx;
159 }
160 
161 /**************************************************************************
162 ** OPENDMARC_POLICY_CONNECT_CLEAR -- Zero the policy context but doesn't
163 **					free it
164 **
165 **	Parameters:
166 **		pctx	-- The context to zero.
167 **	Returns:
168 **		pctx 	-- Zeroed but still allocated context
169 **		NULL	-- On failure and sets errno
170 **	Side Effects:
171 **		Frees memory.
172 ***************************************************************************/
173 DMARC_POLICY_T *
opendmarc_policy_connect_clear(DMARC_POLICY_T * pctx)174 opendmarc_policy_connect_clear(DMARC_POLICY_T *pctx)
175 {
176 	if (pctx == NULL)
177 	{
178 		errno = EINVAL;
179 		return NULL;
180 	}
181 
182 	if (pctx->ip_addr != NULL)
183 		(void) free(pctx->ip_addr);
184 	if (pctx->from_domain != NULL)
185 		(void) free(pctx->from_domain);
186 	if (pctx->spf_domain != NULL)
187 		(void) free(pctx->spf_domain);
188 	if (pctx->dkim_domain != NULL)
189 		(void) free(pctx->dkim_domain);
190 	if (pctx->dkim_selector != NULL)
191 		(void) free(pctx->dkim_selector);
192 	if (pctx->spf_human_outcome != NULL)
193 		(void) free(pctx->spf_human_outcome);
194 	if (pctx->dkim_human_outcome != NULL)
195 		(void) free(pctx->dkim_human_outcome);
196 	if (pctx->organizational_domain != NULL)
197 		(void) free(pctx->organizational_domain);
198 	pctx->rua_list = opendmarc_util_clearargv(pctx->rua_list);
199 	pctx->rua_cnt  = 0;
200 	pctx->ruf_list = opendmarc_util_clearargv(pctx->ruf_list);
201 	pctx->ruf_cnt  = 0;
202 	pctx->fo       = 0;
203 
204 	(void) memset(pctx, '\0', sizeof(DMARC_POLICY_T));
205 	pctx->p   = DMARC_RECORD_P_UNSPECIFIED;
206 	return pctx;
207 }
208 
209 /**************************************************************************
210 ** OPENDMARC_POLICY_CONNECT_RSET -- Rset for another message
211 **	Usefull if there is more than a single envelope per connection.
212 **	Usefull during an SMTP  RSET
213 **
214 **	Parameters:
215 **		pctx	-- The context to rset.
216 **	Returns:
217 **		pctx 	-- RSET context
218 **		NULL	-- On failure and sets errno
219 **	Side Effects:
220 **		Frees memory.
221 **		Preserves the IP address and type
222 ***************************************************************************/
223 DMARC_POLICY_T *
opendmarc_policy_connect_rset(DMARC_POLICY_T * pctx)224 opendmarc_policy_connect_rset(DMARC_POLICY_T *pctx)
225 {
226 	u_char *ip_save;
227 	int     ip_type;
228 
229 	if (pctx == NULL)
230 	{
231 		errno = EINVAL;
232 		return NULL;
233 	}
234 
235 	ip_save       = pctx->ip_addr;
236 	pctx->ip_addr = NULL;
237 	ip_type       = pctx->ip_type;
238 	pctx->ip_type = -1;
239 
240 	pctx = opendmarc_policy_connect_clear(pctx);
241 
242 	if (pctx == NULL)
243 		return NULL;
244 	pctx->ip_addr = ip_save;
245 	pctx->ip_type = ip_type;
246 	return pctx;
247 }
248 
249 /**************************************************************************
250 ** OPENDMARC_POLICY_CONNECT_SHUTDOWN -- Free the policy context
251 **	Frees and deallocates the context
252 **
253 **	Parameters:
254 **		pctx	-- The context to free and deallocate.
255 **	Returns:
256 **		NULL	-- Always
257 **	Side Effects:
258 **		Frees memory.
259 ***************************************************************************/
260 DMARC_POLICY_T *
opendmarc_policy_connect_shutdown(DMARC_POLICY_T * pctx)261 opendmarc_policy_connect_shutdown(DMARC_POLICY_T *pctx)
262 {
263 	if (pctx != NULL)
264 	{
265 		pctx = opendmarc_policy_connect_clear(pctx);
266 		(void) free(pctx);
267 		pctx = NULL;
268 	}
269 	return pctx;
270 }
271 
272 int
opendmarc_policy_check_alignment(u_char * subdomain,u_char * tld,int mode)273 opendmarc_policy_check_alignment(u_char *subdomain, u_char *tld, int mode)
274 {
275 	u_char rev_sub[512];
276 	u_char rev_tld[512];
277 	u_char tld_buf[512];
278 	u_char *ep;
279 	int	ret;
280 
281 	if (subdomain == NULL)
282 		return EINVAL;
283 	if (tld == NULL)
284 		return EINVAL;
285 
286 	if (mode== DMARC_RECORD_A_UNSPECIFIED)
287 		mode= DMARC_RECORD_A_RELAXED;
288 
289 	(void) memset(tld_buf, '\0', sizeof tld_buf);
290 	(void) strlcpy(tld_buf, tld, sizeof tld_buf);
291 
292 	(void) memset(rev_sub, '\0', sizeof rev_sub);
293 	(void) opendmarc_reverse_domain(subdomain, rev_sub, sizeof rev_sub);
294 	ep = rev_sub + strlen(rev_sub) -1;
295 	if (*ep != '.')
296 		(void) strlcat((char *)rev_sub, ".", sizeof rev_sub);
297 
298 	(void) memset(rev_tld, '\0', sizeof rev_tld);
299 	(void) opendmarc_reverse_domain(tld_buf,   rev_tld, sizeof rev_tld);
300 	ep = rev_tld + strlen(rev_tld) -1;
301 	if (*ep != '.')
302 		(void) strlcat((char *)rev_tld, ".", sizeof rev_tld);
303 
304 	/*
305 	 * Perfect match is aligned irrespective of relaxed or strict.
306 	 */
307 	if (strcasecmp(rev_tld, rev_sub) == 0)
308 		return 0;
309 
310 	ret = strncasecmp(rev_tld, rev_sub, strlen(rev_tld));
311 	if (ret == 0 && mode == DMARC_RECORD_A_RELAXED)
312 			return 0;
313 
314         ret = strncasecmp(rev_sub, rev_tld, strlen(rev_sub));
315         if (ret == 0 && mode == DMARC_RECORD_A_RELAXED)
316                         return 0;
317 
318 	ret = opendmarc_get_tld(tld, tld_buf, sizeof tld_buf);
319 	if (ret != 0)
320 		return -1;
321 	(void) memset(rev_tld, '\0', sizeof rev_tld);
322 	(void) opendmarc_reverse_domain(tld_buf,   rev_tld, sizeof rev_tld);
323 	ep = rev_tld + strlen(rev_tld) -1;
324 	if (*ep != '.')
325 		(void) strlcat((char *)rev_tld, ".", sizeof rev_tld);
326 
327 	/*
328 	 * Perfect match is aligned irrespective of relaxed or strict.
329 	 */
330 	if (strcasecmp(rev_tld, rev_sub) == 0)
331 		return 0;
332 
333 	ret = strncasecmp(rev_tld, rev_sub, strlen(rev_tld));
334 	if (ret == 0 && mode == DMARC_RECORD_A_RELAXED)
335 			return 0;
336 
337         ret = strncasecmp(rev_sub, rev_tld, strlen(rev_sub));
338         if (ret == 0 && mode == DMARC_RECORD_A_RELAXED)
339                         return 0;
340 	return -1;
341 }
342 
343 /**************************************************************************
344 ** OPENDMARC_POLICY_STORE_FROM_DOMAIN -- Store domain from the From: header.
345 ** 	If the domain is an address parse the domain from it.
346 **	The domain is needed to perform alignment checks.
347 
348 **	Parameters:
349 **		pctx		-- The context to uptdate
350 **		from_domain 	-- A string
351 **	Returns:
352 **		DMARC_PARSE_OKAY		-- On success
353 **		DMARC_PARSE_ERROR_NULL_CTX	-- If pctx was NULL
354 **		DMARC_PARSE_ERROR_EMPTY		-- if from_domain NULL or zero
355 **		DMARC_PARSE_ERROR_NO_DOMAIN	-- No domain in from_domain
356 **	Side Effects:
357 **		Allocates memory.
358 **	Note:
359 **		Does not check to insure that the found domain is a
360 **		syntactically valid domain. It is okay for domain to
361 **		puney decoded into 8-bit data.
362 ***************************************************************************/
363 OPENDMARC_STATUS_T
opendmarc_policy_store_from_domain(DMARC_POLICY_T * pctx,u_char * from_domain)364 opendmarc_policy_store_from_domain(DMARC_POLICY_T *pctx, u_char *from_domain)
365 {
366 	char domain_buf[256];
367 	char *dp;
368 
369 	if (pctx == NULL)
370 		return DMARC_PARSE_ERROR_NULL_CTX;
371 	if (from_domain == NULL || strlen((char *)from_domain) == 0)
372 		return DMARC_PARSE_ERROR_EMPTY;
373 	dp = opendmarc_util_finddomain(from_domain, domain_buf, sizeof domain_buf);
374 	if (dp == NULL)
375 		return DMARC_PARSE_ERROR_NO_DOMAIN;
376 	pctx->from_domain = strdup((char *)dp);
377 	if (pctx->from_domain == NULL)
378 		return DMARC_PARSE_ERROR_NO_ALLOC;
379 	return DMARC_PARSE_OKAY;
380 }
381 
382 /**************************************************************************
383 ** OPENDMARC_POLICY_STORE_SPF -- Store spf results
384 **	Okay to supply the raw MAIL From: data
385 **
386 **	Parameters:
387 **		pctx	-- The context to uptdate
388 **		domain 	-- The domain used to verify SPF
389 **		result 	-- DMARC_POLICY_SPF_OUTCOME_NONE
390 **			or DMARC_POLICY_SPF_OUTCOME_PASS
391 **			or DMARC_POLICY_SPF_OUTCOME_FAIL
392 **			or DMARC_POLICY_SPF_OUTCOME_TMPFAIL
393 **		origin 	-- DMARC_POLICY_SPF_ORIGIN_MAILFROM
394 **			or DMARC_POLICY_SPF_ORIGIN_HELO
395 **		human_readable -- A human readable reason for failure
396 **	Returns:
397 **		DMARC_PARSE_OKAY		-- On success
398 **		DMARC_PARSE_ERROR_NULL_CTX	-- If pctx was NULL
399 **		DMARC_PARSE_ERROR_EMPTY		-- if domain NULL or zero
400 **		DMARC_PARSE_ERROR_NO_DOMAIN	-- No domain in domain
401 **	Side Effects:
402 **		Allocates memory.
403 **	Note:
404 **		Does not check to insure that the domain is a
405 **		syntactically valid domain. It is okay for domain to
406 **		puney decoded into 8-bit data.
407 ***************************************************************************/
408 OPENDMARC_STATUS_T
opendmarc_policy_store_spf(DMARC_POLICY_T * pctx,u_char * domain,int result,int origin,u_char * human_readable)409 opendmarc_policy_store_spf(DMARC_POLICY_T *pctx, u_char *domain, int result, int origin, u_char *human_readable)
410 {
411 	char domain_buf[256];
412 	char *dp;
413 
414 	if (pctx == NULL)
415 		return DMARC_PARSE_ERROR_NULL_CTX;
416 	if (domain == NULL || strlen((char *)domain) == 0)
417 		return DMARC_PARSE_ERROR_EMPTY;
418 	dp = opendmarc_util_finddomain(domain, domain_buf, sizeof domain_buf);
419 	if (dp == NULL)
420 		return DMARC_PARSE_ERROR_NO_DOMAIN;
421 	if (!check_domain(dp))
422 		return DMARC_PARSE_ERROR_BAD_VALUE;
423 	if (human_readable != NULL)
424 		pctx->spf_human_outcome = strdup((char *)human_readable);
425 	pctx->spf_domain = strdup((char *)dp);
426 	if (pctx->spf_domain == NULL)
427 		return DMARC_PARSE_ERROR_NO_ALLOC;
428 	switch (result)
429 	{
430 		case DMARC_POLICY_SPF_OUTCOME_NONE:
431 		case DMARC_POLICY_SPF_OUTCOME_PASS:
432 		case DMARC_POLICY_SPF_OUTCOME_FAIL:
433 		case DMARC_POLICY_SPF_OUTCOME_TMPFAIL:
434 			pctx->spf_outcome = result;
435 			break;
436 		default:
437 			return DMARC_PARSE_ERROR_BAD_SPF_MACRO;
438 	}
439 	switch (origin)
440 	{
441 		case DMARC_POLICY_SPF_ORIGIN_MAILFROM:
442 		case DMARC_POLICY_SPF_ORIGIN_HELO:
443 			pctx->spf_origin = origin;
444 			break;
445 		default:
446 			return DMARC_PARSE_ERROR_BAD_SPF_MACRO;
447 	}
448 	return DMARC_PARSE_OKAY;
449 }
450 
451 
452 /**************************************************************************
453 ** OPENDMARC_POLICY_STORE_DKIM -- Store dkim results
454 **
455 **	Parameters:
456 **		pctx			-- The context to update
457 **		d_equal_domain 		-- The the domain from the d=
458 **		s_equal_selector	-- The selector from the s=
459 **		dkim_result 		-- DMARC_POLICY_DKIM_OUTCOME_NONE
460 **					or DMARC_POLICY_DKIM_OUTCOME_PASS
461 **					or DMARC_POLICY_DKIM_OUTCOME_FAIL
462 **					or DMARC_POLICY_DKIM_OUTCOME_TMPFAIL
463 **		human_result		-- A human readable reason for failure
464 **	Returns:
465 **		DMARC_PARSE_OKAY		-- On success
466 **		DMARC_PARSE_ERROR_NULL_CTX	-- If pctx was NULL
467 **		DMARC_PARSE_ERROR_EMPTY		-- if domain NULL or zero
468 **		DMARC_PARSE_ERROR_NO_DOMAIN	-- No domain in domain
469 **		DMARC_PARSE_ERROR_NO_ALLOC	-- Memory allocation failed
470 **		DMARC_FROM_DOMAIN_ABSENT	-- No From: domain
471 **	Side Effects:
472 **		Allocates memory.
473 **	Note:
474 **		Does not check to insure that the domain is a
475 **		syntactically valid domain. It is okay for domain to
476 **		puney decoded into 8-bit data.
477 ***************************************************************************/
478 OPENDMARC_STATUS_T
opendmarc_policy_store_dkim(DMARC_POLICY_T * pctx,u_char * d_equal_domain,u_char * s_equal_selector,int dkim_result,u_char * human_result)479 opendmarc_policy_store_dkim(DMARC_POLICY_T *pctx, u_char *d_equal_domain,
480 	u_char *s_equal_selector, int dkim_result, u_char *human_result)
481 {
482 	char	domain_buf[256];
483 	u_char *dp;
484 	int	result = DMARC_POLICY_DKIM_OUTCOME_NONE;
485 
486 	if (pctx == NULL)
487 		return DMARC_PARSE_ERROR_NULL_CTX;
488 	if (d_equal_domain == NULL || strlen((char *)d_equal_domain) == 0)
489 		return DMARC_PARSE_ERROR_EMPTY;
490 	if (pctx->from_domain == NULL)
491 		return DMARC_FROM_DOMAIN_ABSENT;
492 	if (!check_domain(d_equal_domain))
493 		return DMARC_PARSE_ERROR_BAD_VALUE;
494 
495 	switch (dkim_result)
496 	{
497 		case DMARC_POLICY_DKIM_OUTCOME_NONE:
498 		case DMARC_POLICY_DKIM_OUTCOME_PASS:
499 		case DMARC_POLICY_DKIM_OUTCOME_FAIL:
500 		case DMARC_POLICY_DKIM_OUTCOME_TMPFAIL:
501 			result = dkim_result;
502 			break;
503 		default:
504 			return DMARC_PARSE_ERROR_BAD_DKIM_MACRO;
505 	}
506 	if (pctx->dkim_final == TRUE)
507 		return DMARC_PARSE_OKAY;
508 
509 	dp = opendmarc_util_finddomain(d_equal_domain, domain_buf, sizeof domain_buf);
510 	if (dp == NULL || strlen(dp) == 0)
511 		return DMARC_PARSE_ERROR_NO_DOMAIN;
512 
513 	/*
514 	 * If the d= domain is an exact match to the from_domain
515 	 * select this one as the domain of choice.
516 	 * If the outcome is pass, make this the final choice.
517 	 */
518 	if (strcasecmp((char *)dp, pctx->from_domain) == 0)
519 	{
520 		if (pctx->dkim_domain != NULL)
521 		{
522 			(void) free(pctx->dkim_domain);
523 			pctx->dkim_domain = NULL;
524 		}
525 		if (pctx->dkim_selector != NULL)
526 		{
527 			(void) free(pctx->dkim_selector);
528 			pctx->dkim_selector = NULL;
529 		}
530 		if (result == DMARC_POLICY_DKIM_OUTCOME_PASS)
531 		{
532 			pctx->dkim_final = TRUE;
533 			goto set_final;
534 		}
535 		if (pctx->dkim_outcome == DMARC_POLICY_DKIM_OUTCOME_PASS)
536 			return DMARC_PARSE_OKAY;
537 		goto set_final;
538 	}
539 
540 	/*
541 	 * See if the d= is a superset of the from domain.
542 	 * If so and if we have not already found
543 	 * a best match, make this the temporary best match.
544 	 */
545 	if (opendmarc_policy_check_alignment(dp, pctx->from_domain,
546 	                                     pctx->adkim) == 0)
547 	{
548 		if (pctx->dkim_domain != NULL)
549 		{
550 			(void) free(pctx->dkim_domain);
551 			pctx->dkim_domain = NULL;
552 		}
553 		if (pctx->dkim_selector != NULL)
554 		{
555 			(void) free(pctx->dkim_selector);
556 			pctx->dkim_selector = NULL;
557 		}
558 		if (result == DMARC_POLICY_DKIM_OUTCOME_PASS)
559 			goto set_final;
560 	}
561 	/*
562 	 * If we found any record so far that passed, preserve it; if the new entry
563 	 * is not aligned, only replace an existing one by an unaligned one if it was
564 	 * not a pass, but make sure to update the domain in that case!
565 	 */
566 	if (pctx->dkim_outcome == DMARC_POLICY_DKIM_OUTCOME_PASS) {
567 		return DMARC_PARSE_OKAY;
568 	} else {
569 		(void) free(pctx->dkim_domain);
570 		pctx->dkim_domain = NULL;
571 	}
572 
573 set_final:
574 	if (pctx->dkim_domain == NULL)
575 		pctx->dkim_domain = strdup((char *)dp);
576 	if (pctx->dkim_domain == NULL)
577 		return DMARC_PARSE_ERROR_NO_ALLOC;
578 	if (pctx->dkim_selector == NULL && s_equal_selector != NULL)
579 		pctx->dkim_selector = strdup((char *)s_equal_selector);
580 	if (human_result != NULL)
581 	{
582 		if (pctx->dkim_human_outcome != NULL)
583 			(void) free(pctx->dkim_human_outcome);
584 		pctx->dkim_human_outcome = strdup((char *)human_result);
585 	}
586 	pctx->dkim_outcome = result;
587 	return DMARC_PARSE_OKAY;
588 }
589 
590 /**************************************************************************
591 ** OPENDMARC_POLICY_QUERY_DMARC_XDOMAIN -- Verify that we have permission
592 **										to send to domain
593 **	Parameters:
594 **		pctx	-- The context to uptdate
595 **		uri		-- URI listed in DMARC record
596 **	Returns:
597 **		DMARC_PARSE_OKAY		-- On success, and fills pctx
598 **		DMARC_PARSE_ERROR_NULL_CTX	-- If pctx was NULL
599 **		DMARC_PARSE_ERROR_EMPTY		-- if domain NULL or zero
600 **		DMARC_PARSE_ERROR_NO_DOMAIN	-- No domain in domain
601 **		DMARC_DNS_ERROR_TMPERR		-- No domain, try again later
602 **		DMARC_DNS_ERROR_NO_RECORD	-- No DMARC record found.
603 **	Side Effects:
604 **		Performs one or more DNS lookups
605 **
606 ***************************************************************************/
607 OPENDMARC_STATUS_T
opendmarc_policy_query_dmarc_xdomain(DMARC_POLICY_T * pctx,u_char * uri)608 opendmarc_policy_query_dmarc_xdomain(DMARC_POLICY_T *pctx, u_char *uri)
609 {
610 	u_char buf[BUFSIZ];
611 	u_char copy[256];
612 	u_char domain[256];
613 	u_char domain_tld[256];
614 	u_char uri_tld[256];
615 	u_char *ret = NULL;
616 	int dns_reply = 0;
617 	int i = 0;
618 	int err = 0;
619 
620 	if (pctx == NULL || pctx->from_domain == NULL)
621 		return DMARC_PARSE_ERROR_NULL_CTX;
622 
623 	if (uri == NULL)
624 		return DMARC_PARSE_ERROR_EMPTY;
625 
626 	memset(buf, '\0', sizeof buf);
627 	memset(copy, '\0', sizeof copy);
628 	memset(domain, '\0', sizeof domain);
629 	memset(domain_tld, '\0', sizeof domain_tld);
630 	memset(uri_tld, '\0', sizeof uri_tld);
631 
632 	/* Get out domain from our URI */
633 	if (strncasecmp(uri, "mailto:", 7) == 0)
634 		uri += 7;
635 
636 	if (opendmarc_util_finddomain(uri, domain, sizeof domain) == NULL)
637 		return DMARC_PARSE_ERROR_NO_DOMAIN;
638 
639 	/* Ensure that we're not doing a cross-domain check */
640 	err = 0;
641 	err = opendmarc_get_tld(domain, uri_tld, sizeof uri_tld);
642 	err += opendmarc_get_tld(pctx->from_domain, domain_tld, sizeof domain_tld);
643 	if (err != 0)
644 		return DMARC_DNS_ERROR_NO_RECORD;
645 
646 	if (strncasecmp((char *) uri_tld, (char *) domain_tld,
647 	                sizeof uri_tld) == 0)
648 		return DMARC_PARSE_OKAY;
649 
650 	strlcpy((char *) copy, (char *) pctx->from_domain, sizeof copy);
651 	strlcat((char *) copy, "._report._dmarc.", sizeof copy);
652 	strlcat((char *) copy, (char *) domain, sizeof copy);
653 
654 	/* Query DNS */
655 	for (i = 0; i < DNS_MAX_RETRIES && ret == NULL; i++)
656 	{
657 		ret = (u_char *) dmarc_dns_get_record((char *) copy, &dns_reply,
658 		                                      (char *) buf, sizeof buf);
659 		if (ret != 0 || dns_reply == HOST_NOT_FOUND)
660 			break;
661 
662 		/* requery if didn't resolve CNAME */
663 		if (ret == NULL && *buf != '\0')
664 		{
665 			strlcpy((char *) copy, (char *) buf, sizeof copy);
666 			continue;
667 		}
668 	}
669 	if (dns_reply == NETDB_SUCCESS && strcmp( buf, "&" ) != 0)
670 	{
671 		/* Must include DMARC version */
672 		if (strncasecmp((char *)buf, "v=DMARC1", sizeof buf) == 0)
673 		{
674 			return DMARC_PARSE_OKAY;
675 		}
676 	}
677 
678 	/*
679 	** Retry with a * literal.
680 	*/
681 	strlcpy((char *) copy, (char *) "*", sizeof copy);
682 	strlcat((char *) copy, "._report._dmarc.", sizeof copy);
683 	strlcat((char *) copy, (char *) domain, sizeof copy);
684 	for (i = 0; i < DNS_MAX_RETRIES && ret == NULL; i++)
685 	{
686 		ret = (u_char *) dmarc_dns_get_record((char *) copy, &dns_reply,
687 		                                      (char *) buf, sizeof buf);
688 		if (ret != 0 || dns_reply == HOST_NOT_FOUND)
689 			break;
690 
691 		/* requery if didn't resolve CNAME */
692 		if (ret == NULL && *buf != '\0')
693 		{
694 			strlcpy((char *) copy, (char *) buf, sizeof copy);
695 			continue;
696 		}
697 	}
698 	if (dns_reply == NETDB_SUCCESS && strcmp( buf, "&" ) != 0)
699 	{
700 		/* Must include DMARC version */
701 		if (strncasecmp((char *)buf, "v=DMARC1", sizeof buf) == 0)
702 		{
703 			return DMARC_PARSE_OKAY;
704 		}
705 		else
706 		{
707 			return DMARC_DNS_ERROR_NO_RECORD;
708 		}
709 	}
710 
711 	switch (dns_reply)
712 	{
713 		case HOST_NOT_FOUND:
714 		case NO_DATA:
715 		case NO_RECOVERY:
716 			return DMARC_DNS_ERROR_NO_RECORD;
717 		case TRY_AGAIN:
718 		case NETDB_INTERNAL:
719 			return DMARC_DNS_ERROR_TMPERR;
720 		default:
721 			return DMARC_DNS_ERROR_NO_RECORD;
722 	}
723 }
724 
725 /**************************************************************************
726 ** OPENDMARC_POLICY_QUERY_DMARC -- Look up the _dmarc record for the
727 **					specified domain. If not found
728 **				  	try the organizational domain.
729 **	Parameters:
730 **		pctx	-- The context to uptdate
731 **		domain 	-- The domain for which to lookup the DMARC record
732 **	Returns:
733 **		DMARC_PARSE_OKAY		-- On success, and fills pctx
734 **		DMARC_PARSE_ERROR_NULL_CTX	-- If pctx was NULL
735 **		DMARC_PARSE_ERROR_EMPTY		-- if domain NULL or zero
736 **		DMARC_PARSE_ERROR_NO_DOMAIN	-- No domain in domain
737 **		DMARC_DNS_ERROR_NXDOMAIN	-- No domain found in DNS
738 **		DMARC_DNS_ERROR_TMPERR		-- No domain, try again later
739 **		DMARC_DNS_ERROR_NO_RECORD	-- No DMARC record found.
740 **	Side Effects:
741 **		Performs one or more DNS lookups
742 **		Allocates memory.
743 **	Note:
744 **		Does not check to insure that the domain is a
745 **		syntactically valid domain.
746 **		Looks up domain first. If that fails, finds the tld and
747 **		looks up topmost domain under tld. If this later is found
748 **		updates pctx->organizational_domain with the result.
749 **	Warning:
750 **		If no TLD file has been loaded, will silenty not do that
751 **		fallback lookup.
752 **
753 ***************************************************************************/
754 OPENDMARC_STATUS_T
opendmarc_policy_query_dmarc(DMARC_POLICY_T * pctx,u_char * domain)755 opendmarc_policy_query_dmarc(DMARC_POLICY_T *pctx, u_char *domain)
756 {
757 	u_char 		buf[BUFSIZ];
758 	u_char 		copy[256];
759 	u_char 		tld[256];
760 	u_char *	bp = NULL;
761 	int		dns_reply = 0;
762 	int		tld_reply = 0;
763 	int		loop_count = DNS_MAX_RETRIES;
764 
765 	if (pctx == NULL)
766 		return DMARC_PARSE_ERROR_NULL_CTX;
767 	if (domain == NULL || strlen(domain) == 0)
768 	{
769 		if (pctx->from_domain != NULL)
770 			domain = pctx->from_domain;
771 		else
772 			return DMARC_PARSE_ERROR_EMPTY;
773 	}
774 
775 	(void) strlcpy(copy, "_dmarc.", sizeof copy);
776 	(void) strlcat(copy, domain, sizeof copy);
777 
778 query_again:
779 	(void) memset(buf, '\0', sizeof buf);
780 	bp = dmarc_dns_get_record(copy, &dns_reply, buf, sizeof buf);
781 	if (bp != NULL)
782 	{
783 		if (dns_reply != HOST_NOT_FOUND)
784 			goto got_record;
785 	}
786 	/*
787 	 * Was a CNAME was found that the resolver did
788 	 * not follow on its own?
789 	 */
790 	if (bp == NULL && *buf != '\0')
791 	{
792 		(void) strlcpy(copy, buf, sizeof copy);
793 		if (--loop_count != 0)
794 			goto query_again;
795 	}
796 
797 	(void) memset(tld, '\0', sizeof tld);
798 	tld_reply = opendmarc_get_tld(domain, tld, sizeof tld);
799 	if (tld_reply != 0)
800 		goto dns_failed;
801 	if (strlen(tld) > 0)
802 	{
803 		pctx->organizational_domain = strdup(tld);
804 
805 		loop_count = DNS_MAX_RETRIES;
806 		(void) strlcpy(copy, "_dmarc.", sizeof copy);
807 		(void) strlcat(copy, tld, sizeof copy);
808 query_again2:
809 		(void) memset(buf, '\0', sizeof buf);
810 		bp = dmarc_dns_get_record(copy, &dns_reply, buf, sizeof buf);
811 		if (bp != NULL)
812 			goto got_record;
813 		/*
814 		 * Was a CNAME was found that the resolver did
815 		 * not follow on its own?
816 		 */
817 		if (bp == NULL && *buf != '\0')
818 		{
819 			(void) strlcpy(copy, buf, sizeof copy);
820 			if (--loop_count != 0)
821 				goto query_again2;
822 		}
823 	}
824 dns_failed:
825 	switch (dns_reply)
826 	{
827 		case HOST_NOT_FOUND:
828 		case NO_DATA:
829 		case NO_RECOVERY:
830 			return DMARC_DNS_ERROR_NO_RECORD;
831 		case TRY_AGAIN:
832 		case NETDB_INTERNAL:
833 			return DMARC_DNS_ERROR_TMPERR;
834 		default:
835 			return DMARC_DNS_ERROR_NO_RECORD;
836 
837 	}
838 got_record:
839 	return opendmarc_policy_parse_dmarc(pctx, domain, buf);
840 }
841 
842 /**************************************************************************
843 ** OPENDMARC_GET_POLICY_TO_ENFORCE -- What to do with this message. i.e. allow
844 **				possible delivery, quarantine, or reject.
845 **	Parameters:
846 **		pctx	-- A Policy context
847 **	Returns:
848 **		DMARC_PARSE_ERROR_NULL_CTX	-- pctx == NULL
849 **		DMARC_POLICY_ABSENT		-- No DMARC record found
850 **		DMARC_FROM_DOMAIN_ABSENT	-- No From: domain
851 **		DMARC_POLICY_NONE		-- Accept if other policy allows
852 **		DMARC_POLICY_REJECT		-- Policy advises to reject the message
853 **		DMARC_POLICY_QUARANTINE		-- Policy advises to quarantine the message
854 **		DMARC_POLICY_PASS		-- Policy advises to accept the message
855 **	Side Effects:
856 **		Checks for domain alignment.
857 ***************************************************************************/
858 OPENDMARC_STATUS_T
opendmarc_get_policy_to_enforce(DMARC_POLICY_T * pctx)859 opendmarc_get_policy_to_enforce(DMARC_POLICY_T *pctx)
860 {
861 
862 	if (pctx == NULL)
863 		return DMARC_PARSE_ERROR_NULL_CTX;
864 
865 	if (pctx->p == DMARC_RECORD_P_UNSPECIFIED)
866 		return DMARC_POLICY_ABSENT;
867 
868 	if (pctx->from_domain == NULL)
869 		return DMARC_FROM_DOMAIN_ABSENT;
870 
871 	pctx->dkim_alignment = DMARC_POLICY_DKIM_ALIGNMENT_FAIL;
872 	pctx->spf_alignment  = DMARC_POLICY_SPF_ALIGNMENT_FAIL;
873 
874 	/* check for DKIM alignment */
875 	if (pctx->dkim_domain != NULL && pctx->dkim_outcome == DMARC_POLICY_DKIM_OUTCOME_PASS)
876 	{
877 		if (opendmarc_policy_check_alignment(pctx->from_domain, pctx->dkim_domain, pctx->adkim) == 0)
878 			pctx->dkim_alignment = DMARC_POLICY_DKIM_ALIGNMENT_PASS;
879 	}
880 
881 	/* check for SPF alignment */
882 	if (pctx->spf_domain != NULL && pctx->spf_outcome == DMARC_POLICY_SPF_OUTCOME_PASS)
883 	{
884 		if (opendmarc_policy_check_alignment(pctx->from_domain, pctx->spf_domain, pctx->aspf) == 0)
885 			pctx->spf_alignment = DMARC_POLICY_SPF_ALIGNMENT_PASS;
886 	}
887 
888 	/*
889 	 * If dkim passes and dkim aligns OR spf passes and spf aligns
890 	 * Accept the message.
891 	 */
892 	if (pctx->spf_alignment == DMARC_POLICY_SPF_ALIGNMENT_PASS ||
893 	    pctx->dkim_alignment == DMARC_POLICY_DKIM_ALIGNMENT_PASS)
894 		return DMARC_POLICY_PASS;
895 
896 	if (pctx->organizational_domain != NULL)
897 	{
898 		switch (pctx->sp)
899 		{
900 		  case DMARC_RECORD_P_REJECT:
901 			return DMARC_POLICY_REJECT;
902 
903 		  case DMARC_RECORD_P_QUARANTINE:
904 			return DMARC_POLICY_QUARANTINE;
905 
906 		  case DMARC_RECORD_P_NONE:
907 			return DMARC_POLICY_NONE;
908 		}
909 	}
910 
911 	switch (pctx->p)
912 	{
913 		case DMARC_RECORD_P_REJECT:
914 			return DMARC_POLICY_REJECT;
915 		case DMARC_RECORD_P_QUARANTINE:
916 			return DMARC_POLICY_QUARANTINE;
917 		case DMARC_RECORD_P_NONE:
918 			return DMARC_POLICY_NONE;
919 		default:
920 			/* XXX -- shouldn't be possible */
921 			return DMARC_POLICY_PASS;
922 	}
923 }
924 
925 /*******************************************************************************
926 **  OPENDMARC_PARSE_DMARC -- Parse a DMARC record
927 **
928 **	Parameters:
929 **		pctx	-- A Policy context
930 **		domain  -- The domain looked up to get this DMARC record
931 **		record	-- The DMARC record to parse
932 **	Returns:
933 **		DMARC_PARSE_ERROR_EMPTY 	-- if any argument is NULL
934 **		DMARC_PARSE_ERROR_BAD_VERSION	-- if v= was bad
935 **		DMARC_PARSE_ERROR_BAD_VALUE	-- if value following = was bad
936 **		DMARC_PARSE_ERROR_NO_REQUIRED_P -- if p= was absent
937 **		DMARC_PARSE_OKAY		-- On Success
938 **	Side Effects:
939 **		Allocates memory.
940 *********************************************************************************/
941 OPENDMARC_STATUS_T
opendmarc_policy_parse_dmarc(DMARC_POLICY_T * pctx,u_char * domain,u_char * record)942 opendmarc_policy_parse_dmarc(DMARC_POLICY_T *pctx, u_char *domain, u_char *record)
943 {
944 	u_char *cp, *eqp, *ep, *sp, *vp;
945 	u_char copy[BUFSIZ];
946 	u_char cbuf[512];
947 	u_char vbuf[512];
948 
949 	if (pctx == NULL || domain == NULL || record == NULL || strlen((char *)record) == 0)
950 	{
951 		return DMARC_PARSE_ERROR_EMPTY;
952 	}
953 	/*
954 	 * Set the defaults to detect missing required items.
955 	 */
956 	pctx->pct = -1;
957 	pctx->ri  = -1;
958 
959 	(void) memset((char *)copy, '\0', sizeof copy);
960 	(void) strlcpy((char *)copy, (char *)record, sizeof copy);
961 	ep = copy + strlen((char *)copy);
962 
963 
964 	for (cp = copy; cp != NULL && cp <= ep; )
965 	{
966 		sp = (u_char *)strchr(cp, ';');
967 		if (sp != NULL)
968 			*sp++ = '\0';
969 		eqp = (u_char *)strchr((char *)cp, '=');
970 		if (eqp == NULL)
971 		{
972 			cp = sp;
973 			continue;
974 		}
975 		*eqp = '\0';
976 		vp = eqp + 1;
977 
978 		cp = opendmarc_util_cleanup(cp, cbuf, sizeof cbuf);
979 		if (cp == NULL || strlen((char *)cp) == 0)
980 		{
981 			cp = sp;
982 			continue;
983 		}
984 		vp = opendmarc_util_cleanup(vp, vbuf, sizeof vbuf);
985 		if (vp == NULL || strlen((char *)vp) == 0)
986 		{
987 			cp = sp;
988 			continue;
989 		}
990 		/*
991 		 * cp nwo points to the token, and
992 		 * vp now points to the token's value
993 		 * both with all surronding whitepace removed.
994 		 */
995 		if (strcasecmp((char *)cp, "v") == 0)
996 		{
997 			/*
998 			 * Yes, this is required to be first, but why
999 			 * reject it if it is not first?
1000 			 */
1001 			if (strcasecmp((char *)vp, "DMARC1") != 0)
1002 			{
1003 				return DMARC_PARSE_ERROR_BAD_VERSION;
1004 			}
1005 		}
1006 		else if (strcasecmp((char *)cp, "p") == 0)
1007 		{
1008 			/*
1009 			 * Be generous. Accept, for example, "p=r, p=rej, or any
1010 			 * left match of "reject".
1011 			 */
1012 			if (strncasecmp((char *)vp, "reject", strlen((char *)vp)) == 0)
1013 				pctx->p = DMARC_RECORD_P_REJECT;
1014 			else if (strncasecmp((char *)vp, "none", strlen((char *)vp)) == 0)
1015 				pctx->p = DMARC_RECORD_P_NONE;
1016 			else if (strncasecmp((char *)vp, "quarantine", strlen((char *)vp)) == 0)
1017 				pctx->p = DMARC_RECORD_P_QUARANTINE;
1018 			else
1019 			{
1020 				/* A totaly unknown value */
1021 				return DMARC_PARSE_ERROR_BAD_VALUE;
1022 			}
1023 		}
1024 		else if (strcasecmp((char *)cp, "sp") == 0)
1025 		{
1026 			/*
1027 			 * Be generous. Accept, for example, "sp=r, p=rej, or any
1028 			 * left match of "reject".
1029 			 */
1030 			if (strncasecmp((char *)vp, "reject", strlen((char *)vp)) == 0)
1031 				pctx->sp = DMARC_RECORD_P_REJECT;
1032 			else if (strncasecmp((char *)vp, "none", strlen((char *)vp)) == 0)
1033 				pctx->sp = DMARC_RECORD_P_NONE;
1034 			else if (strncasecmp((char *)vp, "quarantine", strlen((char *)vp)) == 0)
1035 				pctx->sp = DMARC_RECORD_P_QUARANTINE;
1036 			else
1037 			{
1038 				/* A totaly unknown value */
1039 				return DMARC_PARSE_ERROR_BAD_VALUE;
1040 			}
1041 		}
1042 		else if (strcasecmp((char *)cp, "adkim") == 0)
1043 		{
1044 			/*
1045 			 * Be generous. Accept, for example, "adkim=s, adkim=strict or any
1046 			 * left match of "strict".
1047 			 */
1048 			if (strncasecmp((char *)vp, "strict", strlen((char *)vp)) == 0)
1049 				pctx->adkim = DMARC_RECORD_A_STRICT;
1050 			else if (strncasecmp((char *)vp, "relaxed", strlen((char *)vp)) == 0)
1051 				pctx->adkim = DMARC_RECORD_A_RELAXED;
1052 			else
1053 			{
1054 				/* A totaly unknown value */
1055 				return DMARC_PARSE_ERROR_BAD_VALUE;
1056 			}
1057 		}
1058 		else if (strcasecmp((char *)cp, "aspf") == 0)
1059 		{
1060 			/*
1061 			 * Be generous. Accept, for example, "aspf=s, aspf=strict or any
1062 			 * left match of "strict".
1063 			 */
1064 			if (strncasecmp((char *)vp, "strict", strlen((char *)vp)) == 0)
1065 				pctx->aspf = DMARC_RECORD_A_STRICT;
1066 			else if (strncasecmp((char *)vp, "relaxed", strlen((char *)vp)) == 0)
1067 				pctx->aspf = DMARC_RECORD_A_RELAXED;
1068 			else
1069 			{
1070 				/* A totaly unknown value */
1071 				return DMARC_PARSE_ERROR_BAD_VALUE;
1072 			}
1073 		}
1074 		else if (strcasecmp((char *)cp, "pct") == 0)
1075 		{
1076 			errno = 0;
1077 			pctx->pct = strtoul(vp, NULL, 10);
1078 			if (pctx->pct < 0 || pctx->pct > 100)
1079 			{
1080 				return DMARC_PARSE_ERROR_BAD_VALUE;
1081 			}
1082 			if (errno == EINVAL || errno == ERANGE)
1083 			{
1084 				return DMARC_PARSE_ERROR_BAD_VALUE;
1085 			}
1086 		}
1087 		else if (strcasecmp((char *)cp, "ri") == 0)
1088 		{
1089 			char *xp;
1090 
1091 			for (xp = vp; *xp != '\0'; ++xp)
1092 			{
1093 				if (! isdigit((int)*xp))
1094 					return DMARC_PARSE_ERROR_BAD_VALUE;
1095 			}
1096 			errno = 0;
1097 			pctx->ri = strtoul(vp, NULL, 10);
1098 			if (errno == EINVAL || errno == ERANGE)
1099 			{
1100 				return DMARC_PARSE_ERROR_BAD_VALUE;
1101 			}
1102 		}
1103 		else if (strcasecmp((char *)cp, "rf") == 0)
1104 		{
1105 			char *xp, *yp;
1106 
1107 			/*
1108 			 * The list may be a comma delimilted list of choices.
1109 			 */
1110 			for (xp = vp; *xp != '\0'; )
1111 			{
1112 				u_char xbuf[32];
1113 
1114 				yp = strchr(xp, ',');
1115 				if (yp != NULL)
1116 					*yp = '\0';
1117 
1118 				xp = opendmarc_util_cleanup(xp, xbuf, sizeof xbuf);
1119 				if (xp != NULL && strlen((char *)xp) > 0)
1120 				{
1121 					/*
1122 					 * Be generous. Accept, for example, "rf=a, aspf=afrf or any
1123 					 * left match of "afrf".
1124 					 */
1125 					if (strncasecmp((char *)xp, "afrf", strlen((char *)xp)) == 0)
1126 						pctx->rf |= DMARC_RECORD_RF_AFRF;
1127 					else if (strncasecmp((char *)xp, "iodef", strlen((char *)xp)) == 0)
1128 						pctx->aspf |= DMARC_RECORD_RF_IODEF;
1129 					else
1130 					{
1131 						/* A totaly unknown value */
1132 						return DMARC_PARSE_ERROR_BAD_VALUE;
1133 					}
1134 				}
1135 				else
1136 				{
1137 					return DMARC_PARSE_ERROR_BAD_VALUE;
1138 				}
1139 
1140 				if (yp != NULL)
1141 					xp = yp+1;
1142 				else
1143 					break;
1144 			}
1145 		}
1146 		else if (strcasecmp((char *)cp, "rua") == 0)
1147 		{
1148 			char *xp, *yp;
1149 
1150 			/*
1151 			 * A possibly comma delimited list of URI of where to send reports.
1152 			 */
1153 
1154 			if (pctx->rua_list != NULL)
1155 				return DMARC_PARSE_ERROR_BAD_VALUE;
1156 
1157 			for (xp = vp; *xp != '\0'; )
1158 			{
1159 				u_char	xbuf[256];
1160 
1161 				yp = strchr(xp, ',');
1162 				if (yp != NULL)
1163 					*yp = '\0';
1164 
1165 				xp = opendmarc_util_cleanup(xp, xbuf, sizeof xbuf);
1166 				if (xp != NULL && strlen((char *)xp) > 0)
1167 				{
1168 					pctx->rua_list = opendmarc_util_pushargv(xp, pctx->rua_list,
1169 										&(pctx->rua_cnt));
1170 				}
1171 				else
1172 				{
1173 					return DMARC_PARSE_ERROR_BAD_VALUE;
1174 				}
1175 
1176 				if (yp != NULL)
1177 					xp = yp+1;
1178 				else
1179 					break;
1180 			}
1181 		}
1182 		else if (strcasecmp((char *)cp, "ruf") == 0)
1183 		{
1184 			char *xp, *yp;
1185 
1186 			/*
1187 			 * A possibly comma delimited list of URI of where to send
1188 			 * MARF reports.
1189 			 */
1190 
1191 			if (pctx->ruf_list != NULL)
1192 				return DMARC_PARSE_ERROR_BAD_VALUE;
1193 
1194 			for (xp = vp; *xp != '\0'; )
1195 			{
1196 				u_char	xbuf[256];
1197 
1198 				yp = strchr(xp, ',');
1199 				if (yp != NULL)
1200 					*yp = '\0';
1201 
1202 				xp = opendmarc_util_cleanup(xp, xbuf, sizeof xbuf);
1203 				if (xp != NULL && strlen((char *)xp) > 0)
1204 				{
1205 					pctx->ruf_list = opendmarc_util_pushargv(xp, pctx->ruf_list,
1206 										&(pctx->ruf_cnt));
1207 				}
1208 				else
1209 				{
1210 					return DMARC_PARSE_ERROR_BAD_VALUE;
1211 				}
1212 
1213 				if (yp != NULL)
1214 					xp = yp+1;
1215 				else
1216 					break;
1217 			}
1218 		}
1219 		else if (strcasecmp((char *)cp, "fo") == 0)
1220 		{
1221 			char *xp, *yp;
1222 
1223 			/*
1224 			 * A possibly colon delimited list of on character settings.
1225 			 */
1226 			for (xp = vp; *xp != '\0'; )
1227 			{
1228 				u_char xbuf[256];
1229 
1230 				yp = strchr(xp, ':');
1231 				if (yp != NULL)
1232 					*yp = '\0';
1233 
1234 				xp = opendmarc_util_cleanup(xp, xbuf, sizeof xbuf);
1235 				if (xp != NULL && strlen((char *)xp) > 0)
1236 				{
1237 					switch ((int)*xp)
1238 					{
1239 						case '0':
1240 							pctx->fo |= DMARC_RECORD_FO_0;
1241 							break;
1242 						case '1':
1243 							pctx->fo |= DMARC_RECORD_FO_1;
1244 							break;
1245 						case 'd':
1246 						case 'D':
1247 							pctx->fo |= DMARC_RECORD_FO_D;
1248 							break;
1249 						case 's':
1250 						case 'S':
1251 							pctx->fo |= DMARC_RECORD_FO_S;
1252 							break;
1253 						default:
1254 							return DMARC_PARSE_ERROR_BAD_VALUE;
1255 					}
1256 				}
1257 				else
1258 				{
1259 					return DMARC_PARSE_ERROR_BAD_VALUE;
1260 				}
1261 
1262 				if (yp != NULL)
1263 					xp = yp+1;
1264 				else
1265 					break;
1266 			}
1267 		}
1268 
1269 		cp = sp;
1270 	}
1271 
1272 	if (pctx->p == DMARC_RECORD_P_UNSPECIFIED)
1273 	{
1274 		return DMARC_PARSE_ERROR_NO_REQUIRED_P;
1275 	}
1276 	/*
1277 	 * Set defaults for unspecifed tokens.
1278 	 */
1279 	if (pctx->adkim == DMARC_RECORD_A_UNSPECIFIED)
1280 		pctx->adkim = DMARC_RECORD_A_RELAXED;
1281 	if (pctx->aspf == DMARC_RECORD_A_UNSPECIFIED)
1282 		pctx->aspf = DMARC_RECORD_A_RELAXED;
1283 	if (pctx->pct < 0)
1284 		pctx->pct = 100;
1285 	if (pctx->rf == DMARC_RECORD_RF_UNSPECIFIED)
1286 		pctx->rf = DMARC_RECORD_RF_AFRF;
1287 	if (pctx->ri == -1)
1288 		pctx->ri = 86400;
1289 	if (pctx->fo == DMARC_RECORD_FO_UNSPECIFIED)
1290 		pctx->fo = DMARC_RECORD_FO_0;
1291 
1292 	if (pctx->from_domain == NULL)
1293 		pctx->from_domain = strdup(domain);
1294 	return DMARC_PARSE_OKAY;
1295 }
1296 
1297 /**************************************************************************
1298 ** OPENDMARC_POLICY_STORE_DMARC -- The application looked up the dmarc record
1299 **					and hands it to us here.
1300 ***************************************************************************/
1301 OPENDMARC_STATUS_T
opendmarc_policy_store_dmarc(DMARC_POLICY_T * pctx,u_char * dmarc_record,u_char * domain,u_char * organizationaldomain)1302 opendmarc_policy_store_dmarc(DMARC_POLICY_T *pctx, u_char *dmarc_record, u_char *domain, u_char *organizationaldomain)
1303 {
1304 	OPENDMARC_STATUS_T status;
1305 
1306 	if (pctx == NULL)
1307 		return DMARC_PARSE_ERROR_NULL_CTX;
1308 	if (dmarc_record == NULL)
1309 		return DMARC_PARSE_ERROR_EMPTY;
1310 	if (domain == NULL)
1311 		return DMARC_PARSE_ERROR_NO_DOMAIN;
1312 
1313 	status = opendmarc_policy_parse_dmarc(pctx, domain , dmarc_record);
1314 	if (status != DMARC_PARSE_OKAY)
1315 		return status;
1316 
1317 	if (pctx->from_domain != NULL)
1318 		(void) free(pctx->from_domain);
1319 	pctx->from_domain = strdup(domain);
1320 	if (organizationaldomain != NULL)
1321 	{
1322 		if (pctx->organizational_domain != NULL)
1323 			(void) free(pctx->organizational_domain);
1324 		pctx->organizational_domain = (u_char *)strdup(organizationaldomain);
1325 	}
1326 	return DMARC_PARSE_OKAY;
1327 }
1328 
1329 
1330 /**************************************************************************
1331 ** DMARC LOOKUP HOOKS
1332 ***************************************************************************/
1333 
1334 OPENDMARC_STATUS_T
opendmarc_policy_fetch_pct(DMARC_POLICY_T * pctx,int * pctp)1335 opendmarc_policy_fetch_pct(DMARC_POLICY_T *pctx, int *pctp)
1336 {
1337 	if (pctx == NULL)
1338 	{
1339 		return DMARC_PARSE_ERROR_NULL_CTX;
1340 	}
1341 	if (pctp == NULL)
1342 	{
1343 		return DMARC_PARSE_ERROR_EMPTY;
1344 	}
1345 	*pctp = pctx->pct;
1346 	return DMARC_PARSE_OKAY;
1347 }
1348 
1349 OPENDMARC_STATUS_T
opendmarc_policy_fetch_adkim(DMARC_POLICY_T * pctx,int * adkim)1350 opendmarc_policy_fetch_adkim(DMARC_POLICY_T *pctx, int *adkim)
1351 {
1352 	if (pctx == NULL)
1353 	{
1354 		return DMARC_PARSE_ERROR_NULL_CTX;
1355 	}
1356 	if (adkim == NULL)
1357 	{
1358 		return DMARC_PARSE_ERROR_EMPTY;
1359 	}
1360 	*adkim = pctx->adkim;
1361 	return DMARC_PARSE_OKAY;
1362 }
1363 
1364 OPENDMARC_STATUS_T
opendmarc_policy_fetch_aspf(DMARC_POLICY_T * pctx,int * aspf)1365 opendmarc_policy_fetch_aspf(DMARC_POLICY_T *pctx, int *aspf)
1366 {
1367 	if (pctx == NULL)
1368 	{
1369 		return DMARC_PARSE_ERROR_NULL_CTX;
1370 	}
1371 	if (aspf == NULL)
1372 	{
1373 		return DMARC_PARSE_ERROR_EMPTY;
1374 	}
1375 	*aspf = pctx->aspf;
1376 	return DMARC_PARSE_OKAY;
1377 }
1378 
1379 OPENDMARC_STATUS_T
opendmarc_policy_fetch_p(DMARC_POLICY_T * pctx,int * p)1380 opendmarc_policy_fetch_p(DMARC_POLICY_T *pctx, int *p)
1381 {
1382 	if (pctx == NULL)
1383 	{
1384 		return DMARC_PARSE_ERROR_NULL_CTX;
1385 	}
1386 	if (p == NULL)
1387 	{
1388 		return DMARC_PARSE_ERROR_EMPTY;
1389 	}
1390 	*p = pctx->p;
1391 	return DMARC_PARSE_OKAY;
1392 }
1393 
1394 OPENDMARC_STATUS_T
opendmarc_policy_fetch_sp(DMARC_POLICY_T * pctx,int * sp)1395 opendmarc_policy_fetch_sp(DMARC_POLICY_T *pctx, int *sp)
1396 {
1397 	if (pctx == NULL)
1398 	{
1399 		return DMARC_PARSE_ERROR_NULL_CTX;
1400 	}
1401 	if (sp == NULL)
1402 	{
1403 		return DMARC_PARSE_ERROR_EMPTY;
1404 	}
1405 	*sp = pctx->sp;
1406 	return DMARC_PARSE_OKAY;
1407 }
1408 
1409 u_char **
opendmarc_policy_fetch_rua(DMARC_POLICY_T * pctx,u_char * list_buf,size_t size_of_buf,int constant)1410 opendmarc_policy_fetch_rua(DMARC_POLICY_T *pctx, u_char *list_buf, size_t size_of_buf, int constant)
1411 {
1412 	u_char *sp, *ep, *rp;
1413 	int	i;
1414 	int	ret;
1415 
1416 	if (pctx == NULL)
1417 	{
1418 		return NULL;
1419 	}
1420 	if (list_buf != NULL && size_of_buf > 0)
1421 	{
1422 		(void) memset(list_buf, '\0', size_of_buf);
1423 		sp = list_buf;
1424 		ep = list_buf + size_of_buf;
1425 		for (i = 0; i < pctx->rua_cnt; i++)
1426 		{
1427 			ret = opendmarc_policy_query_dmarc_xdomain(pctx, pctx->rua_list[i]);
1428 			if (ret != DMARC_PARSE_OKAY)
1429 				continue;
1430 			for (rp = (pctx->rua_list)[i]; *rp != '\0'; ++rp)
1431 			{
1432 				*sp++ = *rp;
1433 				if (sp >= (ep - 2))
1434 					break;
1435 			}
1436 			if (sp >= (ep - 2))
1437 				break;
1438 			if (i != (pctx->rua_cnt -1))
1439 				*sp++ = ',';
1440 			if (sp >= (ep - 2))
1441 				break;
1442 		}
1443 	}
1444 	if (constant != 0)
1445 		return pctx->rua_list;
1446 	return opendmarc_util_dupe_argv(pctx->rua_list);
1447 }
1448 
1449 OPENDMARC_STATUS_T
opendmarc_policy_fetch_alignment(DMARC_POLICY_T * pctx,int * dkim_alignment,int * spf_alignment)1450 opendmarc_policy_fetch_alignment(DMARC_POLICY_T *pctx, int *dkim_alignment, int *spf_alignment)
1451 {
1452 	if (pctx == NULL)
1453 	{
1454 		return DMARC_PARSE_ERROR_NULL_CTX;
1455 	}
1456 	if (dkim_alignment != NULL)
1457 	{
1458 		*dkim_alignment = pctx->dkim_alignment;
1459 	}
1460 	if (spf_alignment != NULL)
1461 	{
1462 		*spf_alignment = pctx->spf_alignment;
1463 	}
1464 	return DMARC_PARSE_OKAY;
1465 }
1466 
1467 u_char **
opendmarc_policy_fetch_ruf(DMARC_POLICY_T * pctx,u_char * list_buf,size_t size_of_buf,int constant)1468 opendmarc_policy_fetch_ruf(DMARC_POLICY_T *pctx, u_char *list_buf, size_t size_of_buf, int constant)
1469 {
1470 	u_char *sp, *ep, *rp;
1471 	int	i;
1472 	int	ret;
1473 
1474 	if (pctx == NULL)
1475 	{
1476 		return NULL;
1477 	}
1478 	if (list_buf != NULL || size_of_buf > 0)
1479 	{
1480 		(void) memset(list_buf, '\0', size_of_buf);
1481 		sp = list_buf;
1482 		ep = list_buf + size_of_buf;
1483 		for (i = 0; i < pctx->ruf_cnt; i++)
1484 		{
1485 			ret = opendmarc_policy_query_dmarc_xdomain(pctx, pctx->ruf_list[i]);
1486 			if (ret != DMARC_PARSE_OKAY)
1487 				continue;
1488 			for (rp = (pctx->ruf_list)[i]; *rp != '\0'; ++rp)
1489 			{
1490 				*sp++ = *rp;
1491 				if (sp >= (ep - 2))
1492 					break;
1493 			}
1494 			if (sp >= (ep - 2))
1495 				break;
1496 			if (i != (pctx->ruf_cnt -1))
1497 				*sp++ = ',';
1498 			if (sp >= (ep - 2))
1499 				break;
1500 		}
1501 	}
1502 	if (constant != 0)
1503 		return pctx->ruf_list;
1504 	return opendmarc_util_dupe_argv(pctx->ruf_list);
1505 }
1506 
1507 OPENDMARC_STATUS_T
opendmarc_policy_fetch_fo(DMARC_POLICY_T * pctx,int * fo)1508 opendmarc_policy_fetch_fo(DMARC_POLICY_T *pctx, int *fo)
1509 {
1510 	if (pctx == NULL)
1511 		return DMARC_PARSE_ERROR_NULL_CTX;
1512 	if (fo == NULL)
1513 		return DMARC_PARSE_ERROR_EMPTY;
1514 	if (pctx->ruf_list == NULL)
1515 		*fo = DMARC_RECORD_FO_UNSPECIFIED;
1516 	else
1517 		*fo = pctx->fo;
1518 	return DMARC_PARSE_OKAY;
1519 }
1520 
1521 OPENDMARC_STATUS_T
opendmarc_policy_fetch_rf(DMARC_POLICY_T * pctx,int * rf)1522 opendmarc_policy_fetch_rf(DMARC_POLICY_T *pctx, int *rf)
1523 {
1524 	if (pctx == NULL)
1525 		return DMARC_PARSE_ERROR_NULL_CTX;
1526 	if (rf == NULL)
1527 		return DMARC_PARSE_ERROR_EMPTY;
1528 	if (pctx->ruf_list == NULL)
1529 		*rf = DMARC_RECORD_RF_UNSPECIFIED;
1530 	else
1531 		*rf = pctx->rf;
1532 	return DMARC_PARSE_OKAY;
1533 }
1534 
1535 /**************************************************************************************************
1536 ** OPENDMARC_POLICY_FETCH_UTILIZED_DOMAIN -- Return domain used to get the dmarc record
1537 **					     Either the From: domain or the organizational domain
1538 **	Arguments
1539 **		pctx	-- Address of a policy context
1540 **		buf	-- Where to scribble result
1541 **		buflen	-- Size of buffer
1542 **	Returns
1543 **		DMARC_PARSE_OKAY		-- On success
1544 **		DMARC_PARSE_ERROR_NULL_CTX	-- If context NULL
1545 **		DMARC_PARSE_ERROR_EMPTY 	-- If buf null or buflen 0 sized
1546 **		DMARC_PARSE_ERROR_NO_DOMAIN 	-- If neigher address is available
1547 **/
1548 OPENDMARC_STATUS_T
opendmarc_policy_fetch_utilized_domain(DMARC_POLICY_T * pctx,u_char * buf,size_t buflen)1549 opendmarc_policy_fetch_utilized_domain(DMARC_POLICY_T *pctx, u_char *buf, size_t buflen)
1550 {
1551 	u_char *which = NULL;
1552 
1553 	if (pctx == NULL)
1554 		return DMARC_PARSE_ERROR_NULL_CTX;
1555 	if (buf == NULL || buflen == 0)
1556 		return DMARC_PARSE_ERROR_EMPTY;
1557 
1558 	if (pctx->organizational_domain != NULL)
1559 		which = pctx->organizational_domain;
1560 	else if (pctx->from_domain != NULL)
1561 		which = pctx->from_domain;
1562 	if (which == NULL)
1563 		return DMARC_PARSE_ERROR_NO_DOMAIN;
1564 # if HAVE_STRLCPY
1565 	(void) strlcpy((char *)buf, (char *)which, buflen);
1566 # else
1567 	(void) strncpy((char *)buf, (char *)which, buflen);
1568 # endif
1569 	return DMARC_PARSE_OKAY;
1570 }
1571 
1572 /**************************************************************************************************
1573 ** OPENDMARC_POLICY_FETCH_FROM_DOMAIN -- Return domain parsed from stored From: header
1574 **	Arguments
1575 **		pctx	-- Address of a policy context
1576 **		buf	-- Where to scribble result
1577 **		buflen	-- Size of buffer
1578 **	Returns
1579 **		DMARC_PARSE_OKAY		-- On success
1580 **		DMARC_PARSE_ERROR_NULL_CTX	-- If context NULL
1581 **		DMARC_PARSE_ERROR_EMPTY 	-- If buf null or buflen 0 sized
1582 **		DMARC_PARSE_ERROR_NO_DOMAIN 	-- If neigher address is available
1583 **/
1584 OPENDMARC_STATUS_T
opendmarc_policy_fetch_from_domain(DMARC_POLICY_T * pctx,u_char * buf,size_t buflen)1585 opendmarc_policy_fetch_from_domain(DMARC_POLICY_T *pctx, u_char *buf, size_t buflen)
1586 {
1587 	u_char *which = NULL;
1588 
1589 	if (pctx == NULL)
1590 		return DMARC_PARSE_ERROR_NULL_CTX;
1591 	if (buf == NULL || buflen == 0)
1592 		return DMARC_PARSE_ERROR_EMPTY;
1593 
1594 	if (pctx->from_domain != NULL)
1595 		which = pctx->from_domain;
1596 	if (which == NULL)
1597 		return DMARC_PARSE_ERROR_NO_DOMAIN;
1598 # if HAVE_STRLCPY
1599 	(void) strlcpy((char *)buf, (char *)which, buflen);
1600 # else
1601 	(void) strncpy((char *)buf, (char *)which, buflen);
1602 # endif
1603 	return DMARC_PARSE_OKAY;
1604 }
1605 /**************************************************************************
1606 ** OPENDMARC_GET_POLICY_TOKEN_USED -- Which policy was actually used
1607 **
1608 **	Parameters:
1609 **		pctx	-- A Policy context
1610 **	Returns:
1611 **		DMARC_PARSE_ERROR_NULL_CTX	-- pctx == NULL
1612 **		DMARC_USED_POLICY_IS_P		-- Domain policy is used
1613 **		DMARC_USED_POLICY_IS_SP		-- Sub-domain policy is used
1614 ***************************************************************************/
1615 OPENDMARC_STATUS_T
opendmarc_get_policy_token_used(DMARC_POLICY_T * pctx)1616 opendmarc_get_policy_token_used(DMARC_POLICY_T *pctx)
1617 {
1618 
1619 	if (pctx == NULL)
1620 		return DMARC_PARSE_ERROR_NULL_CTX;
1621 	if (pctx->organizational_domain != NULL &&
1622 	    pctx->sp != DMARC_RECORD_P_UNSPECIFIED)
1623 		return DMARC_USED_POLICY_IS_SP;
1624 	else
1625 		return DMARC_USED_POLICY_IS_P;
1626 }
1627 
1628 /******************************************************************************
1629 ** OPENDMARC_POLICY_LIBRARY_DNS_HOOK -- Internal hook for dmarc_dns_get_record
1630 *******************************************************************************/
1631 void
opendmarc_policy_library_dns_hook(int * nscountp,struct sockaddr_in * nsaddr_list)1632 opendmarc_policy_library_dns_hook(int *nscountp,
1633                                   struct sockaddr_in *nsaddr_list)
1634 {
1635 	int i;
1636 
1637 	if (nscountp == NULL || nsaddr_list == NULL)
1638 		return;
1639 	if (Opendmarc_Libp == NULL)
1640 		return;
1641 	if (Opendmarc_Libp->nscount == 0 || Opendmarc_Libp->nscount >= MAXNS)
1642 		return;
1643 	for (i = 0; i < Opendmarc_Libp->nscount; i++)
1644 	{
1645 		nsaddr_list[i] = Opendmarc_Libp->nsaddr_list[i];
1646 	}
1647 	*nscountp = i;
1648 	return;
1649 }
1650 
1651 /**************************************************************************
1652 ** OPENDMARC_POLICY_STATUS_TO_STR -- Convert the integer return
1653 **				     of type OPENDMARC_STATUS_T into
1654 **				     a human readable string.
1655 **	Parameters:
1656 **		status	-- The status for which to return a string
1657 **	Returns:
1658 **		NULL		-- On error
1659 **		const char *	-- On success
1660 ***************************************************************************/
1661 const char *
opendmarc_policy_status_to_str(OPENDMARC_STATUS_T status)1662 opendmarc_policy_status_to_str(OPENDMARC_STATUS_T status)
1663 {
1664 	char *msg = "Undefine Value";
1665 
1666 	switch (status)
1667 	{
1668 	    case DMARC_PARSE_OKAY:
1669 	    	msg = "Success. No Errors";
1670 		break;
1671 	    case DMARC_PARSE_ERROR_EMPTY:
1672 		msg = "Function called with nothing to parse";
1673 		break;
1674 	    case DMARC_PARSE_ERROR_NULL_CTX:
1675 		msg  ="Function called with NULL Context";
1676 		break;
1677 	    case DMARC_PARSE_ERROR_BAD_VERSION:
1678 		msg = "Found DMARC record contained a bad v= value";
1679 		break;
1680 	    case DMARC_PARSE_ERROR_BAD_VALUE:
1681 		msg = "Found DMARC record contained a bad token value";
1682 		break;
1683 	    case DMARC_PARSE_ERROR_NO_REQUIRED_P:
1684 		msg = "Found DMARC record lacked a required p= entry";
1685 		break;
1686 	    case DMARC_PARSE_ERROR_NO_DOMAIN:
1687 		msg = "Function found the domain empty, e.g. \"<>\"";
1688 		break;
1689 	    case DMARC_PARSE_ERROR_NO_ALLOC:
1690 		msg = "Memory allocation error";
1691 		break;
1692 	    case DMARC_PARSE_ERROR_BAD_SPF_MACRO:
1693 		msg = "Attempt to store an illegal value";
1694 		break;
1695 	    case DMARC_DNS_ERROR_NO_RECORD:
1696 		msg = "Looked up domain lacked a DMARC record";
1697 		break;
1698 	    case DMARC_DNS_ERROR_NXDOMAIN:
1699 		msg = "Looked up domain did not exist";
1700 		break;
1701 	    case DMARC_DNS_ERROR_TMPERR:
1702 		msg = "DNS lookup of domain tempfailed";
1703 		break;
1704 	    case DMARC_TLD_ERROR_UNKNOWN:
1705 		msg = "Attempt to load an unknown TLD file type";
1706 		break;
1707 	    case DMARC_FROM_DOMAIN_ABSENT:
1708 		msg = "No From: domain was supplied";
1709 		break;
1710 	    case DMARC_POLICY_ABSENT:
1711 		msg = "Policy up to you. No DMARC record found";
1712 		break;
1713 	    case DMARC_POLICY_PASS:
1714 		msg = "Policy OK so accept message";
1715 		break;
1716 	    case DMARC_POLICY_REJECT:
1717 		msg = "Policy says to reject message";
1718 		break;
1719 	    case DMARC_POLICY_QUARANTINE:
1720 		msg = "Policy says to quarantine message";
1721 		break;
1722 	    case DMARC_POLICY_NONE:
1723 		msg = "Policy says to monitor and report";
1724 		break;
1725 	}
1726 	return msg;
1727 }
1728 
1729 /*******************************************************************************
1730 ** OPENDMARC_POLICY_TO_BUF -- Dump the DMARC_POLICY_T to a user supplied buffer
1731 **	Arguments:
1732 **		pctx		A pointer to a filled in DMARC_POLICY_T sttucture
1733 **		buf		A char * buffer
1734 **		buflen		The size of the char * buffer
1735 **	Returns:
1736 **		0 		On success
1737 **		>0 		On failure, and fills errno with the error
1738 **	Side Effects:
1739 **		Blindly overwrites buffer.
1740 *******************************************************************************/
1741 int
opendmarc_policy_to_buf(DMARC_POLICY_T * pctx,char * buf,size_t buflen)1742 opendmarc_policy_to_buf(DMARC_POLICY_T *pctx, char *buf, size_t buflen)
1743 {
1744 	char	nbuf[32];
1745 	int	i;
1746 
1747 	if (pctx == NULL || buf == NULL || buflen == 0)
1748 		return errno = EINVAL;
1749 
1750 	(void) memset(buf, '\0', buflen);
1751 
1752 	if (strlcat(buf, "IP_ADDR=", buflen) >= buflen) return E2BIG;
1753 	if (pctx->ip_addr != NULL)
1754 		if (strlcat(buf, pctx->ip_addr, buflen) >= buflen) return E2BIG;
1755 	if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
1756 
1757 	if (strlcat(buf, "IP_TYPE=", buflen) >= buflen) return E2BIG;
1758 	if (pctx->ip_addr != NULL)
1759 	{
1760 		if (pctx->ip_type == DMARC_POLICY_IP_TYPE_IPV4)
1761 		{
1762 			if (strlcat(buf, "IPv4", buflen) >= buflen) return E2BIG;
1763 		}
1764 		else if (pctx->ip_type == DMARC_POLICY_IP_TYPE_IPV6)
1765 		{
1766 			if (strlcat(buf, "IPv6", buflen) >= buflen) return E2BIG;
1767 		}
1768 	}
1769 	if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
1770 
1771 	if (strlcat(buf, "SPF_DOMAIN=", buflen) >= buflen) return E2BIG;
1772 	if (pctx->spf_domain != NULL)
1773 		if (strlcat(buf, pctx->spf_domain, buflen) >= buflen) return E2BIG;
1774 	if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
1775 
1776 	if (strlcat(buf, "SPF_ORIGIN=", buflen) >= buflen) return E2BIG;
1777 	if (pctx->spf_origin != 0)
1778 	{
1779 		if (pctx->spf_origin == DMARC_POLICY_SPF_ORIGIN_MAILFROM)
1780 		{
1781 			if (strlcat(buf, "MAILFROM", buflen) >= buflen) return E2BIG;
1782 		}
1783 		else if (pctx->spf_origin == DMARC_POLICY_SPF_ORIGIN_HELO)
1784 		{
1785 			if (strlcat(buf, "HELO", buflen) >= buflen) return E2BIG;
1786 		}
1787 	}
1788 	if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
1789 
1790 	if (strlcat(buf, "SPF_OUTCOME=", buflen) >= buflen) return E2BIG;
1791 	switch (pctx->spf_outcome)
1792 	{
1793 		default:
1794 		case DMARC_POLICY_DKIM_OUTCOME_NONE:
1795 			if (strlcat(buf, "NONE", buflen) >= buflen) return E2BIG;
1796 			break;
1797 		case DMARC_POLICY_DKIM_OUTCOME_PASS:
1798 			if (strlcat(buf, "PASS", buflen) >= buflen) return E2BIG;
1799 			break;
1800 		case DMARC_POLICY_DKIM_OUTCOME_FAIL:
1801 			if (strlcat(buf, "FAIL", buflen) >= buflen) return E2BIG;
1802 			break;
1803 		case DMARC_POLICY_DKIM_OUTCOME_TMPFAIL:
1804 			if (strlcat(buf, "TMPFAIL", buflen) >= buflen) return E2BIG;
1805 			break;
1806 	}
1807 	if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
1808 
1809 	if (strlcat(buf, "SPF_HUMAN_OUTCOME=", buflen) >= buflen) return E2BIG;
1810 	if (pctx->spf_human_outcome != NULL)
1811 		if (strlcat(buf, pctx->spf_human_outcome, buflen) >= buflen) return E2BIG;
1812 	if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
1813 
1814 	if (strlcat(buf, "DKIM_FINAL=", buflen) >= buflen) return E2BIG;
1815 	switch (pctx->dkim_final)
1816 	{
1817 		case TRUE:
1818 			if (strlcat(buf, "TRUE", buflen) >= buflen) return E2BIG;
1819 			break;
1820 		default:
1821 		case FALSE:
1822 			if (strlcat(buf, "FALSE", buflen) >= buflen) return E2BIG;
1823 			break;
1824 	}
1825 	if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
1826 
1827 	if (strlcat(buf, "DKIM_DOMAIN=", buflen) >= buflen) return E2BIG;
1828 	if (pctx->dkim_domain != NULL)
1829 		if (strlcat(buf, pctx->dkim_domain, buflen) >= buflen) return E2BIG;
1830 	if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
1831 
1832 	if (strlcat(buf, "DKIM_SELECTOR=", buflen) >= buflen) return E2BIG;
1833 	if (pctx->dkim_selector != NULL)
1834 		if (strlcat(buf, pctx->dkim_selector, buflen) >= buflen) return E2BIG;
1835 	if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
1836 
1837 	if (strlcat(buf, "DKIM_OUTOME=", buflen) >= buflen) return E2BIG;
1838 	switch (pctx->dkim_outcome)
1839 	{
1840 		default:
1841 		case DMARC_POLICY_DKIM_OUTCOME_NONE:
1842 			if (strlcat(buf, "NONE", buflen) >= buflen) return E2BIG;
1843 			break;
1844 		case DMARC_POLICY_DKIM_OUTCOME_PASS:
1845 			if (strlcat(buf, "PASS", buflen) >= buflen) return E2BIG;
1846 			break;
1847 		case DMARC_POLICY_DKIM_OUTCOME_FAIL:
1848 			if (strlcat(buf, "FAIL", buflen) >= buflen) return E2BIG;
1849 			break;
1850 		case DMARC_POLICY_DKIM_OUTCOME_TMPFAIL:
1851 			if (strlcat(buf, "TMPFAIL", buflen) >= buflen) return E2BIG;
1852 			break;
1853 	}
1854 	if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
1855 
1856 	if (strlcat(buf, "DKIM_HUMAN_OUTCOME=", buflen) >= buflen) return E2BIG;
1857 	if (pctx->dkim_human_outcome != NULL)
1858 		if (strlcat(buf, pctx->dkim_human_outcome, buflen) >= buflen) return E2BIG;
1859 	if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
1860 
1861 	if (strlcat(buf, "DKIM_ALIGNMENT=", buflen) >= buflen) return E2BIG;
1862 	switch (pctx->dkim_alignment)
1863 	{
1864 		case DMARC_POLICY_DKIM_ALIGNMENT_PASS:
1865 			if (strlcat(buf, "PASS", buflen) >= buflen) return E2BIG;
1866 			break;
1867 		default:
1868 		case DMARC_POLICY_DKIM_ALIGNMENT_FAIL:
1869 			if (strlcat(buf, "FAIL", buflen) >= buflen) return E2BIG;
1870 			break;
1871 	}
1872 	if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
1873 
1874 	if (strlcat(buf, "SPF_ALIGNMENT=", buflen) >= buflen) return E2BIG;
1875 	switch (pctx->spf_alignment)
1876 	{
1877 		case DMARC_POLICY_SPF_ALIGNMENT_PASS:
1878 			if (strlcat(buf, "PASS", buflen) >= buflen) return E2BIG;
1879 			break;
1880 		default:
1881 		case DMARC_POLICY_SPF_ALIGNMENT_FAIL:
1882 			if (strlcat(buf, "FAIL", buflen) >= buflen) return E2BIG;
1883 			break;
1884 	}
1885 	if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
1886 
1887 	if (strlcat(buf, "H_ERRNO=", buflen) >= buflen) return E2BIG;
1888 	switch (pctx->h_error)
1889 	{
1890 		case HOST_NOT_FOUND:
1891 			if (strlcat(buf, "HOST_NOT_FOUND", buflen) >= buflen) return E2BIG;
1892 			break;
1893 		case TRY_AGAIN:
1894 			if (strlcat(buf, "TRY_AGAIN", buflen) >= buflen) return E2BIG;
1895 			break;
1896 		case NO_RECOVERY:
1897 			if (strlcat(buf, "NO_RECOVERY", buflen) >= buflen) return E2BIG;
1898 			break;
1899 		case NO_DATA:
1900 			if (strlcat(buf, "NO_DATA", buflen) >= buflen) return E2BIG;
1901 			break;
1902 		case NETDB_INTERNAL:
1903 			if (strlcat(buf, "NETDB_INTERNAL", buflen) >= buflen) return E2BIG;
1904 			break;
1905 	}
1906 	if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
1907 
1908 	if (strlcat(buf, "ADKIM=", buflen) >= buflen) return E2BIG;
1909 	switch (pctx->adkim)
1910 	{
1911 		case DMARC_RECORD_A_UNSPECIFIED:
1912 			if (strlcat(buf, "UNSPECIFIED", buflen) >= buflen) return E2BIG;
1913 			break;
1914 		case DMARC_RECORD_A_STRICT:
1915 			if (strlcat(buf, "STRICT", buflen) >= buflen) return E2BIG;
1916 			break;
1917 		case DMARC_RECORD_A_RELAXED:
1918 			if (strlcat(buf, "RELAXED", buflen) >= buflen) return E2BIG;
1919 			break;
1920 	}
1921 	if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
1922 
1923 	if (strlcat(buf, "ASPF=", buflen) >= buflen) return E2BIG;
1924 	switch (pctx->aspf)
1925 	{
1926 		case DMARC_RECORD_A_UNSPECIFIED:
1927 			if (strlcat(buf, "UNSPECIFIED", buflen) >= buflen) return E2BIG;
1928 			break;
1929 		case DMARC_RECORD_A_STRICT:
1930 			if (strlcat(buf, "STRICT", buflen) >= buflen) return E2BIG;
1931 			break;
1932 		case DMARC_RECORD_A_RELAXED:
1933 			if (strlcat(buf, "RELAXED", buflen) >= buflen) return E2BIG;
1934 			break;
1935 	}
1936 	if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
1937 
1938 	if (strlcat(buf, "P=", buflen) >= buflen) return E2BIG;
1939 	switch (pctx->p)
1940 	{
1941 		case DMARC_RECORD_A_UNSPECIFIED:
1942 			if (strlcat(buf, "UNSPECIFIED", buflen) >= buflen) return E2BIG;
1943 			break;
1944 		case DMARC_RECORD_P_NONE:
1945 			if (strlcat(buf, "NONE", buflen) >= buflen) return E2BIG;
1946 			break;
1947 		case DMARC_RECORD_P_QUARANTINE:
1948 			if (strlcat(buf, "QUARANTINE", buflen) >= buflen) return E2BIG;
1949 			break;
1950 		case DMARC_RECORD_P_REJECT:
1951 			if (strlcat(buf, "REJECT", buflen) >= buflen) return E2BIG;
1952 			break;
1953 	}
1954 	if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
1955 
1956 	if (strlcat(buf, "SP=", buflen) >= buflen) return E2BIG;
1957 	switch (pctx->sp)
1958 	{
1959 		case DMARC_RECORD_A_UNSPECIFIED:
1960 			if (strlcat(buf, "UNSPECIFIED", buflen) >= buflen) return E2BIG;
1961 			break;
1962 		case DMARC_RECORD_P_NONE:
1963 			if (strlcat(buf, "NONE", buflen) >= buflen) return E2BIG;
1964 			break;
1965 		case DMARC_RECORD_P_QUARANTINE:
1966 			if (strlcat(buf, "QUARANTINE", buflen) >= buflen) return E2BIG;
1967 			break;
1968 		case DMARC_RECORD_P_REJECT:
1969 			if (strlcat(buf, "REJECT", buflen) >= buflen) return E2BIG;
1970 			break;
1971 	}
1972 	if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
1973 
1974 	if (strlcat(buf, "PCT=", buflen) >= buflen) return E2BIG;
1975 	(void) snprintf(nbuf, sizeof nbuf, "%d", pctx->pct);
1976 	if (strlcat(buf, nbuf, buflen) >= buflen) return E2BIG;
1977 	if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
1978 
1979 	if (strlcat(buf, "RF=", buflen) >= buflen) return E2BIG;
1980 	if (pctx->rf == 0)
1981 	{
1982 		if (strlcat(buf, "UNSPECIFIED", buflen) >= buflen) return E2BIG;
1983 	}
1984 	if ((pctx->rf&DMARC_RECORD_RF_AFRF) != 0)
1985 	{
1986 		if (strlcat(buf, "AFRF", buflen) >= buflen) return E2BIG;
1987 	}
1988 	if ((pctx->rf&DMARC_RECORD_RF_IODEF) != 0 &&
1989 	    (pctx->rf&DMARC_RECORD_RF_AFRF) != 0)
1990 	{
1991 		if (strlcat(buf, ",", buflen) >= buflen) return E2BIG;
1992 	}
1993 	if ((pctx->rf&DMARC_RECORD_RF_IODEF) != 0)
1994 	{
1995 		if (strlcat(buf, "IODEF", buflen) >= buflen) return E2BIG;
1996 	}
1997 	if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
1998 
1999 	if (strlcat(buf, "RI=", buflen) >= buflen) return E2BIG;
2000 	(void) snprintf(nbuf, sizeof nbuf, "%d", pctx->ri);
2001 	if (strlcat(buf, nbuf, buflen) >= buflen) return E2BIG;
2002 	if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
2003 
2004 	if (strlcat(buf, "RUA=", buflen) >= buflen) return E2BIG;
2005 	for (i = 0; i < pctx->rua_cnt; ++i)
2006 	{
2007 		if (i > 0)
2008 		{
2009 			if (strlcat(buf, ",", buflen) >= buflen) return E2BIG;
2010 		}
2011 		if (strlcat(buf, (pctx->rua_list)[i], buflen) >= buflen) return E2BIG;
2012 	}
2013 	if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
2014 
2015 	if (strlcat(buf, "RUF=", buflen) >= buflen) return E2BIG;
2016 	for (i = 0; i < pctx->ruf_cnt; ++i)
2017 	{
2018 		if (i > 0)
2019 		{
2020 			if (strlcat(buf, ",", buflen) >= buflen) return E2BIG;
2021 		}
2022 		if (strlcat(buf, (pctx->ruf_list)[i], buflen) >= buflen) return E2BIG;
2023 	}
2024 	if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
2025 
2026 	if (strlcat(buf, "FO=", buflen) >= buflen) return E2BIG;
2027 	if (pctx->ruf_list == NULL || pctx->fo == DMARC_RECORD_FO_UNSPECIFIED)
2028 	{
2029 		if (strlcat(buf, "UNSPECIFIED", buflen) >= buflen) return E2BIG;
2030 	}
2031 	if ((pctx->fo&DMARC_RECORD_FO_0) != 0)
2032 	{
2033 		if (strlcat(buf, "0:", buflen) >= buflen) return E2BIG;
2034 	}
2035 	if ((pctx->fo&DMARC_RECORD_FO_1) != 0)
2036 	{
2037 		if (strlcat(buf, "1:", buflen) >= buflen) return E2BIG;
2038 	}
2039 	if ((pctx->fo&DMARC_RECORD_FO_D) != 0)
2040 	{
2041 		if (strlcat(buf, "d:", buflen) >= buflen) return E2BIG;
2042 	}
2043 	if ((pctx->fo&DMARC_RECORD_FO_S) != 0)
2044 	{
2045 		if (strlcat(buf, "s:", buflen) >= buflen) return E2BIG;
2046 	}
2047 	if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG;
2048 
2049 	return 0;
2050 }
2051