1/* $NetBSD: lwconfig.c,v 1.3.4.1 2012/06/05 21:14:54 bouyer Exp $ */ 2 3/* 4 * Copyright (C) 2004-2008, 2011, 2012 Internet Systems Consortium, Inc. ("ISC") 5 * Copyright (C) 2000-2003 Internet Software Consortium. 6 * 7 * Permission to use, copy, modify, and/or distribute this software for any 8 * purpose with or without fee is hereby granted, provided that the above 9 * copyright notice and this permission notice appear in all copies. 10 * 11 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH 12 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, 14 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 16 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 * PERFORMANCE OF THIS SOFTWARE. 18 */ 19 20/* Id */ 21 22/*! \file */ 23 24/** 25 * Module for parsing resolv.conf files. 26 * 27 * lwres_conf_init() creates an empty lwres_conf_t structure for 28 * lightweight resolver context ctx. 29 * 30 * lwres_conf_clear() frees up all the internal memory used by that 31 * lwres_conf_t structure in resolver context ctx. 32 * 33 * lwres_conf_parse() opens the file filename and parses it to initialise 34 * the resolver context ctx's lwres_conf_t structure. 35 * 36 * lwres_conf_print() prints the lwres_conf_t structure for resolver 37 * context ctx to the FILE fp. 38 * 39 * \section lwconfig_return Return Values 40 * 41 * lwres_conf_parse() returns #LWRES_R_SUCCESS if it successfully read and 42 * parsed filename. It returns #LWRES_R_FAILURE if filename could not be 43 * opened or contained incorrect resolver statements. 44 * 45 * lwres_conf_print() returns #LWRES_R_SUCCESS unless an error occurred 46 * when converting the network addresses to a numeric host address 47 * string. If this happens, the function returns #LWRES_R_FAILURE. 48 * 49 * \section lwconfig_see See Also 50 * 51 * stdio(3), \link resolver resolver \endlink 52 * 53 * \section files Files 54 * 55 * /etc/resolv.conf 56 */ 57 58#include <config.h> 59 60#include <assert.h> 61#include <ctype.h> 62#include <errno.h> 63#include <stdlib.h> 64#include <stdio.h> 65#include <string.h> 66#include <unistd.h> 67 68#include <lwres/lwbuffer.h> 69#include <lwres/lwres.h> 70#include <lwres/net.h> 71#include <lwres/result.h> 72 73#include "assert_p.h" 74#include "context_p.h" 75 76 77#if ! defined(NS_INADDRSZ) 78#define NS_INADDRSZ 4 79#endif 80 81#if ! defined(NS_IN6ADDRSZ) 82#define NS_IN6ADDRSZ 16 83#endif 84 85static lwres_result_t 86lwres_conf_parsenameserver(lwres_context_t *ctx, FILE *fp); 87 88static lwres_result_t 89lwres_conf_parselwserver(lwres_context_t *ctx, FILE *fp); 90 91static lwres_result_t 92lwres_conf_parsedomain(lwres_context_t *ctx, FILE *fp); 93 94static lwres_result_t 95lwres_conf_parsesearch(lwres_context_t *ctx, FILE *fp); 96 97static lwres_result_t 98lwres_conf_parsesortlist(lwres_context_t *ctx, FILE *fp); 99 100static lwres_result_t 101lwres_conf_parseoption(lwres_context_t *ctx, FILE *fp); 102 103static void 104lwres_resetaddr(lwres_addr_t *addr); 105 106static lwres_result_t 107lwres_create_addr(const char *buff, lwres_addr_t *addr, int convert_zero); 108 109static int lwresaddr2af(int lwresaddrtype); 110 111 112static int 113lwresaddr2af(int lwresaddrtype) 114{ 115 int af = 0; 116 117 switch (lwresaddrtype) { 118 case LWRES_ADDRTYPE_V4: 119 af = AF_INET; 120 break; 121 122 case LWRES_ADDRTYPE_V6: 123 af = AF_INET6; 124 break; 125 } 126 127 return (af); 128} 129 130 131/*! 132 * Eat characters from FP until EOL or EOF. Returns EOF or '\n' 133 */ 134static int 135eatline(FILE *fp) { 136 int ch; 137 138 ch = fgetc(fp); 139 while (ch != '\n' && ch != EOF) 140 ch = fgetc(fp); 141 142 return (ch); 143} 144 145 146/*! 147 * Eats white space up to next newline or non-whitespace character (of 148 * EOF). Returns the last character read. Comments are considered white 149 * space. 150 */ 151static int 152eatwhite(FILE *fp) { 153 int ch; 154 155 ch = fgetc(fp); 156 while (ch != '\n' && ch != EOF && isspace((unsigned char)ch)) 157 ch = fgetc(fp); 158 159 if (ch == ';' || ch == '#') 160 ch = eatline(fp); 161 162 return (ch); 163} 164 165 166/*! 167 * Skip over any leading whitespace and then read in the next sequence of 168 * non-whitespace characters. In this context newline is not considered 169 * whitespace. Returns EOF on end-of-file, or the character 170 * that caused the reading to stop. 171 */ 172static int 173getword(FILE *fp, char *buffer, size_t size) { 174 int ch; 175 char *p = buffer; 176 177 REQUIRE(buffer != NULL); 178 REQUIRE(size > 0U); 179 180 *p = '\0'; 181 182 ch = eatwhite(fp); 183 184 if (ch == EOF) 185 return (EOF); 186 187 for (;;) { 188 *p = '\0'; 189 190 if (ch == EOF || isspace((unsigned char)ch)) 191 break; 192 else if ((size_t) (p - buffer) == size - 1) 193 return (EOF); /* Not enough space. */ 194 195 *p++ = (char)ch; 196 ch = fgetc(fp); 197 } 198 199 return (ch); 200} 201 202static void 203lwres_resetaddr(lwres_addr_t *addr) { 204 REQUIRE(addr != NULL); 205 206 memset(addr->address, 0, LWRES_ADDR_MAXLEN); 207 addr->family = 0; 208 addr->length = 0; 209} 210 211static char * 212lwres_strdup(lwres_context_t *ctx, const char *str) { 213 char *p; 214 215 REQUIRE(str != NULL); 216 REQUIRE(strlen(str) > 0U); 217 218 p = CTXMALLOC(strlen(str) + 1); 219 if (p != NULL) 220 strcpy(p, str); 221 222 return (p); 223} 224 225/*% intializes data structure for subsequent config parsing. */ 226void 227lwres_conf_init(lwres_context_t *ctx) { 228 int i; 229 lwres_conf_t *confdata; 230 231 REQUIRE(ctx != NULL); 232 confdata = &ctx->confdata; 233 234 confdata->nsnext = 0; 235 confdata->lwnext = 0; 236 confdata->domainname = NULL; 237 confdata->searchnxt = 0; 238 confdata->sortlistnxt = 0; 239 confdata->resdebug = 0; 240 confdata->ndots = 1; 241 confdata->no_tld_query = 0; 242 243 for (i = 0; i < LWRES_CONFMAXNAMESERVERS; i++) 244 lwres_resetaddr(&confdata->nameservers[i]); 245 246 for (i = 0; i < LWRES_CONFMAXSEARCH; i++) 247 confdata->search[i] = NULL; 248 249 for (i = 0; i < LWRES_CONFMAXSORTLIST; i++) { 250 lwres_resetaddr(&confdata->sortlist[i].addr); 251 lwres_resetaddr(&confdata->sortlist[i].mask); 252 } 253} 254 255/*% Frees up all the internal memory used by the config data structure, returning it to the lwres_context_t. */ 256void 257lwres_conf_clear(lwres_context_t *ctx) { 258 int i; 259 lwres_conf_t *confdata; 260 261 REQUIRE(ctx != NULL); 262 confdata = &ctx->confdata; 263 264 for (i = 0; i < confdata->nsnext; i++) 265 lwres_resetaddr(&confdata->nameservers[i]); 266 267 if (confdata->domainname != NULL) { 268 CTXFREE(confdata->domainname, 269 strlen(confdata->domainname) + 1); 270 confdata->domainname = NULL; 271 } 272 273 for (i = 0; i < confdata->searchnxt; i++) { 274 if (confdata->search[i] != NULL) { 275 CTXFREE(confdata->search[i], 276 strlen(confdata->search[i]) + 1); 277 confdata->search[i] = NULL; 278 } 279 } 280 281 for (i = 0; i < LWRES_CONFMAXSORTLIST; i++) { 282 lwres_resetaddr(&confdata->sortlist[i].addr); 283 lwres_resetaddr(&confdata->sortlist[i].mask); 284 } 285 286 confdata->nsnext = 0; 287 confdata->lwnext = 0; 288 confdata->domainname = NULL; 289 confdata->searchnxt = 0; 290 confdata->sortlistnxt = 0; 291 confdata->resdebug = 0; 292 confdata->ndots = 1; 293 confdata->no_tld_query = 0; 294} 295 296static lwres_result_t 297lwres_conf_parsenameserver(lwres_context_t *ctx, FILE *fp) { 298 char word[LWRES_CONFMAXLINELEN]; 299 int res; 300 lwres_conf_t *confdata; 301 lwres_addr_t address; 302 303 confdata = &ctx->confdata; 304 305 if (confdata->nsnext == LWRES_CONFMAXNAMESERVERS) 306 return (LWRES_R_SUCCESS); 307 308 res = getword(fp, word, sizeof(word)); 309 if (strlen(word) == 0U) 310 return (LWRES_R_FAILURE); /* Nothing on line. */ 311 else if (res == ' ' || res == '\t') 312 res = eatwhite(fp); 313 314 if (res != EOF && res != '\n') 315 return (LWRES_R_FAILURE); /* Extra junk on line. */ 316 317 res = lwres_create_addr(word, &address, 1); 318 if (res == LWRES_R_SUCCESS && 319 ((address.family == LWRES_ADDRTYPE_V4 && ctx->use_ipv4 == 1) || 320 (address.family == LWRES_ADDRTYPE_V6 && ctx->use_ipv6 == 1))) { 321 confdata->nameservers[confdata->nsnext++] = address; 322 } 323 324 return (LWRES_R_SUCCESS); 325} 326 327static lwres_result_t 328lwres_conf_parselwserver(lwres_context_t *ctx, FILE *fp) { 329 char word[LWRES_CONFMAXLINELEN]; 330 int res; 331 lwres_conf_t *confdata; 332 333 confdata = &ctx->confdata; 334 335 if (confdata->lwnext == LWRES_CONFMAXLWSERVERS) 336 return (LWRES_R_SUCCESS); 337 338 res = getword(fp, word, sizeof(word)); 339 if (strlen(word) == 0U) 340 return (LWRES_R_FAILURE); /* Nothing on line. */ 341 else if (res == ' ' || res == '\t') 342 res = eatwhite(fp); 343 344 if (res != EOF && res != '\n') 345 return (LWRES_R_FAILURE); /* Extra junk on line. */ 346 347 res = lwres_create_addr(word, 348 &confdata->lwservers[confdata->lwnext++], 1); 349 if (res != LWRES_R_SUCCESS) 350 return (res); 351 352 return (LWRES_R_SUCCESS); 353} 354 355static lwres_result_t 356lwres_conf_parsedomain(lwres_context_t *ctx, FILE *fp) { 357 char word[LWRES_CONFMAXLINELEN]; 358 int res, i; 359 lwres_conf_t *confdata; 360 361 confdata = &ctx->confdata; 362 363 res = getword(fp, word, sizeof(word)); 364 if (strlen(word) == 0U) 365 return (LWRES_R_FAILURE); /* Nothing else on line. */ 366 else if (res == ' ' || res == '\t') 367 res = eatwhite(fp); 368 369 if (res != EOF && res != '\n') 370 return (LWRES_R_FAILURE); /* Extra junk on line. */ 371 372 if (confdata->domainname != NULL) 373 CTXFREE(confdata->domainname, 374 strlen(confdata->domainname) + 1); /* */ 375 376 /* 377 * Search and domain are mutually exclusive. 378 */ 379 for (i = 0; i < LWRES_CONFMAXSEARCH; i++) { 380 if (confdata->search[i] != NULL) { 381 CTXFREE(confdata->search[i], 382 strlen(confdata->search[i])+1); 383 confdata->search[i] = NULL; 384 } 385 } 386 confdata->searchnxt = 0; 387 388 confdata->domainname = lwres_strdup(ctx, word); 389 390 if (confdata->domainname == NULL) 391 return (LWRES_R_FAILURE); 392 393 return (LWRES_R_SUCCESS); 394} 395 396static lwres_result_t 397lwres_conf_parsesearch(lwres_context_t *ctx, FILE *fp) { 398 int idx, delim; 399 char word[LWRES_CONFMAXLINELEN]; 400 lwres_conf_t *confdata; 401 402 confdata = &ctx->confdata; 403 404 if (confdata->domainname != NULL) { 405 /* 406 * Search and domain are mutually exclusive. 407 */ 408 CTXFREE(confdata->domainname, 409 strlen(confdata->domainname) + 1); 410 confdata->domainname = NULL; 411 } 412 413 /* 414 * Remove any previous search definitions. 415 */ 416 for (idx = 0; idx < LWRES_CONFMAXSEARCH; idx++) { 417 if (confdata->search[idx] != NULL) { 418 CTXFREE(confdata->search[idx], 419 strlen(confdata->search[idx])+1); 420 confdata->search[idx] = NULL; 421 } 422 } 423 confdata->searchnxt = 0; 424 425 delim = getword(fp, word, sizeof(word)); 426 if (strlen(word) == 0U) 427 return (LWRES_R_FAILURE); /* Nothing else on line. */ 428 429 idx = 0; 430 while (strlen(word) > 0U) { 431 if (confdata->searchnxt == LWRES_CONFMAXSEARCH) 432 goto ignore; /* Too many domains. */ 433 434 confdata->search[idx] = lwres_strdup(ctx, word); 435 if (confdata->search[idx] == NULL) 436 return (LWRES_R_FAILURE); 437 idx++; 438 confdata->searchnxt++; 439 440 ignore: 441 if (delim == EOF || delim == '\n') 442 break; 443 else 444 delim = getword(fp, word, sizeof(word)); 445 } 446 447 return (LWRES_R_SUCCESS); 448} 449 450static lwres_result_t 451lwres_create_addr(const char *buffer, lwres_addr_t *addr, int convert_zero) { 452 struct in_addr v4; 453 struct in6_addr v6; 454 455 if (lwres_net_aton(buffer, &v4) == 1) { 456 if (convert_zero) { 457 unsigned char zeroaddress[] = {0, 0, 0, 0}; 458 unsigned char loopaddress[] = {127, 0, 0, 1}; 459 if (memcmp(&v4, zeroaddress, 4) == 0) 460 memcpy(&v4, loopaddress, 4); 461 } 462 addr->family = LWRES_ADDRTYPE_V4; 463 addr->length = NS_INADDRSZ; 464 memcpy((void *)addr->address, &v4, NS_INADDRSZ); 465 466 } else if (lwres_net_pton(AF_INET6, buffer, &v6) == 1) { 467 addr->family = LWRES_ADDRTYPE_V6; 468 addr->length = NS_IN6ADDRSZ; 469 memcpy((void *)addr->address, &v6, NS_IN6ADDRSZ); 470 } else { 471 return (LWRES_R_FAILURE); /* Unrecognised format. */ 472 } 473 474 return (LWRES_R_SUCCESS); 475} 476 477static lwres_result_t 478lwres_conf_parsesortlist(lwres_context_t *ctx, FILE *fp) { 479 int delim, res, idx; 480 char word[LWRES_CONFMAXLINELEN]; 481 char *p; 482 lwres_conf_t *confdata; 483 484 confdata = &ctx->confdata; 485 486 delim = getword(fp, word, sizeof(word)); 487 if (strlen(word) == 0U) 488 return (LWRES_R_FAILURE); /* Empty line after keyword. */ 489 490 while (strlen(word) > 0U) { 491 if (confdata->sortlistnxt == LWRES_CONFMAXSORTLIST) 492 return (LWRES_R_FAILURE); /* Too many values. */ 493 494 p = strchr(word, '/'); 495 if (p != NULL) 496 *p++ = '\0'; 497 498 idx = confdata->sortlistnxt; 499 res = lwres_create_addr(word, &confdata->sortlist[idx].addr, 1); 500 if (res != LWRES_R_SUCCESS) 501 return (res); 502 503 if (p != NULL) { 504 res = lwres_create_addr(p, 505 &confdata->sortlist[idx].mask, 506 0); 507 if (res != LWRES_R_SUCCESS) 508 return (res); 509 } else { 510 /* 511 * Make up a mask. 512 */ 513 confdata->sortlist[idx].mask = 514 confdata->sortlist[idx].addr; 515 516 memset(&confdata->sortlist[idx].mask.address, 0xff, 517 confdata->sortlist[idx].addr.length); 518 } 519 520 confdata->sortlistnxt++; 521 522 if (delim == EOF || delim == '\n') 523 break; 524 else 525 delim = getword(fp, word, sizeof(word)); 526 } 527 528 return (LWRES_R_SUCCESS); 529} 530 531static lwres_result_t 532lwres_conf_parseoption(lwres_context_t *ctx, FILE *fp) { 533 int delim; 534 long ndots; 535 char *p; 536 char word[LWRES_CONFMAXLINELEN]; 537 lwres_conf_t *confdata; 538 539 REQUIRE(ctx != NULL); 540 confdata = &ctx->confdata; 541 542 delim = getword(fp, word, sizeof(word)); 543 if (strlen(word) == 0U) 544 return (LWRES_R_FAILURE); /* Empty line after keyword. */ 545 546 while (strlen(word) > 0U) { 547 if (strcmp("debug", word) == 0) { 548 confdata->resdebug = 1; 549 } else if (strcmp("no_tld_query", word) == 0) { 550 confdata->no_tld_query = 1; 551 } else if (strncmp("ndots:", word, 6) == 0) { 552 ndots = strtol(word + 6, &p, 10); 553 if (*p != '\0') /* Bad string. */ 554 return (LWRES_R_FAILURE); 555 if (ndots < 0 || ndots > 0xff) /* Out of range. */ 556 return (LWRES_R_FAILURE); 557 confdata->ndots = (lwres_uint8_t)ndots; 558 } 559 560 if (delim == EOF || delim == '\n') 561 break; 562 else 563 delim = getword(fp, word, sizeof(word)); 564 } 565 566 return (LWRES_R_SUCCESS); 567} 568 569/*% parses a file and fills in the data structure. */ 570lwres_result_t 571lwres_conf_parse(lwres_context_t *ctx, const char *filename) { 572 FILE *fp = NULL; 573 char word[256]; 574 lwres_result_t rval, ret; 575 lwres_conf_t *confdata; 576 int stopchar; 577 578 REQUIRE(ctx != NULL); 579 confdata = &ctx->confdata; 580 581 REQUIRE(filename != NULL); 582 REQUIRE(strlen(filename) > 0U); 583 REQUIRE(confdata != NULL); 584 585 errno = 0; 586 if ((fp = fopen(filename, "r")) == NULL) 587 return (LWRES_R_NOTFOUND); 588 589 ret = LWRES_R_SUCCESS; 590 for (;;) { 591 stopchar = getword(fp, word, sizeof(word)); 592 if (stopchar == EOF) { 593 rval = LWRES_R_SUCCESS; 594 POST(rval); 595 break; 596 } 597 598 if (strlen(word) == 0U) 599 rval = LWRES_R_SUCCESS; 600 else if (strcmp(word, "nameserver") == 0) 601 rval = lwres_conf_parsenameserver(ctx, fp); 602 else if (strcmp(word, "lwserver") == 0) 603 rval = lwres_conf_parselwserver(ctx, fp); 604 else if (strcmp(word, "domain") == 0) 605 rval = lwres_conf_parsedomain(ctx, fp); 606 else if (strcmp(word, "search") == 0) 607 rval = lwres_conf_parsesearch(ctx, fp); 608 else if (strcmp(word, "sortlist") == 0) 609 rval = lwres_conf_parsesortlist(ctx, fp); 610 else if (strcmp(word, "options") == 0) 611 rval = lwres_conf_parseoption(ctx, fp); 612 else { 613 /* unrecognised word. Ignore entire line */ 614 rval = LWRES_R_SUCCESS; 615 stopchar = eatline(fp); 616 if (stopchar == EOF) { 617 break; 618 } 619 } 620 if (ret == LWRES_R_SUCCESS && rval != LWRES_R_SUCCESS) 621 ret = rval; 622 } 623 624 fclose(fp); 625 626 return (ret); 627} 628 629/*% Prints the config data structure to the FILE. */ 630lwres_result_t 631lwres_conf_print(lwres_context_t *ctx, FILE *fp) { 632 int i; 633 int af; 634 char tmp[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; 635 const char *p; 636 lwres_conf_t *confdata; 637 lwres_addr_t tmpaddr; 638 639 REQUIRE(ctx != NULL); 640 confdata = &ctx->confdata; 641 642 REQUIRE(confdata->nsnext <= LWRES_CONFMAXNAMESERVERS); 643 644 for (i = 0; i < confdata->nsnext; i++) { 645 af = lwresaddr2af(confdata->nameservers[i].family); 646 647 p = lwres_net_ntop(af, confdata->nameservers[i].address, 648 tmp, sizeof(tmp)); 649 if (p != tmp) 650 return (LWRES_R_FAILURE); 651 652 fprintf(fp, "nameserver %s\n", tmp); 653 } 654 655 for (i = 0; i < confdata->lwnext; i++) { 656 af = lwresaddr2af(confdata->lwservers[i].family); 657 658 p = lwres_net_ntop(af, confdata->lwservers[i].address, 659 tmp, sizeof(tmp)); 660 if (p != tmp) 661 return (LWRES_R_FAILURE); 662 663 fprintf(fp, "lwserver %s\n", tmp); 664 } 665 666 if (confdata->domainname != NULL) { 667 fprintf(fp, "domain %s\n", confdata->domainname); 668 } else if (confdata->searchnxt > 0) { 669 REQUIRE(confdata->searchnxt <= LWRES_CONFMAXSEARCH); 670 671 fprintf(fp, "search"); 672 for (i = 0; i < confdata->searchnxt; i++) 673 fprintf(fp, " %s", confdata->search[i]); 674 fputc('\n', fp); 675 } 676 677 REQUIRE(confdata->sortlistnxt <= LWRES_CONFMAXSORTLIST); 678 679 if (confdata->sortlistnxt > 0) { 680 fputs("sortlist", fp); 681 for (i = 0; i < confdata->sortlistnxt; i++) { 682 af = lwresaddr2af(confdata->sortlist[i].addr.family); 683 684 p = lwres_net_ntop(af, 685 confdata->sortlist[i].addr.address, 686 tmp, sizeof(tmp)); 687 if (p != tmp) 688 return (LWRES_R_FAILURE); 689 690 fprintf(fp, " %s", tmp); 691 692 tmpaddr = confdata->sortlist[i].mask; 693 memset(&tmpaddr.address, 0xff, tmpaddr.length); 694 695 if (memcmp(&tmpaddr.address, 696 confdata->sortlist[i].mask.address, 697 confdata->sortlist[i].mask.length) != 0) { 698 af = lwresaddr2af( 699 confdata->sortlist[i].mask.family); 700 p = lwres_net_ntop 701 (af, 702 confdata->sortlist[i].mask.address, 703 tmp, sizeof(tmp)); 704 if (p != tmp) 705 return (LWRES_R_FAILURE); 706 707 fprintf(fp, "/%s", tmp); 708 } 709 } 710 fputc('\n', fp); 711 } 712 713 if (confdata->resdebug) 714 fprintf(fp, "options debug\n"); 715 716 if (confdata->ndots > 0) 717 fprintf(fp, "options ndots:%d\n", confdata->ndots); 718 719 if (confdata->no_tld_query) 720 fprintf(fp, "options no_tld_query\n"); 721 722 return (LWRES_R_SUCCESS); 723} 724 725/*% Returns a pointer to the current config structure. */ 726lwres_conf_t * 727lwres_conf_get(lwres_context_t *ctx) { 728 REQUIRE(ctx != NULL); 729 730 return (&ctx->confdata); 731} 732