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