1 /***********************************************************************
2 ** OPENDMARC_XML.C
3 **		OPENDMARC_XML -- Parse a blob of xml DMARC report data
4 **		OPENDMARC_XML_PARSE -- Read a file into a blob
5 **  Copyright (c) 2012-2014, The Trusted Domain Project.  All rights reserved.
6 ************************************************************************/
7 # include "opendmarc_internal.h"
8 
9 /* libbsd if found */
10 #ifdef USE_BSD_H
11 # include <bsd/string.h>
12 #endif /* USE_BSD_H */
13 
14 /* libstrl if needed */
15 #ifdef USE_STRL_H
16 # include <strl.h>
17 #endif /* USE_STRL_H */
18 
19 /* opendmarc_strl if needed */
20 #ifdef USE_DMARCSTRL_H
21 # include <opendmarc_strl.h>
22 #endif /* USE_DMARCSTRL_H */
23 
24 static char *Taglist[] = {
25 	"adkim",
26 	"aspf",
27 	"auth_results",
28 	"begin",
29 	"comment",
30 	"count",
31 	"date_range",
32 	"disposition",
33 	"dkim",
34 	"domain",
35 	"email",
36 	"end",
37 	"extra_contact_info",
38 	"feedback",
39 	"header_from",
40 	"human_result",
41 	"identifiers",
42 	"org_name",
43 	"p",
44 	"pct",
45 	"policy_evaluated",
46 	"policy_published",
47 	"reason",
48 	"record",
49 	"report_id",
50 	"report_metadata",
51 	"result",
52 	"row",
53 	"source_ip",
54 	"sp",
55 	"spf",
56 	"type",
57 	NULL,
58 };
59 
60 static int
tag_lookup(char * tag)61 tag_lookup(char *tag)
62 {
63 	char **cpp;
64 
65 	for (cpp = Taglist; *cpp != NULL; ++cpp)
66 	{
67 		if (strcasecmp(*cpp, tag) == 0)
68 			return TRUE;
69 	}
70 	return FALSE;
71 }
72 
73 /***********************************************************************
74 ** OPENDMARC_XML -- Parse a blob of xml DMARC report data
75 **	Arguments:
76 **		b	-- The blob of xml report data
77 **		blen	-- Size of blob
78 **	Returns:
79 **		Nothing yet NEED TO DESIGN OUTPUT
80 **	Side Effects:
81 **		Pushes and pops off local stack, no recursion.
82 ************************************************************************/
83 # define MAX_STACK_DEPTH		(10)
84 # define MAX_STACK_LINE_LEN	(256)
85 # define MAX_ITEM_NAME_LEN	(256)
86 typedef char STACK[MAX_STACK_DEPTH][MAX_STACK_LINE_LEN];
87 
88 u_char **
opendmarc_xml(char * b,size_t blen,char * e,size_t elen)89 opendmarc_xml(char *b, size_t blen, char *e, size_t elen)
90 {
91 	STACK		stack;
92 	int		sidx			 = -1;
93 	char		*cp, *ep, *sp, *tagp;
94 	int		i;
95 	int		inside = FALSE;
96 	char		org_name[MAX_ITEM_NAME_LEN];
97 	u_char **	ary			= NULL;
98 	int		ary_cnt			= 0;
99 	char		begin[MAX_ITEM_NAME_LEN];
100 	char		end[MAX_ITEM_NAME_LEN];
101 	char		source_ip[MAX_ITEM_NAME_LEN];
102 	char		report_id[MAX_ITEM_NAME_LEN];
103 	char		email[MAX_ITEM_NAME_LEN];
104 	char		count[MAX_ITEM_NAME_LEN];
105 	char		disposition[MAX_ITEM_NAME_LEN];
106 	char		policy_eval_dkim[MAX_ITEM_NAME_LEN];
107 	char		policy_eval_spf[MAX_ITEM_NAME_LEN];
108 	char		domain[MAX_ITEM_NAME_LEN];
109 	char		reason_type[MAX_ITEM_NAME_LEN];
110 	char		reason_comment[MAX_ITEM_NAME_LEN];
111 	char		adkim[8];
112 	char		aspf[8];
113 	char		p[32];
114 	char		pct[8];
115 	char		header_from[MAX_ITEM_NAME_LEN];
116 	char		auth_dkim_domain[MAX_ITEM_NAME_LEN];
117 	char		auth_dkim_result[MAX_ITEM_NAME_LEN];
118 	char		auth_dkim_human[MAX_ITEM_NAME_LEN];
119 	char		auth_spf_domain[MAX_ITEM_NAME_LEN];
120 	char		auth_spf_result[MAX_ITEM_NAME_LEN];
121 	char		auth_spf_human[MAX_ITEM_NAME_LEN];
122 	char		obuf[BUFSIZ * 2];
123 	char		e_buf[128];
124 
125 
126 	if (e == NULL)
127 	{
128 		e = e_buf;
129 		elen = sizeof e_buf;
130 	}
131 	(void) memset(auth_dkim_domain, '\0', sizeof auth_dkim_domain);
132 	(void) memset(auth_dkim_human,	'\0', sizeof auth_dkim_human);
133 	(void) memset(auth_dkim_result, '\0', sizeof auth_dkim_result);
134 	(void) memset(auth_spf_domain,	'\0', sizeof auth_spf_domain);
135 	(void) memset(auth_spf_human,	'\0', sizeof auth_spf_human);
136 	(void) memset(auth_spf_result,	'\0', sizeof auth_spf_result);
137 	(void) memset(count,		'\0', sizeof count);
138 	(void) memset(disposition,	'\0', sizeof disposition);
139 	(void) memset(email,		'\0', sizeof email);
140 	(void) memset(header_from,	'\0', sizeof header_from);
141 	(void) memset(policy_eval_dkim, '\0', sizeof policy_eval_dkim);
142 	(void) memset(policy_eval_spf,	'\0', sizeof policy_eval_spf);
143 	(void) memset(source_ip,	'\0', sizeof source_ip);
144 	(void) memset(stack, 		'\0', sizeof(STACK));
145 
146 	(void) memset(obuf, '\0', sizeof obuf);
147 	(void) strlcpy(obuf, "begin,end,org_name,email,domain,adkim,aspf,p,pct,source_ip,count,disposition,policy_eval_dkim,policy_eval_spf,reason_type,reason_comment,header_from,auth_dkim_domain,auth_dkim_result,auth_dkim_human,auth_spf_domain,auth_spf_result,auth_spf_human", sizeof obuf);
148 	ary = opendmarc_util_pushargv((u_char *)obuf, ary, &ary_cnt);
149 
150 	ep = b + blen;
151 	for (cp = b; cp < ep; ++cp)
152 	{
153 		if (isspace((int) *cp))
154 			continue;
155 
156 		if (inside == FALSE)
157 		{
158 			if (*cp != '<')
159 				continue;
160 			++cp;
161 			for (sp = cp; *sp != '\0'; ++sp)
162 			{
163 				if (*sp == '?')
164 					break;
165 				if (isalpha((int) *sp) || *sp == '_' ||*sp == '/')
166 					continue;
167 				break;
168 			}
169 			if (*sp == '?')
170 				continue;
171 			*sp = '\0';
172 			if (*cp == '/')
173 				tagp = cp+1;
174 			else
175 				tagp = cp;
176 			if (tag_lookup(tagp) == FALSE)
177 			{
178 				continue;
179 			}
180 			if (*cp == '/')
181 			{
182 				if (sidx == -1)
183 				{
184 					//(void) fprintf(stderr, "<%s>: %s\n",
185 					//	cp, "End token with never a start token (ignored)");
186 					cp = sp;
187 					continue;
188 				}
189 				if (strcasecmp(cp+1, "record") == 0)
190 				{
191 					(void) memset(obuf, '\0', sizeof obuf);
192 					(void) strlcat(obuf, begin, sizeof obuf);
193 					(void) strlcat(obuf, ",", sizeof obuf);
194 					(void) strlcat(obuf, end, sizeof obuf);
195 					(void) strlcat(obuf, ",", sizeof obuf);
196 					(void) strlcat(obuf, org_name, sizeof obuf);
197 					(void) strlcat(obuf, ",", sizeof obuf);
198 					(void) strlcat(obuf, email, sizeof obuf);
199 					(void) strlcat(obuf, ",", sizeof obuf);
200 					(void) strlcat(obuf, domain, sizeof obuf);
201 					(void) strlcat(obuf, ",", sizeof obuf);
202 					(void) strlcat(obuf, adkim, sizeof obuf);
203 					(void) strlcat(obuf, ",", sizeof obuf);
204 					(void) strlcat(obuf, aspf, sizeof obuf);
205 					(void) strlcat(obuf, ",", sizeof obuf);
206 					(void) strlcat(obuf, p, sizeof obuf);
207 					(void) strlcat(obuf, ",", sizeof obuf);
208 					(void) strlcat(obuf, pct, sizeof obuf);
209 					(void) strlcat(obuf, ",", sizeof obuf);
210 					(void) strlcat(obuf, source_ip, sizeof obuf);
211 					(void) strlcat(obuf, ",", sizeof obuf);
212 					(void) strlcat(obuf, count, sizeof obuf);
213 					(void) strlcat(obuf, ",", sizeof obuf);
214 					(void) strlcat(obuf, disposition, sizeof obuf);
215 					(void) strlcat(obuf, ",", sizeof obuf);
216 					(void) strlcat(obuf, policy_eval_dkim, sizeof obuf);
217 					(void) strlcat(obuf, ",", sizeof obuf);
218 					(void) strlcat(obuf, policy_eval_spf, sizeof obuf);
219 					(void) strlcat(obuf, ",", sizeof obuf);
220 					(void) strlcat(obuf, reason_type, sizeof obuf);
221 					(void) strlcat(obuf, ",", sizeof obuf);
222 					(void) strlcat(obuf, reason_comment, sizeof obuf);
223 					(void) strlcat(obuf, ",", sizeof obuf);
224 					(void) strlcat(obuf, header_from, sizeof obuf);
225 					(void) strlcat(obuf, ",", sizeof obuf);
226 					(void) strlcat(obuf, auth_dkim_domain, sizeof obuf);
227 					(void) strlcat(obuf, ",", sizeof obuf);
228 					(void) strlcat(obuf, auth_dkim_result, sizeof obuf);
229 					(void) strlcat(obuf, ",", sizeof obuf);
230 					(void) strlcat(obuf, auth_dkim_human, sizeof obuf);
231 					(void) strlcat(obuf, ",", sizeof obuf);
232 					(void) strlcat(obuf, auth_spf_domain, sizeof obuf);
233 					(void) strlcat(obuf, ",", sizeof obuf);
234 					(void) strlcat(obuf, auth_spf_result, sizeof obuf);
235 					(void) strlcat(obuf, ",", sizeof obuf);
236 					(void) strlcat(obuf, auth_spf_human, sizeof obuf);
237 					(void) strlcat(obuf, ",", sizeof obuf);
238 					ary = opendmarc_util_pushargv((u_char *)obuf, ary, &ary_cnt);
239 					if (ary == NULL)
240 					{
241 						int xerror = errno;
242 
243 						(void) strlcpy(e, "Allocate memory :", elen);
244 						(void) strlcat(e, strerror(xerror), elen);
245 						return ary;
246 					}
247 
248 					(void) memset(count,		'\0', sizeof count);
249 					(void) memset(source_ip,	'\0', sizeof source_ip);
250 					(void) memset(disposition,	'\0', sizeof disposition);
251 					(void) memset(policy_eval_dkim, '\0', sizeof policy_eval_dkim);
252 					(void) memset(policy_eval_spf,	'\0', sizeof policy_eval_spf);
253 					(void) memset(reason_type,	'\0', sizeof reason_type);
254 					(void) memset(reason_comment,	'\0', sizeof reason_comment);
255 					(void) memset(header_from,	'\0', sizeof header_from);
256 					(void) memset(auth_dkim_domain, '\0', sizeof auth_dkim_domain);
257 					(void) memset(auth_dkim_result, '\0', sizeof auth_dkim_result);
258 					(void) memset(auth_dkim_human,	'\0', sizeof auth_dkim_human);
259 					(void) memset(auth_spf_domain,	'\0', sizeof auth_spf_domain);
260 					(void) memset(auth_spf_result,	'\0', sizeof auth_spf_result);
261 					(void) memset(auth_spf_human,	'\0', sizeof auth_spf_human);
262 				}
263 				/*
264 				** If </foo> matches current <foo>, pop off the stack.
265 				** Possiblle bug here, for bad case of <foo>text</boo>
266 				** My understanding is that XML clauses may not overlap. That is,
267 				** the following is illegal:
268 				**     <aaa>text<bbb>text</aaa>text</bbb>
269 				*/
270 				if (strcasecmp(cp+1, stack[sidx]) == 0)
271 				{
272 					--sidx;
273 					cp = sp;
274 					if (sidx < -1)
275 						break;
276 					continue;
277 				}
278 				else
279 				{
280 					/* recover gracefully how? */
281 				}
282 				for (i = sidx; i > 0; --i)
283 				{
284 					if (strcasecmp(cp+1, stack[sidx]) == 0)
285 						break;
286 				}
287 				if (i < 0)
288 				{
289 					//(void) fprintf(stderr, "<%s>: %s\n",
290 					//	cp, "End token with no start token (ignored)");
291 					cp = sp;
292 					continue;
293 				}
294 				if (sidx >= 0)
295 					--sidx;
296 				cp = sp;
297 				continue;
298 
299 			}
300 			else
301 			{
302 				++sidx;
303 				if (sidx >= MAX_STACK_DEPTH)
304 				{
305 					(void) strlcpy(e, "<", elen);
306 					(void) strlcat(e, cp, elen);
307 					(void) strlcat(e, ">: Too much stack depth", elen);
308 					return (ary = opendmarc_util_clearargv(ary));
309 				}
310 				(void) strlcpy(stack[sidx], cp, MAX_STACK_LINE_LEN);
311 				cp = sp;
312 				inside = TRUE;
313 				continue;
314 			}
315 		}
316 		else
317 		{
318 			if (*cp == '<')
319 			{
320 				inside = FALSE;
321 				--cp;
322 				continue;
323 			}
324 			for(sp = cp; *sp != '\0'; ++sp)
325 			{
326 				if (*sp == '<')
327 					break;
328 				continue;
329 			}
330 			if (*sp != '<')
331 			{
332 				cp = sp-1;
333 				continue;
334 			}
335 			*sp = '\0';
336 			if (strcasecmp(stack[sidx], "org_name") == 0)
337 			{
338 				(void) memset(org_name, '\0', sizeof org_name);
339 				(void) strlcpy(org_name, cp, sizeof org_name);
340 			}
341 			else if (strcasecmp(stack[sidx], "report_id") == 0)
342 			{
343 				(void) memset(report_id, '\0', sizeof report_id);
344 				(void) strlcpy(report_id, cp, sizeof report_id);
345 			}
346 			else if (strcasecmp(stack[sidx], "email") == 0)
347 			{
348 				(void) memset(email, '\0', sizeof email);
349 				(void) strlcpy(email, cp, sizeof email);
350 			}
351 			else if (strcasecmp(stack[sidx], "begin") == 0)
352 			{
353 				time_t t;
354 				struct tm *tm;
355 
356 				t = strtoul(cp, NULL, 10);
357 				tm = gmtime(&t);
358 				(void) memset(begin, '\0', sizeof begin);
359 				(void) strftime(begin, sizeof begin, "%F-%H:%M:%S", tm);
360 			}
361 			else if (strcasecmp(stack[sidx], "end") == 0)
362 			{
363 				time_t t;
364 				struct tm *tm;
365 
366 				t = strtoul(cp, NULL, 10);
367 				tm = gmtime(&t);
368 				(void) memset(end, '\0', sizeof end);
369 				(void) strftime(end, sizeof end, "%F-%H:%M:%S", tm);
370 			}
371 			else if (strcasecmp(stack[sidx], "source_ip") == 0)
372 			{
373 				(void) strlcpy(source_ip, cp, sizeof source_ip);
374 			}
375 			else if (sidx > 1 && strcasecmp(stack[sidx-2], "auth_results") == 0 &&
376 					strcasecmp(stack[sidx-1], "dkim") == 0 &&
377 					strcasecmp(stack[sidx], "domain") == 0)
378 			{
379 				if (*auth_dkim_domain == '\0')
380 					(void) strlcpy(auth_dkim_domain, cp, sizeof auth_dkim_domain);
381 				else
382 				{
383 					(void) strlcat(auth_dkim_domain, "|", sizeof auth_dkim_domain);
384 					(void) strlcat(auth_dkim_domain, cp, sizeof auth_dkim_domain);
385 				}
386 			}
387 			else if (sidx > 1 && strcasecmp(stack[sidx-2], "auth_results") == 0 &&
388 					strcasecmp(stack[sidx-1], "dkim") == 0 &&
389 					strcasecmp(stack[sidx], "result") == 0)
390 			{
391 				if (*auth_dkim_result == '\0')
392 					(void) strlcpy(auth_dkim_result, cp, sizeof auth_dkim_result);
393 				else
394 				{
395 					(void) strlcat(auth_dkim_result, "|", sizeof auth_dkim_result);
396 					(void) strlcat(auth_dkim_result, cp, sizeof auth_dkim_result);
397 				}
398 			}
399 			else if (sidx > 1 && strcasecmp(stack[sidx-2], "auth_results") == 0 &&
400 					strcasecmp(stack[sidx-1], "dkim") == 0 &&
401 					strcasecmp(stack[sidx], "human_result") == 0)
402 			{
403 				if (*auth_dkim_human == '\0')
404 					(void) strlcpy(auth_dkim_human, cp, sizeof auth_dkim_human);
405 				else
406 				{
407 					(void) strlcat(auth_dkim_human, "|", sizeof auth_dkim_human);
408 					(void) strlcat(auth_dkim_human, cp, sizeof auth_dkim_human);
409 				}
410 			}
411 			else if (sidx > 1 && strcasecmp(stack[sidx-2], "auth_results") == 0 &&
412 					strcasecmp(stack[sidx-1], "spf") == 0 &&
413 					strcasecmp(stack[sidx], "domain") == 0)
414 			{
415 				if (*auth_spf_domain == '\0')
416 					(void) strlcpy(auth_spf_domain, cp, sizeof auth_spf_domain);
417 				else
418 				{
419 					(void) strlcat(auth_spf_domain, "|", sizeof auth_spf_domain);
420 					(void) strlcat(auth_spf_domain, cp, sizeof auth_spf_domain);
421 				}
422 			}
423 			else if (sidx > 1 && strcasecmp(stack[sidx-2], "auth_results") == 0 &&
424 					strcasecmp(stack[sidx-1], "spf") == 0 &&
425 					strcasecmp(stack[sidx], "result") == 0)
426 			{
427 				if (*auth_spf_result == '\0')
428 					(void) strlcpy(auth_spf_result, cp, sizeof auth_spf_result);
429 				else
430 				{
431 					(void) strlcat(auth_spf_result, "|", sizeof auth_spf_result);
432 					(void) strlcat(auth_spf_result, cp, sizeof auth_spf_result);
433 				}
434 			}
435 			else if (sidx > 1 && strcasecmp(stack[sidx-2], "auth_results") == 0 &&
436 					strcasecmp(stack[sidx-1], "spf") == 0 &&
437 					strcasecmp(stack[sidx], "human_result") == 0)
438 			{
439 				if (*auth_spf_human == '\0')
440 					(void) strlcpy(auth_spf_human, cp, sizeof auth_spf_human);
441 				else
442 				{
443 					(void) strlcat(auth_spf_human, "|", sizeof auth_spf_human);
444 					(void) strlcat(auth_spf_human, cp, sizeof auth_spf_human);
445 				}
446 			}
447 			else if (sidx > 0 && strcasecmp(stack[sidx-1], "policy_published") == 0 &&
448 					strcasecmp(stack[sidx], "domain") == 0)
449 			{
450 				(void) memset(domain, '\0', sizeof domain);
451 				(void) strlcpy(domain, cp, sizeof domain);
452 			}
453 			else if (sidx > 0 && strcasecmp(stack[sidx-1], "policy_published") == 0 &&
454 					strcasecmp(stack[sidx], "adkim") == 0)
455 			{
456 				(void) memset(adkim, '\0', sizeof adkim);
457 				(void) strlcpy(adkim, cp, sizeof adkim);
458 			}
459 			else if (sidx > 0 && strcasecmp(stack[sidx-1], "policy_published") == 0 &&
460 					strcasecmp(stack[sidx], "aspf") == 0)
461 			{
462 				(void) memset(aspf, '\0', sizeof aspf);
463 				(void) strlcpy(aspf, cp, sizeof aspf);
464 			}
465 			else if (sidx > 0 && strcasecmp(stack[sidx-1], "policy_published") == 0 &&
466 					strcasecmp(stack[sidx], "pct") == 0)
467 			{
468 				(void) memset(pct, '\0', sizeof pct);
469 				(void) strlcpy(pct, cp, sizeof pct);
470 			}
471 			else if (sidx > 0 && strcasecmp(stack[sidx-1], "policy_published") == 0 &&
472 					strcasecmp(stack[sidx], "p") == 0)
473 			{
474 				(void) memset(p, '\0', sizeof p);
475 				(void) strlcpy(p, cp, sizeof p);
476 			}
477 			else if (sidx > 0 && strcasecmp(stack[sidx-1], "reason") == 0 &&
478 					strcasecmp(stack[sidx], "type") == 0)
479 			{
480 				if (strlen(reason_type) > 0)
481 					(void) strlcat(reason_type, " ", sizeof reason_type);
482 				(void) strlcat(reason_type, cp, sizeof reason_type);
483 			}
484 			else if (sidx > 0 && strcasecmp(stack[sidx-1], "reason") == 0 &&
485 					strcasecmp(stack[sidx], "comment") == 0)
486 			{
487 				if (strlen(reason_comment) > 0)
488 					(void) strlcat(reason_comment, " ", sizeof reason_comment);
489 				(void) strlcat(reason_comment, cp, sizeof reason_comment);
490 			}
491 			else if (sidx > 0 && strcasecmp(stack[sidx-1], "identifiers") == 0 &&
492 				 strcasecmp(stack[sidx], "header_from") == 0)
493 			{
494 				/*
495 				 * Some sites put a full address in here.
496 				 * Some others list mutilple address here.
497 				 */
498 				if (*header_from == '\0')
499 					(void) strlcpy(header_from, cp, sizeof header_from);
500 				else
501 				{
502 					(void) strlcat(header_from, "|", sizeof header_from);
503 					(void) strlcat(header_from, cp, sizeof header_from);
504 				}
505 			}
506 			else if (strcasecmp(stack[sidx], "count") == 0)
507 			{
508 				(void) strlcpy(count, cp, sizeof count);
509 			}
510 			else if (sidx > 0 && strcasecmp(stack[sidx-1], "policy_evaluated") == 0 &&
511 					strcasecmp(stack[sidx], "disposition") == 0)
512 			{
513 				(void) strlcpy(disposition, cp, sizeof disposition);
514 			}
515 			else if (sidx > 0 && strcasecmp(stack[sidx-1], "policy_evaluated") == 0 &&
516 					strcasecmp(stack[sidx], "dkim") == 0)
517 			{
518 				(void) strlcpy(policy_eval_dkim, cp, sizeof policy_eval_dkim);
519 			}
520 			else if (sidx > 0 && strcasecmp(stack[sidx-1], "policy_evaluated") == 0 &&
521 					strcasecmp(stack[sidx], "spf") == 0)
522 			{
523 				(void) strlcpy(policy_eval_spf, cp, sizeof policy_eval_spf);
524 			}
525 			*sp = '<';
526 			cp = sp-1;
527 			inside = FALSE;
528 			continue;
529 		}
530 	}
531 	return ary;
532 }
533 
534 u_char **
opendmarc_xml_parse(char * fname,char * err_buf,size_t err_len)535 opendmarc_xml_parse(char *fname, char *err_buf, size_t err_len)
536 {
537 	struct stat	statb;
538 	FILE *		fp;
539 	char *		bufp;
540 	char		e_buf[128];
541 	int		ret;
542 	u_char **	ary = NULL;
543 	int		xerror;
544 	size_t		rb;
545 
546 	if (fname == NULL)
547 	{
548 		xerror = errno;
549 		(void) snprintf(err_buf, err_len, "%s", "File name was NULL");
550 		errno = EINVAL;
551 		return NULL;
552 	}
553 	if (err_buf == NULL)
554 	{
555 		err_buf = e_buf;
556 		err_len = sizeof e_buf;
557 	}
558 
559 	ret = lstat(fname, &statb);
560 	if (ret != 0)
561 	{
562 		xerror = errno;
563 		(void) snprintf(err_buf, err_len, "%s: %s", fname, strerror(errno));
564 		errno = xerror;
565 		return NULL;
566 	}
567 	if (statb.st_size == 0)
568 	{
569 		xerror = errno;
570 		(void) snprintf(err_buf, err_len, "%s: %s", fname, "Empty file.");
571 		errno = xerror;
572 		return NULL;
573 	}
574 
575 	bufp = calloc(statb.st_size + 1, 1);
576 	if (bufp == NULL)
577 	{
578 		xerror = errno;
579 		(void) snprintf(err_buf, err_len, "%s: %s", fname, strerror(errno));
580 		errno = xerror;
581 		return NULL;
582 	}
583 
584 	fp = fopen(fname, "r");
585 	if (fp == NULL)
586 	{
587 		xerror = errno;
588 		(void) snprintf(err_buf, err_len, "%s: %s", fname, strerror(errno));
589 		(void) free(bufp);
590 		errno = xerror;
591 		return NULL;
592 	}
593 
594 	rb = fread(bufp, 1, statb.st_size, fp);
595 	if (rb != statb.st_size)
596 	{
597 		xerror = errno;
598 		(void) snprintf(err_buf, err_len, "%s: truncated read", fname);
599 		(void) free(bufp);
600 		(void) fclose(fp);
601 		errno = xerror;
602 		return NULL;
603 	}
604 	else if (ferror(fp))
605 	{
606 		xerror = errno;
607 		(void) snprintf(err_buf, err_len, "%s: %s", fname, strerror(errno));
608 		(void) free(bufp);
609 		(void) fclose(fp);
610 		errno = xerror;
611 		return NULL;
612 	}
613 	(void) fclose(fp);
614 	ary =  opendmarc_xml(bufp, statb.st_size, err_buf, err_len);
615 	xerror = errno;
616 	(void) free(bufp);
617 	errno = xerror;
618 	return ary;
619 }
620 
621