xref: /minix3/external/bsd/bind/dist/lib/irs/resconf.c (revision 00b67f09dd46474d133c95011a48590a8e8f94c7)
1 /*	$NetBSD: resconf.c,v 1.8 2014/12/10 04:37:59 christos Exp $	*/
2 
3 /*
4  * Copyright (C) 2009, 2011, 2012, 2014  Internet Systems Consortium, Inc. ("ISC")
5  *
6  * Permission to use, copy, modify, and/or distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
11  * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
12  * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
13  * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
14  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
15  * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
16  * PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 /* Id */
20 
21 /*! \file resconf.c */
22 
23 /**
24  * Module for parsing resolv.conf files (largely derived from lwconfig.c).
25  *
26  *    irs_resconf_load() opens the file filename and parses it to initialize
27  *    the configuration structure.
28  *
29  * \section lwconfig_return Return Values
30  *
31  *    irs_resconf_load() returns #IRS_R_SUCCESS if it successfully read and
32  *    parsed filename. It returns a non-0 error code if filename could not be
33  *    opened or contained incorrect resolver statements.
34  *
35  * \section lwconfig_see See Also
36  *
37  *    stdio(3), \link resolver resolver \endlink
38  *
39  * \section files Files
40  *
41  *    /etc/resolv.conf
42  */
43 
44 #include <config.h>
45 
46 #ifndef WIN32
47 #include <sys/types.h>
48 #include <sys/socket.h>
49 #include <netdb.h>
50 #endif
51 
52 #include <ctype.h>
53 #include <errno.h>
54 #include <stdlib.h>
55 #include <stdio.h>
56 #include <string.h>
57 
58 #include <isc/magic.h>
59 #include <isc/mem.h>
60 #include <isc/netaddr.h>
61 #include <isc/sockaddr.h>
62 #include <isc/util.h>
63 
64 #include <irs/netdb.h>
65 #include <irs/resconf.h>
66 
67 #define IRS_RESCONF_MAGIC		ISC_MAGIC('R', 'E', 'S', 'c')
68 #define IRS_RESCONF_VALID(c)		ISC_MAGIC_VALID(c, IRS_RESCONF_MAGIC)
69 
70 /*!
71  * protocol constants
72  */
73 
74 #if ! defined(NS_INADDRSZ)
75 #define NS_INADDRSZ	 4
76 #endif
77 
78 #if ! defined(NS_IN6ADDRSZ)
79 #define NS_IN6ADDRSZ	16
80 #endif
81 
82 /*!
83  * resolv.conf parameters
84  */
85 
86 #define RESCONFMAXNAMESERVERS 3		/*%< max 3 "nameserver" entries */
87 #define RESCONFMAXSEARCH 8		/*%< max 8 domains in "search" entry */
88 #define RESCONFMAXLINELEN 256		/*%< max size of a line */
89 #define RESCONFMAXSORTLIST 10		/*%< max 10 */
90 
91 /*!
92  * configuration data structure
93  */
94 
95 struct irs_resconf {
96 	/*
97 	 * The configuration data is a thread-specific object, and does not
98 	 * need to be locked.
99 	 */
100 	unsigned int		magic;
101 	isc_mem_t		*mctx;
102 
103 	isc_sockaddrlist_t	nameservers;
104 	unsigned int		numns; /*%< number of configured servers */
105 
106 	char	       		*domainname;
107 	char 	       		*search[RESCONFMAXSEARCH];
108 	isc_uint8_t		searchnxt; /*%< index for next free slot */
109 
110 	irs_resconf_searchlist_t searchlist;
111 
112 	struct {
113 		isc_netaddr_t	addr;
114 		/*% mask has a non-zero 'family' if set */
115 		isc_netaddr_t	mask;
116 	} sortlist[RESCONFMAXSORTLIST];
117 	isc_uint8_t		sortlistnxt;
118 
119 	/*%< non-zero if 'options debug' set */
120 	isc_uint8_t		resdebug;
121 	/*%< set to n in 'options ndots:n' */
122 	isc_uint8_t		ndots;
123 };
124 
125 static isc_result_t
126 resconf_parsenameserver(irs_resconf_t *conf,  FILE *fp);
127 static isc_result_t
128 resconf_parsedomain(irs_resconf_t *conf,  FILE *fp);
129 static isc_result_t
130 resconf_parsesearch(irs_resconf_t *conf,  FILE *fp);
131 static isc_result_t
132 resconf_parsesortlist(irs_resconf_t *conf,  FILE *fp);
133 static isc_result_t
134 resconf_parseoption(irs_resconf_t *ctx,  FILE *fp);
135 
136 /*!
137  * Eat characters from FP until EOL or EOF. Returns EOF or '\n'
138  */
139 static int
eatline(FILE * fp)140 eatline(FILE *fp) {
141 	int ch;
142 
143 	ch = fgetc(fp);
144 	while (ch != '\n' && ch != EOF)
145 		ch = fgetc(fp);
146 
147 	return (ch);
148 }
149 
150 /*!
151  * Eats white space up to next newline or non-whitespace character (of
152  * EOF). Returns the last character read. Comments are considered white
153  * space.
154  */
155 static int
eatwhite(FILE * fp)156 eatwhite(FILE *fp) {
157 	int ch;
158 
159 	ch = fgetc(fp);
160 	while (ch != '\n' && ch != EOF && isspace((unsigned char)ch))
161 		ch = fgetc(fp);
162 
163 	if (ch == ';' || ch == '#')
164 		ch = eatline(fp);
165 
166 	return (ch);
167 }
168 
169 /*!
170  * Skip over any leading whitespace and then read in the next sequence of
171  * non-whitespace characters. In this context newline is not considered
172  * whitespace. Returns EOF on end-of-file, or the character
173  * that caused the reading to stop.
174  */
175 static int
getword(FILE * fp,char * buffer,size_t size)176 getword(FILE *fp, char *buffer, size_t size) {
177 	int ch;
178 	char *p = buffer;
179 
180 	REQUIRE(buffer != NULL);
181 	REQUIRE(size > 0U);
182 
183 	*p = '\0';
184 
185 	ch = eatwhite(fp);
186 
187 	if (ch == EOF)
188 		return (EOF);
189 
190 	do {
191 		*p = '\0';
192 
193 		if (ch == EOF || isspace((unsigned char)ch))
194 			break;
195 		else if ((size_t) (p - buffer) == size - 1)
196 			return (EOF);	/* Not enough space. */
197 
198 		*p++ = (char)ch;
199 		ch = fgetc(fp);
200 	} while (1);
201 
202 	return (ch);
203 }
204 
205 static isc_result_t
add_server(isc_mem_t * mctx,const char * address_str,isc_sockaddrlist_t * nameservers)206 add_server(isc_mem_t *mctx, const char *address_str,
207 	   isc_sockaddrlist_t *nameservers)
208 {
209 	int error;
210 	isc_sockaddr_t *address = NULL;
211 	struct addrinfo hints, *res;
212 	isc_result_t result = ISC_R_SUCCESS;
213 
214 	res = NULL;
215 	memset(&hints, 0, sizeof(hints));
216 	hints.ai_family = AF_UNSPEC;
217 	hints.ai_socktype = SOCK_DGRAM;
218 	hints.ai_protocol = IPPROTO_UDP;
219 	hints.ai_flags = AI_NUMERICHOST;
220 	error = getaddrinfo(address_str, "53", &hints, &res);
221 	if (error != 0)
222 		return (ISC_R_BADADDRESSFORM);
223 
224 	/* XXX: special case: treat all-0 IPv4 address as loopback */
225 	if (res->ai_family == AF_INET) {
226 		struct in_addr *v4;
227 		unsigned char zeroaddress[] = {0, 0, 0, 0};
228 		unsigned char loopaddress[] = {127, 0, 0, 1};
229 
230 		v4 = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
231 		if (memcmp(v4, zeroaddress, 4) == 0)
232 			memmove(v4, loopaddress, 4);
233 	}
234 
235 	address = isc_mem_get(mctx, sizeof(*address));
236 	if (address == NULL) {
237 		result = ISC_R_NOMEMORY;
238 		goto cleanup;
239 	}
240 	if (res->ai_addrlen > sizeof(address->type)) {
241 		isc_mem_put(mctx, address, sizeof(*address));
242 		result = ISC_R_RANGE;
243 		goto cleanup;
244 	}
245 	address->length = (unsigned int)res->ai_addrlen;
246 	memmove(&address->type.ss, res->ai_addr, res->ai_addrlen);
247 	ISC_LINK_INIT(address, link);
248 	ISC_LIST_APPEND(*nameservers, address, link);
249 
250   cleanup:
251 	freeaddrinfo(res);
252 
253 	return (result);
254 }
255 
256 static isc_result_t
create_addr(const char * buffer,isc_netaddr_t * addr,int convert_zero)257 create_addr(const char *buffer, isc_netaddr_t *addr, int convert_zero) {
258 	struct in_addr v4;
259 	struct in6_addr v6;
260 
261 	if (inet_aton(buffer, &v4) == 1) {
262 		if (convert_zero) {
263 			unsigned char zeroaddress[] = {0, 0, 0, 0};
264 			unsigned char loopaddress[] = {127, 0, 0, 1};
265 			if (memcmp(&v4, zeroaddress, 4) == 0)
266 				memmove(&v4, loopaddress, 4);
267 		}
268 		addr->family = AF_INET;
269 		memmove(&addr->type.in, &v4, NS_INADDRSZ);
270 		addr->zone = 0;
271 	} else if (inet_pton(AF_INET6, buffer, &v6) == 1) {
272 		addr->family = AF_INET6;
273 		memmove(&addr->type.in6, &v6, NS_IN6ADDRSZ);
274 		addr->zone = 0;
275 	} else
276 		return (ISC_R_BADADDRESSFORM); /* Unrecognised format. */
277 
278 	return (ISC_R_SUCCESS);
279 }
280 
281 static isc_result_t
resconf_parsenameserver(irs_resconf_t * conf,FILE * fp)282 resconf_parsenameserver(irs_resconf_t *conf,  FILE *fp) {
283 	char word[RESCONFMAXLINELEN];
284 	int cp;
285 	isc_result_t result;
286 
287 	if (conf->numns == RESCONFMAXNAMESERVERS)
288 		return (ISC_R_SUCCESS);
289 
290 	cp = getword(fp, word, sizeof(word));
291 	if (strlen(word) == 0U)
292 		return (ISC_R_UNEXPECTEDEND); /* Nothing on line. */
293 	else if (cp == ' ' || cp == '\t')
294 		cp = eatwhite(fp);
295 
296 	if (cp != EOF && cp != '\n')
297 		return (ISC_R_UNEXPECTEDTOKEN); /* Extra junk on line. */
298 
299 	result = add_server(conf->mctx, word, &conf->nameservers);
300 	if (result != ISC_R_SUCCESS)
301 		return (result);
302 	conf->numns++;
303 
304 	return (ISC_R_SUCCESS);
305 }
306 
307 static isc_result_t
resconf_parsedomain(irs_resconf_t * conf,FILE * fp)308 resconf_parsedomain(irs_resconf_t *conf,  FILE *fp) {
309 	char word[RESCONFMAXLINELEN];
310 	int res, i;
311 
312 	res = getword(fp, word, sizeof(word));
313 	if (strlen(word) == 0U)
314 		return (ISC_R_UNEXPECTEDEND); /* Nothing else on line. */
315 	else if (res == ' ' || res == '\t')
316 		res = eatwhite(fp);
317 
318 	if (res != EOF && res != '\n')
319 		return (ISC_R_UNEXPECTEDTOKEN); /* Extra junk on line. */
320 
321 	if (conf->domainname != NULL)
322 		isc_mem_free(conf->mctx, conf->domainname);
323 
324 	/*
325 	 * Search and domain are mutually exclusive.
326 	 */
327 	for (i = 0; i < RESCONFMAXSEARCH; i++) {
328 		if (conf->search[i] != NULL) {
329 			isc_mem_free(conf->mctx, conf->search[i]);
330 			conf->search[i] = NULL;
331 		}
332 	}
333 	conf->searchnxt = 0;
334 
335 	conf->domainname = isc_mem_strdup(conf->mctx, word);
336 	if (conf->domainname == NULL)
337 		return (ISC_R_NOMEMORY);
338 
339 	return (ISC_R_SUCCESS);
340 }
341 
342 static isc_result_t
resconf_parsesearch(irs_resconf_t * conf,FILE * fp)343 resconf_parsesearch(irs_resconf_t *conf,  FILE *fp) {
344 	int idx, delim;
345 	char word[RESCONFMAXLINELEN];
346 
347 	if (conf->domainname != NULL) {
348 		/*
349 		 * Search and domain are mutually exclusive.
350 		 */
351 		isc_mem_free(conf->mctx, conf->domainname);
352 		conf->domainname = NULL;
353 	}
354 
355 	/*
356 	 * Remove any previous search definitions.
357 	 */
358 	for (idx = 0; idx < RESCONFMAXSEARCH; idx++) {
359 		if (conf->search[idx] != NULL) {
360 			isc_mem_free(conf->mctx, conf->search[idx]);
361 			conf->search[idx] = NULL;
362 		}
363 	}
364 	conf->searchnxt = 0;
365 
366 	delim = getword(fp, word, sizeof(word));
367 	if (strlen(word) == 0U)
368 		return (ISC_R_UNEXPECTEDEND); /* Nothing else on line. */
369 
370 	idx = 0;
371 	while (strlen(word) > 0U) {
372 		if (conf->searchnxt == RESCONFMAXSEARCH)
373 			goto ignore; /* Too many domains. */
374 
375 		conf->search[idx] = isc_mem_strdup(conf->mctx, word);
376 		if (conf->search[idx] == NULL)
377 			return (ISC_R_NOMEMORY);
378 		idx++;
379 		conf->searchnxt++;
380 
381 	ignore:
382 		if (delim == EOF || delim == '\n')
383 			break;
384 		else
385 			delim = getword(fp, word, sizeof(word));
386 	}
387 
388 	return (ISC_R_SUCCESS);
389 }
390 
391 static isc_result_t
resconf_parsesortlist(irs_resconf_t * conf,FILE * fp)392 resconf_parsesortlist(irs_resconf_t *conf,  FILE *fp) {
393 	int delim, res, idx;
394 	char word[RESCONFMAXLINELEN];
395 	char *p;
396 
397 	delim = getword(fp, word, sizeof(word));
398 	if (strlen(word) == 0U)
399 		return (ISC_R_UNEXPECTEDEND); /* Empty line after keyword. */
400 
401 	while (strlen(word) > 0U) {
402 		if (conf->sortlistnxt == RESCONFMAXSORTLIST)
403 			return (ISC_R_QUOTA); /* Too many values. */
404 
405 		p = strchr(word, '/');
406 		if (p != NULL)
407 			*p++ = '\0';
408 
409 		idx = conf->sortlistnxt;
410 		res = create_addr(word, &conf->sortlist[idx].addr, 1);
411 		if (res != ISC_R_SUCCESS)
412 			return (res);
413 
414 		if (p != NULL) {
415 			res = create_addr(p, &conf->sortlist[idx].mask, 0);
416 			if (res != ISC_R_SUCCESS)
417 				return (res);
418 		} else {
419 			/*
420 			 * Make up a mask. (XXX: is this correct?)
421 			 */
422 			conf->sortlist[idx].mask = conf->sortlist[idx].addr;
423 			memset(&conf->sortlist[idx].mask.type, 0xff,
424 			       sizeof(conf->sortlist[idx].mask.type));
425 		}
426 
427 		conf->sortlistnxt++;
428 
429 		if (delim == EOF || delim == '\n')
430 			break;
431 		else
432 			delim = getword(fp, word, sizeof(word));
433 	}
434 
435 	return (ISC_R_SUCCESS);
436 }
437 
438 static isc_result_t
resconf_parseoption(irs_resconf_t * conf,FILE * fp)439 resconf_parseoption(irs_resconf_t *conf,  FILE *fp) {
440 	int delim;
441 	long ndots;
442 	char *p;
443 	char word[RESCONFMAXLINELEN];
444 
445 	delim = getword(fp, word, sizeof(word));
446 	if (strlen(word) == 0U)
447 		return (ISC_R_UNEXPECTEDEND); /* Empty line after keyword. */
448 
449 	while (strlen(word) > 0U) {
450 		if (strcmp("debug", word) == 0) {
451 			conf->resdebug = 1;
452 		} else if (strncmp("ndots:", word, 6) == 0) {
453 			ndots = strtol(word + 6, &p, 10);
454 			if (*p != '\0') /* Bad string. */
455 				return (ISC_R_UNEXPECTEDTOKEN);
456 			if (ndots < 0 || ndots > 0xff) /* Out of range. */
457 				return (ISC_R_RANGE);
458 			conf->ndots = (isc_uint8_t)ndots;
459 		}
460 
461 		if (delim == EOF || delim == '\n')
462 			break;
463 		else
464 			delim = getword(fp, word, sizeof(word));
465 	}
466 
467 	return (ISC_R_SUCCESS);
468 }
469 
470 static isc_result_t
add_search(irs_resconf_t * conf,char * domain)471 add_search(irs_resconf_t *conf, char *domain) {
472 	irs_resconf_search_t *entry;
473 
474 	entry = isc_mem_get(conf->mctx, sizeof(*entry));
475 	if (entry == NULL)
476 		return (ISC_R_NOMEMORY);
477 
478 	entry->domain = domain;
479 	ISC_LINK_INIT(entry, link);
480 	ISC_LIST_APPEND(conf->searchlist, entry, link);
481 
482 	return (ISC_R_SUCCESS);
483 }
484 
485 /*% parses a file and fills in the data structure. */
486 isc_result_t
irs_resconf_load(isc_mem_t * mctx,const char * filename,irs_resconf_t ** confp)487 irs_resconf_load(isc_mem_t *mctx, const char *filename, irs_resconf_t **confp)
488 {
489 	FILE *fp = NULL;
490 	char word[256];
491 	isc_result_t rval, ret = ISC_R_SUCCESS;
492 	irs_resconf_t *conf;
493 	int i, stopchar;
494 
495 	REQUIRE(mctx != NULL);
496 	REQUIRE(filename != NULL);
497 	REQUIRE(strlen(filename) > 0U);
498 	REQUIRE(confp != NULL && *confp == NULL);
499 
500 	conf = isc_mem_get(mctx, sizeof(*conf));
501 	if (conf == NULL)
502 		return (ISC_R_NOMEMORY);
503 
504 	conf->mctx = mctx;
505 	ISC_LIST_INIT(conf->nameservers);
506 	conf->numns = 0;
507 	conf->domainname = NULL;
508 	conf->searchnxt = 0;
509 	conf->resdebug = 0;
510 	conf->ndots = 1;
511 	for (i = 0; i < RESCONFMAXSEARCH; i++)
512 		conf->search[i] = NULL;
513 
514 	errno = 0;
515 	if ((fp = fopen(filename, "r")) != NULL) {
516 		do {
517 			stopchar = getword(fp, word, sizeof(word));
518 			if (stopchar == EOF) {
519 				rval = ISC_R_SUCCESS;
520 				POST(rval);
521 				break;
522 			}
523 
524 			if (strlen(word) == 0U)
525 				rval = ISC_R_SUCCESS;
526 			else if (strcmp(word, "nameserver") == 0)
527 				rval = resconf_parsenameserver(conf, fp);
528 			else if (strcmp(word, "domain") == 0)
529 				rval = resconf_parsedomain(conf, fp);
530 			else if (strcmp(word, "search") == 0)
531 				rval = resconf_parsesearch(conf, fp);
532 			else if (strcmp(word, "sortlist") == 0)
533 				rval = resconf_parsesortlist(conf, fp);
534 			else if (strcmp(word, "options") == 0)
535 				rval = resconf_parseoption(conf, fp);
536 			else {
537 				/* unrecognised word. Ignore entire line */
538 				rval = ISC_R_SUCCESS;
539 				stopchar = eatline(fp);
540 				if (stopchar == EOF) {
541 					break;
542 				}
543 			}
544 			if (ret == ISC_R_SUCCESS && rval != ISC_R_SUCCESS)
545 				ret = rval;
546 		} while (1);
547 
548 		fclose(fp);
549 	} else {
550 		switch (errno) {
551 		case ENOENT:
552 			break;
553 		default:
554 			isc_mem_put(mctx, conf, sizeof(*conf));
555 			return (ISC_R_INVALIDFILE);
556 		}
557 	}
558 
559 	/* If we don't find a nameserver fall back to localhost */
560 	if (conf->numns == 0) {
561 		INSIST(ISC_LIST_EMPTY(conf->nameservers));
562 
563 		/* XXX: should we catch errors? */
564 		(void)add_server(conf->mctx, "127.0.0.1", &conf->nameservers);
565 		(void)add_server(conf->mctx, "::1", &conf->nameservers);
566 	}
567 
568 	/*
569 	 * Construct unified search list from domain or configured
570 	 * search list
571 	 */
572 	ISC_LIST_INIT(conf->searchlist);
573 	if (conf->domainname != NULL) {
574 		ret = add_search(conf, conf->domainname);
575 	} else if (conf->searchnxt > 0) {
576 		for (i = 0; i < conf->searchnxt; i++) {
577 			ret = add_search(conf, conf->search[i]);
578 			if (ret != ISC_R_SUCCESS)
579 				break;
580 		}
581 	}
582 
583 	conf->magic = IRS_RESCONF_MAGIC;
584 
585 	if (ret != ISC_R_SUCCESS)
586 		irs_resconf_destroy(&conf);
587 	else {
588 		if (fp == NULL)
589 			ret = ISC_R_FILENOTFOUND;
590 		*confp = conf;
591 	}
592 
593 	return (ret);
594 }
595 
596 void
irs_resconf_destroy(irs_resconf_t ** confp)597 irs_resconf_destroy(irs_resconf_t **confp) {
598 	irs_resconf_t *conf;
599 	isc_sockaddr_t *address;
600 	irs_resconf_search_t *searchentry;
601 	int i;
602 
603 	REQUIRE(confp != NULL);
604 	conf = *confp;
605 	REQUIRE(IRS_RESCONF_VALID(conf));
606 
607 	while ((searchentry = ISC_LIST_HEAD(conf->searchlist)) != NULL) {
608 		ISC_LIST_UNLINK(conf->searchlist, searchentry, link);
609 		isc_mem_put(conf->mctx, searchentry, sizeof(*searchentry));
610 	}
611 
612 	while ((address = ISC_LIST_HEAD(conf->nameservers)) != NULL) {
613 		ISC_LIST_UNLINK(conf->nameservers, address, link);
614 		isc_mem_put(conf->mctx, address, sizeof(*address));
615 	}
616 
617 	if (conf->domainname != NULL)
618 		isc_mem_free(conf->mctx, conf->domainname);
619 
620 	for (i = 0; i < RESCONFMAXSEARCH; i++) {
621 		if (conf->search[i] != NULL)
622 			isc_mem_free(conf->mctx, conf->search[i]);
623 	}
624 
625 	isc_mem_put(conf->mctx, conf, sizeof(*conf));
626 
627 	*confp = NULL;
628 }
629 
630 isc_sockaddrlist_t *
irs_resconf_getnameservers(irs_resconf_t * conf)631 irs_resconf_getnameservers(irs_resconf_t *conf) {
632 	REQUIRE(IRS_RESCONF_VALID(conf));
633 
634 	return (&conf->nameservers);
635 }
636 
637 irs_resconf_searchlist_t *
irs_resconf_getsearchlist(irs_resconf_t * conf)638 irs_resconf_getsearchlist(irs_resconf_t *conf) {
639 	REQUIRE(IRS_RESCONF_VALID(conf));
640 
641 	return (&conf->searchlist);
642 }
643 
644 unsigned int
irs_resconf_getndots(irs_resconf_t * conf)645 irs_resconf_getndots(irs_resconf_t *conf) {
646 	REQUIRE(IRS_RESCONF_VALID(conf));
647 
648 	return ((unsigned int)conf->ndots);
649 }
650