1/*
2 * Copyright (c) 1988, 1993
3 * The Regents of the University of California. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 4. Neither the name of the University nor the names of its contributors
14 * may be used to endorse or promote products derived from this software
15 * without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30/*
31 * Portions Copyright (c) 1993 by Digital Equipment Corporation.
32 *
33 * Permission to use, copy, modify, and distribute this software for any
34 * purpose with or without fee is hereby granted, provided that the above
35 * copyright notice and this permission notice appear in all copies, and that
36 * the name of Digital Equipment Corporation not be used in advertising or
37 * publicity pertaining to distribution of the document or software without
38 * specific, written prior permission.
39 *
40 * THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL
41 * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
42 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DIGITAL EQUIPMENT
43 * CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
44 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
45 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
46 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
47 * SOFTWARE.
48 */
49
50/*
51 * Portions Copyright (c) 1996-1999 by Internet Software Consortium.
52 *
53 * Permission to use, copy, modify, and distribute this software for any
54 * purpose with or without fee is hereby granted, provided that the above
55 * copyright notice and this permission notice appear in all copies.
56 *
57 * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
58 * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
59 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
60 * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
61 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
62 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
63 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
64 * SOFTWARE.
65 */
66
67#include <assert.h>
68#include <sys/types.h>
69#include <sys/param.h>
70#include <netinet/in.h>
71#include <arpa/inet.h>
72#include <arpa/nameser.h>
73#include <ctype.h>
74#include <errno.h>
75#include <netdb.h>
76#include <resolv.h>
77#include <stdio.h>
78#include <stdlib.h>
79#include <string.h>
80
81/* Options. Leave them on. */
82/* #undef DEBUG */
83
84#if PACKETSZ > 65536
85#define MAXPACKET PACKETSZ
86#else
87#define MAXPACKET 65536
88#endif
89
90#define QUERYSIZE (HFIXEDSZ + QFIXEDSZ + MAXCDNAME + 1)
91
92static int
93__libc_res_nquerydomain(res_state statp, const char *name, const char *domain,
94 int class, int type, u_char *answer, int anslen,
95 u_char **answerp, u_char **answerp2, int *nanswerp2,
96 int *resplen2, int *answerp2_malloced);
97
98/*
99 * Formulate a normal query, send, and await answer.
100 * Returned answer is placed in supplied buffer "answer".
101 * Perform preliminary check of answer, returning success only
102 * if no error is indicated and the answer count is nonzero.
103 * Return the size of the response on success, -1 on error.
104 * Error number is left in H_ERRNO.
105 *
106 * Caller must parse answer and determine whether it answers the question.
107 */
108int
109__libc_res_nquery(res_state statp,
110 const char *name, /* domain name */
111 int class, int type, /* class and type of query */
112 u_char *answer, /* buffer to put answer */
113 int anslen, /* size of answer buffer */
114 u_char **answerp, /* if buffer needs to be enlarged */
115 u_char **answerp2,
116 int *nanswerp2,
117 int *resplen2,
118 int *answerp2_malloced)
119{
120 HEADER *hp = (HEADER *) answer;
121 HEADER *hp2;
122 int n, use_malloc = 0;
123 u_int oflags = statp->_flags;
124
125 size_t bufsize = (type == T_QUERY_A_AND_AAAA ? 2 : 1) * QUERYSIZE;
126 u_char *buf = alloca (bufsize);
127 u_char *query1 = buf;
128 int nquery1 = -1;
129 u_char *query2 = NULL;
130 int nquery2 = 0;
131
132 again:
133 hp->rcode = NOERROR; /* default */
134
135#ifdef DEBUG
136 if (statp->options & RES_DEBUG)
137 printf(";; res_query(%s, %d, %d)\n", name, class, type);
138#endif
139
140 if (type == T_QUERY_A_AND_AAAA)
141 {
142 n = res_nmkquery(statp, QUERY, name, class, T_A, NULL, 0, NULL,
143 query1, bufsize);
144 if (n > 0)
145 {
146 if ((oflags & RES_F_EDNS0ERR) == 0
147 && (statp->options & (RES_USE_EDNS0|RES_USE_DNSSEC)) != 0)
148 {
149 n = __res_nopt(statp, n, query1, bufsize, anslen / 2);
150 if (n < 0)
151 goto unspec_nomem;
152 }
153
154 nquery1 = n;
155 /* Align the buffer. */
156 int npad = ((nquery1 + __alignof__ (HEADER) - 1)
157 & ~(__alignof__ (HEADER) - 1)) - nquery1;
158 if (n > bufsize - npad)
159 {
160 n = -1;
161 goto unspec_nomem;
162 }
163 int nused = n + npad;
164 query2 = buf + nused;
165 n = res_nmkquery(statp, QUERY, name, class, T_AAAA, NULL, 0,
166 NULL, query2, bufsize - nused);
167 if (n > 0
168 && (oflags & RES_F_EDNS0ERR) == 0
169 && (statp->options & (RES_USE_EDNS0|RES_USE_DNSSEC)) != 0)
170 n = __res_nopt(statp, n, query2, bufsize - nused - n,
171 anslen / 2);
172 nquery2 = n;
173 }
174
175 unspec_nomem:;
176 }
177 else
178 {
179 n = res_nmkquery(statp, QUERY, name, class, type, NULL, 0, NULL,
180 query1, bufsize);
181
182 if (n > 0
183 && (oflags & RES_F_EDNS0ERR) == 0
184 && (statp->options & (RES_USE_EDNS0|RES_USE_DNSSEC)) != 0)
185 n = __res_nopt(statp, n, query1, bufsize, anslen);
186
187 nquery1 = n;
188 }
189
190 if (__builtin_expect (n <= 0, 0) && !use_malloc) {
191 /* Retry just in case res_nmkquery failed because of too
192 short buffer. Shouldn't happen. */
193 bufsize = (type == T_QUERY_A_AND_AAAA ? 2 : 1) * MAXPACKET;
194 buf = malloc (bufsize);
195 if (buf != NULL) {
196 query1 = buf;
197 use_malloc = 1;
198 goto again;
199 }
200 }
201 if (__glibc_unlikely (n <= 0)) {
202 /* If the query choked with EDNS0, retry without EDNS0. */
203 if ((statp->options & (RES_USE_EDNS0|RES_USE_DNSSEC)) != 0
204 && ((oflags ^ statp->_flags) & RES_F_EDNS0ERR) != 0) {
205 statp->_flags |= RES_F_EDNS0ERR;
206#ifdef DEBUG
207 if (statp->options & RES_DEBUG)
208 printf(";; res_nquery: retry without EDNS0\n");
209#endif
210 goto again;
211 }
212#ifdef DEBUG
213 if (statp->options & RES_DEBUG)
214 printf(";; res_query: mkquery failed\n");
215#endif
216 RES_SET_H_ERRNO(statp, NO_RECOVERY);
217 if (use_malloc)
218 free (buf);
219 return (n);
220 }
221 assert (answerp == NULL || (void *) *answerp == (void *) answer);
222 n = __libc_res_nsend(statp, query1, nquery1, query2, nquery2, answer,
223 anslen, answerp, answerp2, nanswerp2, resplen2,
224 answerp2_malloced);
225 if (use_malloc)
226 free (buf);
227 if (n < 0) {
228#ifdef DEBUG
229 if (statp->options & RES_DEBUG)
230 printf(";; res_query: send error\n");
231#endif
232 RES_SET_H_ERRNO(statp, TRY_AGAIN);
233 return (n);
234 }
235
236 if (answerp != NULL)
237 /* __libc_res_nsend might have reallocated the buffer. */
238 hp = (HEADER *) *answerp;
239
240 /* We simplify the following tests by assigning HP to HP2 or
241 vice versa. It is easy to verify that this is the same as
242 ignoring all tests of HP or HP2. */
243 if (answerp2 == NULL || *resplen2 < (int) sizeof (HEADER))
244 {
245 hp2 = hp;
246 }
247 else
248 {
249 hp2 = (HEADER *) *answerp2;
250 if (n < (int) sizeof (HEADER))
251 {
252 hp = hp2;
253 }
254 }
255
256 /* Make sure both hp and hp2 are defined */
257 assert((hp != NULL) && (hp2 != NULL));
258
259 if ((hp->rcode != NOERROR || ntohs(hp->ancount) == 0)
260 && (hp2->rcode != NOERROR || ntohs(hp2->ancount) == 0)) {
261#ifdef DEBUG
262 if (statp->options & RES_DEBUG) {
263 printf(";; rcode = %d, ancount=%d\n", hp->rcode,
264 ntohs(hp->ancount));
265 if (hp != hp2)
266 printf(";; rcode2 = %d, ancount2=%d\n", hp2->rcode,
267 ntohs(hp2->ancount));
268 }
269#endif
270 switch (hp->rcode == NOERROR ? hp2->rcode : hp->rcode) {
271 case NXDOMAIN:
272 if ((hp->rcode == NOERROR && ntohs (hp->ancount) != 0)
273 || (hp2->rcode == NOERROR
274 && ntohs (hp2->ancount) != 0))
275 goto success;
276 RES_SET_H_ERRNO(statp, HOST_NOT_FOUND);
277 break;
278 case SERVFAIL:
279 RES_SET_H_ERRNO(statp, TRY_AGAIN);
280 break;
281 case NOERROR:
282 if (ntohs (hp->ancount) != 0
283 || ntohs (hp2->ancount) != 0)
284 goto success;
285 RES_SET_H_ERRNO(statp, NO_DATA);
286 break;
287 case FORMERR:
288 case NOTIMP:
289 /* Servers must not reply to AAAA queries with
290 NOTIMP etc but some of them do. */
291 if ((hp->rcode == NOERROR && ntohs (hp->ancount) != 0)
292 || (hp2->rcode == NOERROR
293 && ntohs (hp2->ancount) != 0))
294 goto success;
295 /* FALLTHROUGH */
296 case REFUSED:
297 default:
298 RES_SET_H_ERRNO(statp, NO_RECOVERY);
299 break;
300 }
301 return (-1);
302 }
303 success:
304 return (n);
305}
306libresolv_hidden_def (__libc_res_nquery)
307
308int
309res_nquery(res_state statp,
310 const char *name, /* domain name */
311 int class, int type, /* class and type of query */
312 u_char *answer, /* buffer to put answer */
313 int anslen) /* size of answer buffer */
314{
315 return __libc_res_nquery(statp, name, class, type, answer, anslen,
316 NULL, NULL, NULL, NULL, NULL);
317}
318libresolv_hidden_def (res_nquery)
319
320/*
321 * Formulate a normal query, send, and retrieve answer in supplied buffer.
322 * Return the size of the response on success, -1 on error.
323 * If enabled, implement search rules until answer or unrecoverable failure
324 * is detected. Error code, if any, is left in H_ERRNO.
325 */
326int
327__libc_res_nsearch(res_state statp,
328 const char *name, /* domain name */
329 int class, int type, /* class and type of query */
330 u_char *answer, /* buffer to put answer */
331 int anslen, /* size of answer */
332 u_char **answerp,
333 u_char **answerp2,
334 int *nanswerp2,
335 int *resplen2,
336 int *answerp2_malloced)
337{
338 const char *cp, * const *domain;
339 HEADER *hp = (HEADER *) answer;
340 char tmp[NS_MAXDNAME];
341 u_int dots;
342 int trailing_dot, ret, saved_herrno;
343 int got_nodata = 0, got_servfail = 0, root_on_list = 0;
344 int tried_as_is = 0;
345 int searched = 0;
346
347 __set_errno (0);
348 RES_SET_H_ERRNO(statp, HOST_NOT_FOUND); /* True if we never query. */
349
350 dots = 0;
351 for (cp = name; *cp != '\0'; cp++)
352 dots += (*cp == '.');
353 trailing_dot = 0;
354 if (cp > name && *--cp == '.')
355 trailing_dot++;
356
357 /* If there aren't any dots, it could be a user-level alias. */
358 if (!dots && (cp = res_hostalias(statp, name, tmp, sizeof tmp))!= NULL)
359 return (__libc_res_nquery(statp, cp, class, type, answer,
360 anslen, answerp, answerp2,
361 nanswerp2, resplen2, answerp2_malloced));
362
363#ifdef DEBUG
364 if (statp->options & RES_DEBUG)
365 printf("dots=%d, statp->ndots=%d, trailing_dot=%d, name=%s\n",
366 (int)dots,(int)statp->ndots,(int)trailing_dot,name);
367#endif
368
369 /*
370 * If there are enough dots in the name, let's just give it a
371 * try 'as is'. The threshold can be set with the "ndots" option.
372 * Also, query 'as is', if there is a trailing dot in the name.
373 */
374 saved_herrno = -1;
375 if (dots >= statp->ndots || trailing_dot) {
376 ret = __libc_res_nquerydomain(statp, name, NULL, class, type,
377 answer, anslen, answerp,
378 answerp2, nanswerp2, resplen2,
379 answerp2_malloced);
380 if (ret > 0 || trailing_dot
381 /* If the second response is valid then we use that. */
382 || (ret == 0 && resplen2 != NULL && *resplen2 > 0))
383 return (ret);
384 saved_herrno = h_errno;
385 tried_as_is++;
386 if (answerp && *answerp != answer) {
387 answer = *answerp;
388 anslen = MAXPACKET;
389 }
390 if (answerp2 && *answerp2_malloced)
391 {
392 free (*answerp2);
393 *answerp2 = NULL;
394 *nanswerp2 = 0;
395 *answerp2_malloced = 0;
396 }
397 }
398
399 /*
400 * We do at least one level of search if
401 * - there is no dot and RES_DEFNAME is set, or
402 * - there is at least one dot, there is no trailing dot,
403 * and RES_DNSRCH is set.
404 */
405 if ((!dots && (statp->options & RES_DEFNAMES) != 0) ||
406 (dots && !trailing_dot && (statp->options & RES_DNSRCH) != 0)) {
407 int done = 0;
408
409 for (domain = (const char * const *)statp->dnsrch;
410 *domain && !done;
411 domain++) {
412 const char *dname = domain[0];
413 searched = 1;
414
415 /* __libc_res_nquerydoman concatenates name
416 with dname with a "." in between. If we
417 pass it in dname the "." we got from the
418 configured default search path, we'll end
419 up with "name..", which won't resolve.
420 OTOH, passing it "" will result in "name.",
421 which has the intended effect for both
422 possible representations of the root
423 domain. */
424 if (dname[0] == '.')
425 dname++;
426 if (dname[0] == '\0')
427 root_on_list++;
428
429 ret = __libc_res_nquerydomain(statp, name, dname,
430 class, type,
431 answer, anslen, answerp,
432 answerp2, nanswerp2,
433 resplen2, answerp2_malloced);
434 if (ret > 0 || (ret == 0 && resplen2 != NULL
435 && *resplen2 > 0))
436 return (ret);
437
438 if (answerp && *answerp != answer) {
439 answer = *answerp;
440 anslen = MAXPACKET;
441 }
442 if (answerp2 && *answerp2_malloced)
443 {
444 free (*answerp2);
445 *answerp2 = NULL;
446 *nanswerp2 = 0;
447 *answerp2_malloced = 0;
448 }
449
450 /*
451 * If no server present, give up.
452 * If name isn't found in this domain,
453 * keep trying higher domains in the search list
454 * (if that's enabled).
455 * On a NO_DATA error, keep trying, otherwise
456 * a wildcard entry of another type could keep us
457 * from finding this entry higher in the domain.
458 * If we get some other error (negative answer or
459 * server failure), then stop searching up,
460 * but try the input name below in case it's
461 * fully-qualified.
462 */
463 if (errno == ECONNREFUSED) {
464 RES_SET_H_ERRNO(statp, TRY_AGAIN);
465 return (-1);
466 }
467
468 switch (statp->res_h_errno) {
469 case NO_DATA:
470 got_nodata++;
471 /* FALLTHROUGH */
472 case HOST_NOT_FOUND:
473 /* keep trying */
474 break;
475 case TRY_AGAIN:
476 if (hp->rcode == SERVFAIL) {
477 /* try next search element, if any */
478 got_servfail++;
479 break;
480 }
481 /* FALLTHROUGH */
482 default:
483 /* anything else implies that we're done */
484 done++;
485 }
486
487 /* if we got here for some reason other than DNSRCH,
488 * we only wanted one iteration of the loop, so stop.
489 */
490 if ((statp->options & RES_DNSRCH) == 0)
491 done++;
492 }
493 }
494
495 /*
496 * If the query has not already been tried as is then try it
497 * unless RES_NOTLDQUERY is set and there were no dots.
498 */
499 if ((dots || !searched || (statp->options & RES_NOTLDQUERY) == 0)
500 && !(tried_as_is || root_on_list)) {
501 ret = __libc_res_nquerydomain(statp, name, NULL, class, type,
502 answer, anslen, answerp,
503 answerp2, nanswerp2, resplen2,
504 answerp2_malloced);
505 if (ret > 0 || (ret == 0 && resplen2 != NULL
506 && *resplen2 > 0))
507 return (ret);
508 }
509
510 /* if we got here, we didn't satisfy the search.
511 * if we did an initial full query, return that query's H_ERRNO
512 * (note that we wouldn't be here if that query had succeeded).
513 * else if we ever got a nodata, send that back as the reason.
514 * else send back meaningless H_ERRNO, that being the one from
515 * the last DNSRCH we did.
516 */
517 if (answerp2 && *answerp2_malloced)
518 {
519 free (*answerp2);
520 *answerp2 = NULL;
521 *nanswerp2 = 0;
522 *answerp2_malloced = 0;
523 }
524 if (saved_herrno != -1)
525 RES_SET_H_ERRNO(statp, saved_herrno);
526 else if (got_nodata)
527 RES_SET_H_ERRNO(statp, NO_DATA);
528 else if (got_servfail)
529 RES_SET_H_ERRNO(statp, TRY_AGAIN);
530 return (-1);
531}
532libresolv_hidden_def (__libc_res_nsearch)
533
534int
535res_nsearch(res_state statp,
536 const char *name, /* domain name */
537 int class, int type, /* class and type of query */
538 u_char *answer, /* buffer to put answer */
539 int anslen) /* size of answer */
540{
541 return __libc_res_nsearch(statp, name, class, type, answer,
542 anslen, NULL, NULL, NULL, NULL, NULL);
543}
544libresolv_hidden_def (res_nsearch)
545
546/*
547 * Perform a call on res_query on the concatenation of name and domain.
548 */
549static int
550__libc_res_nquerydomain(res_state statp,
551 const char *name,
552 const char *domain,
553 int class, int type, /* class and type of query */
554 u_char *answer, /* buffer to put answer */
555 int anslen, /* size of answer */
556 u_char **answerp,
557 u_char **answerp2,
558 int *nanswerp2,
559 int *resplen2,
560 int *answerp2_malloced)
561{
562 char nbuf[MAXDNAME];
563 const char *longname = nbuf;
564 size_t n, d;
565
566#ifdef DEBUG
567 if (statp->options & RES_DEBUG)
568 printf(";; res_nquerydomain(%s, %s, %d, %d)\n",
569 name, domain?domain:"<Nil>", class, type);
570#endif
571 if (domain == NULL) {
572 n = strlen(name);
573
574 /* Decrement N prior to checking it against MAXDNAME
575 so that we detect a wrap to SIZE_MAX and return
576 a reasonable error. */
577 n--;
578 if (n >= MAXDNAME - 1) {
579 RES_SET_H_ERRNO(statp, NO_RECOVERY);
580 return (-1);
581 }
582 longname = name;
583 } else {
584 n = strlen(name);
585 d = strlen(domain);
586 if (n + d + 1 >= MAXDNAME) {
587 RES_SET_H_ERRNO(statp, NO_RECOVERY);
588 return (-1);
589 }
590 sprintf(nbuf, "%s.%s", name, domain);
591 }
592 return (__libc_res_nquery(statp, longname, class, type, answer,
593 anslen, answerp, answerp2, nanswerp2,
594 resplen2, answerp2_malloced));
595}
596
597int
598res_nquerydomain(res_state statp,
599 const char *name,
600 const char *domain,
601 int class, int type, /* class and type of query */
602 u_char *answer, /* buffer to put answer */
603 int anslen) /* size of answer */
604{
605 return __libc_res_nquerydomain(statp, name, domain, class, type,
606 answer, anslen, NULL, NULL, NULL, NULL,
607 NULL);
608}
609libresolv_hidden_def (res_nquerydomain)
610
611const char *
612res_hostalias(const res_state statp, const char *name, char *dst, size_t siz) {
613 char *file, *cp1, *cp2;
614 char buf[BUFSIZ];
615 FILE *fp;
616
617 if (statp->options & RES_NOALIASES)
618 return (NULL);
619 file = getenv("HOSTALIASES");
620 if (file == NULL || (fp = fopen(file, "rce")) == NULL)
621 return (NULL);
622 setbuf(fp, NULL);
623 buf[sizeof(buf) - 1] = '\0';
624 while (fgets(buf, sizeof(buf), fp)) {
625 for (cp1 = buf; *cp1 && !isspace(*cp1); ++cp1)
626 ;
627 if (!*cp1)
628 break;
629 *cp1 = '\0';
630 if (ns_samename(buf, name) == 1) {
631 while (isspace(*++cp1))
632 ;
633 if (!*cp1)
634 break;
635 for (cp2 = cp1 + 1; *cp2 && !isspace(*cp2); ++cp2)
636 ;
637 *cp2 = '\0';
638 strncpy(dst, cp1, siz - 1);
639 dst[siz - 1] = '\0';
640 fclose(fp);
641 return (dst);
642 }
643 }
644 fclose(fp);
645 return (NULL);
646}
647libresolv_hidden_def (res_hostalias)
648