From patchwork Sun Feb 14 11:27:48 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 582489 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from sourceware.org (server1.sourceware.org [209.132.180.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 691D714031F for ; Sun, 14 Feb 2016 22:28:05 +1100 (AEDT) Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; secure) header.d=sourceware.org header.i=@sourceware.org header.b=d6UNiqZ2; dkim-atps=neutral DomainKey-Signature: a=rsa-sha1; c=nofws; d=sourceware.org; h=list-id :list-unsubscribe:list-subscribe:list-archive:list-post :list-help:sender:to:from:subject:message-id:date:mime-version :content-type; q=dns; s=default; b=rHknlpO33hCN+jDUmokVK7TEDx0oG 1zCcdKiTA1d96M1mU/CkeFz/u8X8AFci4Kw2sTaSm6M5eE88J5uD6RpgWrADHuDk 0ODyQnjcmNeRIkBWy3FTxtPNFwmLKDE1MNm1xJh+UR9d72BjkoXOkXhPVadgGUed pXJ9ljjLRVaC9U= DKIM-Signature: v=1; a=rsa-sha1; c=relaxed; d=sourceware.org; h=list-id :list-unsubscribe:list-subscribe:list-archive:list-post :list-help:sender:to:from:subject:message-id:date:mime-version :content-type; s=default; bh=yMK/5+QdZIZNCGbXeIHLGTajR64=; b=d6U NiqZ2q58TeBnvAiRQU63qnbJ7FZf6wfUbxlAD+EaFkKbVN65BvIm2qSqKG156dG8 TRKOKbW09V42LJkum0EOTfOt6r/I07tBIybmnRONDCsGsHHBpXPf74wlX88gnxAZ Hn/hm2hqG90KMjY6IncMvLWD3fhIOj/rzKg9+JvE= Received: (qmail 108849 invoked by alias); 14 Feb 2016 11:27:57 -0000 Mailing-List: contact libc-alpha-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: libc-alpha-owner@sourceware.org Delivered-To: mailing list libc-alpha@sourceware.org Received: (qmail 108791 invoked by uid 89); 14 Feb 2016 11:27:54 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-1.9 required=5.0 tests=BAYES_00, RP_MATCHES_RCVD, SPF_HELO_PASS autolearn=ham version=3.3.2 spammy=427, FILL, 42, 7, 1985 X-HELO: mx1.redhat.com To: GNU C Library From: Florian Weimer Subject: [PATCH] resolv: Extract __res_init_fp function X-Enigmail-Draft-Status: N1110 Message-ID: <56C064B4.1080306@redhat.com> Date: Sun, 14 Feb 2016 12:27:48 +0100 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Thunderbird/38.5.0 MIME-Version: 1.0 This function helps with resolver testing. The new test shows a memory leak under valgrind, which is due to bug 19257. Florian 2016-02-14 Florian Weimer * resolv/res_init.c (init_defdname): Extracted from _res_vinit. (__res_init_fp): Likewise. Use parameters instead of environment variables, and have the caller supply the input stream. Use __getline to read lines from the input file. (__res_vinit): Use __res_init_fp. * resolv/Versions (GLIBC_PRIVATE): Export __res_init_fp. * resolv/tst-res_init.c: New test. * resolv/Makefile (tests): Add it. diff --git a/include/resolv.h b/include/resolv.h index 4c61476..fc05484 100644 --- a/include/resolv.h +++ b/include/resolv.h @@ -22,6 +22,7 @@ extern __thread struct __res_state *__resp attribute_tls_model_ie; /* Now define the internal interfaces. */ extern int __res_vinit (res_state, int); +extern void __res_init_fp (res_state, FILE *, bool, const char *); extern int __res_maybe_init (res_state, int); extern void _sethtent (int); extern void _endhtent (void); @@ -41,6 +42,7 @@ extern void __res_iclose (res_state statp, bool free_addr); extern int __res_nopt(res_state statp, int n0, u_char *buf, int buflen, int anslen); libc_hidden_proto (__res_ninit) +libc_hidden_proto (__res_init_fp) libc_hidden_proto (__res_maybe_init) libc_hidden_proto (__res_nclose) libc_hidden_proto (__res_iclose) diff --git a/resolv/Makefile b/resolv/Makefile index 8be41d3..08503e0 100644 --- a/resolv/Makefile +++ b/resolv/Makefile @@ -30,7 +30,7 @@ headers := resolv.h \ routines := herror inet_addr inet_ntop inet_pton nsap_addr res_init \ res_hconf res_libc res-state -tests = tst-aton tst-leaks tst-inet_ntop +tests = tst-aton tst-leaks tst-inet_ntop tst-res_init xtests = tst-leaks2 generate := mtrace-tst-leaks.out tst-leaks.mtrace tst-leaks2.mtrace diff --git a/resolv/Versions b/resolv/Versions index e561bce..e3226cd 100644 --- a/resolv/Versions +++ b/resolv/Versions @@ -27,6 +27,9 @@ libc { __h_errno; __resp; __res_maybe_init; __res_iclose; + + # For testing. + __res_init_fp; } } diff --git a/resolv/res_init.c b/resolv/res_init.c index 128004a..6fefa6d 100644 --- a/resolv/res_init.c +++ b/resolv/res_init.c @@ -1,3 +1,21 @@ +/* Resolver structure initialization and resolv.conf parsing. + Copyright (C) 1995-2016 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + /* * Copyright (c) 1985, 1989, 1993 * The Regents of the University of California. All rights reserved. @@ -132,16 +150,57 @@ res_ninit(res_state statp) { libc_hidden_def (__res_ninit) #endif -/* This function has to be reachable by res_data.c but not publically. */ int -__res_vinit(res_state statp, int preinit) { - FILE *fp; +__res_vinit (res_state statp, int preinit) +{ + FILE *fp = fopen (_PATH_RESCONF, "rce"); + /* LOCALDOMAIN is listed in unsecvars.h and removed in AT_SECURE + mode. */ + const char *localdomain = getenv ("LOCALDOMAIN"); + __res_init_fp (statp, fp, preinit, localdomain); + if (fp != NULL) + fclose (fp); + /* RES_OPTIONS is listed in unsecvars.h, too. */ + const char *options = getenv ("RES_OPTIONS"); + if (options != NULL) + res_setoptions (statp, options, "env"); + /* __res_init_fp currently ignores all errors. */ + return 0; +} + +/* Initialize the defdname member of *STATP if it is empty. The + default is derived from the system host name. */ +static void +init_defdname (res_state statp) +{ + if (statp->defdname[0] != '\0') + return; + if (__gethostname (statp->defdname, sizeof (statp->defdname) - 1) != 0) + return; + const char *dot = strchr (statp->defdname, '.'); + if (dot == NULL) + /* Host name does not contain a dot. Use the root as the + default domain name. */ + statp->defdname[0] = '\0'; + else + /* Use the remaining part of the host name as the domain name. */ + memmove (statp->defdname, dot + 1, strlen (dot + 1) + 1); +} + +/* Initialize *STATP from the resolv.conf-style file FP. If PREINIT, + do not set default values for the *STATP members retrans, retry, + options, id. If LOCALDOMAIN is not NULL, use it to initialize the + search path instead of the contents of the file. If FP is NULL, + just write the default values (and, possibly, the contents of + LOCALDOMAIN) to *STATP. */ +void +__res_init_fp (res_state statp, FILE *fp, bool preinit, + const char *localdomain) +{ char *cp, **pp; int n; - char buf[BUFSIZ]; int nserv = 0; /* number of nameservers read from file */ int have_serv6 = 0; - int haveenv = 0; int havesearch = 0; #ifdef RESOLVSORT int nsort = 0; @@ -174,10 +233,10 @@ __res_vinit(res_state statp, int preinit) { statp->_u._ext.nsaddrs[n] = NULL; /* Allow user to override the local domain definition */ - if ((cp = getenv("LOCALDOMAIN")) != NULL) { - (void)strncpy(statp->defdname, cp, sizeof(statp->defdname) - 1); + if (localdomain != NULL) { + strncpy (statp->defdname, localdomain, + sizeof (statp->defdname) - 1); statp->defdname[sizeof(statp->defdname) - 1] = '\0'; - haveenv++; /* * Set search list to be blank-separated strings @@ -213,17 +272,17 @@ __res_vinit(res_state statp, int preinit) { (line[sizeof(name) - 1] == ' ' || \ line[sizeof(name) - 1] == '\t')) - if ((fp = fopen(_PATH_RESCONF, "rce")) != NULL) { - /* No threads use this stream. */ - __fsetlocking (fp, FSETLOCKING_BYCALLER); - /* read the config file */ - while (__fgets_unlocked(buf, sizeof(buf), fp) != NULL) { + if (fp != NULL) { + char *buf = NULL; + size_t buf_len = 0; + while (__getline (&buf, &buf_len, fp) >= 0) { /* skip comments */ if (*buf == ';' || *buf == '#') continue; /* read default domain name */ if (MATCH(buf, "domain")) { - if (haveenv) /* skip if have from environ */ + /* Do not override the environment setting. */ + if (localdomain != NULL) continue; cp = buf + sizeof("domain") - 1; while (*cp == ' ' || *cp == '\t') @@ -239,8 +298,9 @@ __res_vinit(res_state statp, int preinit) { } /* set search list */ if (MATCH(buf, "search")) { - if (haveenv) /* skip if have from environ */ - continue; + /* Do not override the environment setting. */ + if (localdomain != NULL) + continue; cp = buf + sizeof("search") - 1; while (*cp == ' ' || *cp == '\t') cp++; @@ -399,7 +459,7 @@ __res_vinit(res_state statp, int preinit) { #ifdef RESOLVSORT statp->nsort = nsort; #endif - (void) fclose(fp); + free (buf); } if (__builtin_expect(statp->nscount == 0, 0)) { statp->nsaddr.sin_addr = __inet_makeaddr(IN_LOOPBACKNET, 1); @@ -407,10 +467,7 @@ __res_vinit(res_state statp, int preinit) { statp->nsaddr.sin_port = htons(NAMESERVER_PORT); statp->nscount = 1; } - if (statp->defdname[0] == 0 && - __gethostname(buf, sizeof(statp->defdname) - 1) == 0 && - (cp = strchr(buf, '.')) != NULL) - strcpy(statp->defdname, cp + 1); + init_defdname (statp); /* find components of local domain that might be searched */ if (havesearch == 0) { @@ -443,11 +500,9 @@ __res_vinit(res_state statp, int preinit) { #endif /* !RFC1535 */ } - if ((cp = getenv("RES_OPTIONS")) != NULL) - res_setoptions(statp, cp, "env"); statp->options |= RES_INIT; - return (0); } +libc_hidden_def (__res_init_fp) static void internal_function diff --git a/resolv/tst-res_init.c b/resolv/tst-res_init.c new file mode 100644 index 0000000..c1e5e03 --- /dev/null +++ b/resolv/tst-res_init.c @@ -0,0 +1,236 @@ +/* Test resolv.conf parsing. + Copyright (C) 1995-2016 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include +#include +#include +#include + +static bool errors; + +static char hostname[HOST_NAME_MAX + 1]; +static const char *domainname; + +/* Allocate a new resolver state. */ +static res_state +new_state (void) +{ + res_state statp = malloc (sizeof (*statp)); + if (statp == NULL) + { + printf ("error: malloc: %m\n"); + abort (); + } + return statp; +} + +/* Opens string for reading. Does not make a copy of the string. */ +static FILE * +open_string (const char *str) +{ + FILE *fp = fmemopen ((char *) str, strlen (str), "r"); + if (fp == NULL) + { + printf ("error: fmemopen: %m\n"); + abort (); + } + return fp; +} + +/* Tests with an empty configuration. If positive, FILL is used to + initialize the allocated resolver structure. */ +static void +test_empty (int fill) +{ + res_state statp = new_state (); + if (fill >= 0) + memset (statp, fill, sizeof (*statp)); + + { + FILE *fp = open_string (""); + __res_init_fp (statp, fp, false, NULL); + fclose (fp); + } + + if ((statp->options & RES_INIT) == 0) + { + printf ("error: RES_INIT not set\n"); + errors = true; + } + if (domainname == NULL) + /* System does not have a domain name as part of the host name. */ + { + if (statp->defdname[0] != '\0') + { + printf ("error: incorrect default domain name: %s\n", statp->defdname); + errors = true; + } + if (statp->dnsrch[0][0] != '\0') + { + printf ("error: invalid search entry 0: %s\n", statp->dnsrch[0]); + errors = true; + } + } + else + /* System has a domain name as part of the host name. */ + { + if (strcmp (statp->defdname, domainname) != 0) + { + printf ("info: system host name: %s\n", hostname); + printf ("error: incorrect default domain name: %s\n", statp->defdname); + errors = true; + } + if (strcmp (statp->dnsrch[0], domainname) != 0) + { + printf ("error: incorrect search entry 0: %s\n", statp->dnsrch[0]); + errors = true; + } + } + if (statp->dnsrch[1] != NULL) + { + printf ("error: invalid search entry 1: %s\n", statp->dnsrch[1]); + errors = true; + } + + res_nclose (statp); + free (statp); +} + +static void +test_ipv6 (void) +{ + res_state statp = new_state (); + + { + const char *conf + = "domain .\n" + "nameserver 2001:db8::1\n"; + FILE *fp = open_string (conf); + __res_init_fp (statp, fp, false, NULL); + fclose (fp); + } + + if (statp->nscount != 1) + { + printf ("error: expected one name server, got %d\n", statp->nscount); + errors = true; + } + else if (statp->_u._ext.nsaddrs[0] == NULL) + { + printf ("error: IPv6 name server is missing\n"); + errors = true; + } + else + { + char buf[128]; + if (inet_ntop (AF_INET6, statp->_u._ext.nsaddrs[0]->sin6_addr.s6_addr, + buf, sizeof (buf)) == NULL) + { + printf ("error: inet_ntop: %m\n"); + errors = true; + } + else if (strcmp (buf, "2001:db8::1") != 0) + { + printf ("error: incorrect name server %s\n", buf); + errors = true; + } + } + + res_nclose (statp); + free (statp); +} + +/* Check that the original settings are restored on reinitialization + of a resolver structure with an empty configuration. */ +static void +bug19369 (const char *localdomain) +{ + res_state statp_reference = new_state (); + { + FILE *fp = open_string (""); + __res_init_fp (statp_reference, fp, false, localdomain); + fclose (fp); + } + + res_state statp = new_state (); + { + /* NB: No IPv6 name servers. They would trigger memory + allocation, and reinitialization without an intervening + res_nclose leads to a memory leak. */ + const char *conf + = "search example.org\n" + "nameserver 192.0.2.1\n" + "nameserver 192.0.2.2\n"; + FILE *fp = open_string (conf); + __res_init_fp (statp, fp, false, NULL); + fclose (fp); + } + if (statp->nscount != 2) + { + printf ("error: invalid name server count: %d\n", statp->nscount); + errors = true; + } + { + FILE *fp = open_string (""); + __res_init_fp (statp, fp, false, localdomain); + fclose (fp); + } + if (statp->nscount != 1) + { + printf ("error: invalid name server count: %d\n", statp->nscount); + errors = true; + } + if (ntohl (statp->nsaddr_list[0].sin_addr.s_addr) != INADDR_LOOPBACK) + { + printf ("error: incorrect loopback name server address\n"); + errors = true; + } + if (strcmp (statp->defdname, statp_reference->defdname) != 0) + { + printf ("info: expected defdname: %s\n", statp_reference->defdname); + printf ("error: actual defdname: %s\n", statp->defdname); + errors = true; + } + + free (statp); + free (statp_reference); +} + +static int +do_test (void) +{ + if (gethostname (hostname, sizeof (hostname) - 1) != 0) + hostname[0] = '\0'; + domainname = strchr (hostname, '.'); + if (domainname != NULL) + ++domainname; + + for (int i = -1; i <= 255; ++i) + test_empty (i); + + test_ipv6 (); + bug19369 (NULL); + bug19369 (""); + + return errors; +} + +#define TEST_FUNCTION do_test () +#include "../test-skeleton.c"