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