diff --git a/ares.h b/ares.h index 65a82cb5..06f60b33 100644 --- a/ares.h +++ b/ares.h @@ -164,6 +164,7 @@ extern "C" { #define ARES_OPT_ROTATE (1 << 14) #define ARES_OPT_EDNSPSZ (1 << 15) #define ARES_OPT_NOROTATE (1 << 16) +#define ARES_OPT_RESOLVCONF (1 << 17) /* Nameinfo flag values */ #define ARES_NI_NOFQDN (1 << 0) @@ -270,6 +271,7 @@ struct ares_options { struct apattern *sortlist; int nsort; int ednspsz; + char *resolvconf_path; }; struct hostent; diff --git a/ares_destroy.c b/ares_destroy.c index d55fe88b..fed2009a 100644 --- a/ares_destroy.c +++ b/ares_destroy.c @@ -36,6 +36,8 @@ void ares_destroy_options(struct ares_options *options) ares_free(options->sortlist); if(options->lookups) ares_free(options->lookups); + if(options->resolvconf_path) + ares_free(options->resolvconf_path); } void ares_destroy(ares_channel channel) @@ -85,6 +87,9 @@ void ares_destroy(ares_channel channel) if (channel->lookups) ares_free(channel->lookups); + if (channel->resolvconf_path) + ares_free(channel->resolvconf_path); + ares_free(channel); } diff --git a/ares_init.c b/ares_init.c index 851255cf..b4f09941 100644 --- a/ares_init.c +++ b/ares_init.c @@ -169,6 +169,7 @@ int ares_init_options(ares_channel *channelptr, struct ares_options *options, channel->sock_config_cb_data = NULL; channel->sock_funcs = NULL; channel->sock_func_cb_data = NULL; + channel->resolvconf_path = NULL; channel->last_server = 0; channel->last_timeout_processed = (time_t)now.tv_sec; @@ -242,6 +243,8 @@ done: ares_free(channel->sortlist); if(channel->lookups) ares_free(channel->lookups); + if(channel->resolvconf_path) + ares_free(channel->resolvconf_path); ares_free(channel); return status; } @@ -347,7 +350,7 @@ int ares_save_options(ares_channel channel, struct ares_options *options, (*optmask) = (ARES_OPT_FLAGS|ARES_OPT_TRIES|ARES_OPT_NDOTS| ARES_OPT_UDP_PORT|ARES_OPT_TCP_PORT|ARES_OPT_SOCK_STATE_CB| ARES_OPT_SERVERS|ARES_OPT_DOMAINS|ARES_OPT_LOOKUPS| - ARES_OPT_SORTLIST|ARES_OPT_TIMEOUTMS); + ARES_OPT_SORTLIST|ARES_OPT_TIMEOUTMS|ARES_OPT_RESOLVCONF); (*optmask) |= (channel->rotate ? ARES_OPT_ROTATE : ARES_OPT_NOROTATE); /* Copy easy stuff */ @@ -422,6 +425,13 @@ int ares_save_options(ares_channel channel, struct ares_options *options, } options->nsort = channel->nsort; + /* copy path for resolv.conf file */ + if (channel->resolvconf_path) { + options->resolvconf_path = ares_strdup(channel->resolvconf_path); + if (!options->resolvconf_path) + return ARES_ENOMEM; + } + return ARES_SUCCESS; } @@ -530,6 +540,14 @@ static int init_by_options(ares_channel channel, channel->nsort = options->nsort; } + /* Set path for resolv.conf file, if given. */ + if ((optmask & ARES_OPT_RESOLVCONF) && !channel->resolvconf_path) + { + channel->resolvconf_path = ares_strdup(options->resolvconf_path); + if (!channel->resolvconf_path && options->resolvconf_path) + return ARES_ENOMEM; + } + channel->optmask = optmask; return ARES_SUCCESS; @@ -1644,6 +1662,7 @@ static int init_by_resolv_conf(ares_channel channel) size_t linesize; int error; int update_domains; + const char *resolvconf_path; /* Don't read resolv.conf and friends if we don't have to */ if (ARES_CONFIG_CHECK(channel)) @@ -1652,7 +1671,14 @@ static int init_by_resolv_conf(ares_channel channel) /* Only update search domains if they're not already specified */ update_domains = (channel->ndomains == -1); - fp = fopen(PATH_RESOLV_CONF, "r"); + /* Support path for resolvconf filename set by ares_init_options */ + if(channel->resolvconf_path) { + resolvconf_path = channel->resolvconf_path; + } else { + resolvconf_path = PATH_RESOLV_CONF; + } + + fp = fopen(resolvconf_path, "r"); if (fp) { while ((status = ares__read_line(fp, &line, &linesize)) == ARES_SUCCESS) { @@ -1663,10 +1689,10 @@ static int init_by_resolv_conf(ares_channel channel) else if ((p = try_config(line, "search", ';')) && update_domains) status = set_search(channel, p); else if ((p = try_config(line, "nameserver", ';')) && - channel->nservers == -1) + channel->nservers == -1) status = config_nameserver(&servers, &nservers, p); else if ((p = try_config(line, "sortlist", ';')) && - channel->nsort == -1) + channel->nsort == -1) status = config_sortlist(&sortlist, &nsort, p); else if ((p = try_config(line, "options", ';'))) status = set_options(channel, p); @@ -1686,7 +1712,7 @@ static int init_by_resolv_conf(ares_channel channel) break; default: DEBUGF(fprintf(stderr, "fopen() failed with error: %d %s\n", - error, strerror(error))); + error, strerror(error))); DEBUGF(fprintf(stderr, "Error opening file: %s\n", PATH_RESOLV_CONF)); status = ARES_EFILE; } @@ -1954,6 +1980,11 @@ static int init_by_defaults(ares_channel channel) ares_free(channel->lookups); channel->lookups = NULL; } + + if(channel->resolvconf_path) { + ares_free(channel->resolvconf_path); + channel->resolvconf_path = NULL; + } } if(hostname) diff --git a/ares_init_options.3 b/ares_init_options.3 index 7a8bf28d..df5f6d34 100644 --- a/ares_init_options.3 +++ b/ares_init_options.3 @@ -40,6 +40,7 @@ struct ares_options { struct apattern *sortlist; int nsort; int ednspsz; + char *resolvconf_path; }; int ares_init_options(ares_channel *\fIchannelptr\fP, @@ -181,6 +182,15 @@ The receive buffer size to set for the socket. .B ARES_OPT_EDNSPSZ .B int \fIednspsz\fP; .br +.TP 18 +.B ARES_OPT_RESOLVCONF +.B char *\fIresolvconf_path\fP; +The path to use for reading the resolv.conf file. The +.I resolvconf_path +should be set to a path string, and will be honoured on *nix like systems. The +default is +.B /etc/resolv.conf +.br The message size to be advertized in EDNS; only takes effect if the .B ARES_FLAG_EDNS flag is set. @@ -259,6 +269,9 @@ c-ares library initialization not yet performed. .SH NOTES When initializing from .B /etc/resolv.conf, +(or, alternatively when specified by the +.I resolvconf_path +path location) \fBares_init_options(3)\fP reads the \fIdomain\fP and \fIsearch\fP directives to allow lookups of short names relative to the domains specified. The \fIdomain\fP and \fIsearch\fP directives override one another. If more that diff --git a/ares_private.h b/ares_private.h index 4dfbb534..6d9b6c36 100644 --- a/ares_private.h +++ b/ares_private.h @@ -325,6 +325,9 @@ struct ares_channeldata { const struct ares_socket_functions * sock_funcs; void *sock_func_cb_data; + + /* Path for resolv.conf file, configurable via ares_options */ + char *resolvconf_path; }; /* Memory management functions */ diff --git a/test/ares-test-init.cc b/test/ares-test-init.cc index 8c2bcac3..9177ba16 100644 --- a/test/ares-test-init.cc +++ b/test/ares-test-init.cc @@ -86,6 +86,8 @@ TEST_F(LibraryTest, OptionsChannelInit) { opts.lookups = strdup("b"); optmask |= ARES_OPT_LOOKUPS; optmask |= ARES_OPT_ROTATE; + opts.resolvconf_path = strdup("/etc/resolv.conf"); + optmask |= ARES_OPT_RESOLVCONF; ares_channel channel = nullptr; EXPECT_EQ(ARES_SUCCESS, ares_init_options(&channel, &opts, optmask)); @@ -112,6 +114,7 @@ TEST_F(LibraryTest, OptionsChannelInit) { EXPECT_EQ(std::string(opts.domains[0]), std::string(opts2.domains[0])); EXPECT_EQ(std::string(opts.domains[1]), std::string(opts2.domains[1])); EXPECT_EQ(std::string(opts.lookups), std::string(opts2.lookups)); + EXPECT_EQ(std::string(opts.resolvconf_path), std::string(opts2.resolvconf_path)); ares_destroy_options(&opts); ares_destroy_options(&opts2); @@ -169,6 +172,8 @@ TEST_F(LibraryTest, OptionsChannelAllocFail) { opts.lookups = strdup("b"); optmask |= ARES_OPT_LOOKUPS; optmask |= ARES_OPT_ROTATE; + opts.resolvconf_path = strdup("/etc/resolv.conf"); + optmask |= ARES_OPT_RESOLVCONF; ares_channel channel = nullptr; for (int ii = 1; ii <= 8; ii++) { @@ -396,6 +401,35 @@ CONTAINED_TEST_F(LibraryTest, ContainerFullResolvInit, return HasFailure(); } +// Allow path for resolv.conf to be configurable +NameContentList myresolvconf = { + {"/tmp/myresolv.cnf", " nameserver 1.2.3.4 \n" + "search first.com second.com\n" + "lookup bind\n" + "options debug ndots:5\n" + "sortlist 1.2.3.4/16 2.3.4.5\n"}}; +CONTAINED_TEST_F(LibraryTest, ContainerMyResolvConfInit, + "myhostname", "mydomain.org", myresolvconf) { + char filename[] = "/tmp/myresolv.cnf"; + ares_channel channel = nullptr; + struct ares_options options = {0}; + options.resolvconf_path = strdup(filename); + int optmask = ARES_OPT_RESOLVCONF; + EXPECT_EQ(ARES_SUCCESS, ares_init_options(&channel, &options, optmask)); + + optmask = 0; + free(options.resolvconf_path); + options.resolvconf_path = NULL; + + EXPECT_EQ(ARES_SUCCESS, ares_save_options(channel, &options, &optmask)); + EXPECT_EQ(ARES_OPT_RESOLVCONF, (optmask & ARES_OPT_RESOLVCONF)); + EXPECT_EQ(std::string(filename), std::string(options.resolvconf_path)); + + ares_destroy_options(&options); + ares_destroy(channel); + return HasFailure(); +} + NameContentList hostconf = { {"/etc/resolv.conf", "nameserver 1.2.3.4\n" "sortlist1.2.3.4\n" // malformed line