/*
* reshook.c - dnssec-trigger resolv.conf hooks for adjusting name resolution
*
* Copyright (c) 2011, NLnet Labs. All rights reserved.
*
* This software is open source.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the NLNET LABS nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* \file
*
* This file contains the unbound hooks for adjusting the name resolution
* on the system (to 127.0.0.1).
*/
#include "config.h"
#include <sys/stat.h>
#include "reshook.h"
#include "log.h"
#include "cfg.h"
#include "probe.h"
#ifdef USE_WINSOCK
#include "winrc/win_svc.h"
#endif
#ifdef HAVE_CHFLAGS
#include <sys/stat.h>
#endif
static int set_to_localhost = 0;
#ifdef HOOKS_OSX
/** set the DNS the OSX way */
static void
set_dns_osx(struct cfg* cfg, char* iplist)
{
char cmd[10240];
char dm[1024];
char* domains = "nothing.invalid";
if(cfg->rescf_domain && cfg->rescf_domain[0])
domains = cfg->rescf_domain;
else if(cfg->rescf_search && cfg->rescf_search[0]) {
snprintf(dm, sizeof(dm), "%s", cfg->rescf_search);
domains = dm;
}
snprintf(cmd, sizeof(cmd), "%s/dnssec-trigger-setdns.sh mset %s -- %s",
LIBEXEC_DIR, domains, iplist);
verbose(VERB_QUERY, "%s", cmd);
system(cmd);
}
/** restore resolv.conf on OSX if localhost is enabled */
void restore_resolv_osx(struct cfg* cfg)
{
if(set_to_localhost)
hook_resolv_localhost(cfg);
}
#endif /* HOOKS_OSX */
#ifndef USE_WINSOCK
static void prline(FILE* out, const char* line)
{
int r = fprintf(out, "%s", line);
if(r < 0) {
log_err("cannot write resolvconf: %s", strerror(errno));
} else if(r != (int)strlen(line)) {
log_err("short write resolvconf: filesystem full?");
}
}
#if defined(HAVE_CHFLAGS) && !defined(HOOKS_OSX)
static void r_mutable_bsd(const char* f)
{
if(chflags(f, 0) < 0) {
log_err("chflags(%s, nouchg) failed: %s", f, strerror(errno));
}
}
static void r_immutable_bsd(const char* f)
{
/* BSD method for immutable files */
if(chflags(f, UF_IMMUTABLE|UF_NOUNLINK) < 0) {
log_err("chflags(%s, uchg) failed: %s", f, strerror(errno));
}
}
#elif !defined(HAVE_CHFLAGS) && !defined(HOOKS_OSX)
static void r_mutable_efs(const char* f)
{
char buf[10240];
snprintf(buf, sizeof(buf), "chattr -i %s", f);
if(system(buf) < 0)
log_err("could not %s: %s", buf, strerror(errno));
}
static void r_immutable_efs(const char* f)
{
char buf[10240];
/* this chattr only works on extX file systems */
snprintf(buf, sizeof(buf), "chattr +i %s", f);
if(system(buf) < 0)
log_err("could not %s: %s", buf, strerror(errno));
}
#endif /* mutable/immutable on BSD and Linux */
static FILE* open_rescf(struct cfg* cfg)
{
FILE* out;
char line[1024];
# if defined(HAVE_CHFLAGS) && !defined(HOOKS_OSX)
r_mutable_bsd(cfg->resolvconf);
# elif !defined(HAVE_CHFLAGS) && !defined(HOOKS_OSX)
r_mutable_efs(cfg->resolvconf);
# endif
if(chmod(cfg->resolvconf, 0644)<0) {
log_err("chmod(%s) failed: %s", cfg->resolvconf,
strerror(errno));
}
/* make resolv.conf writable */
out = fopen(cfg->resolvconf, "w");
if(!out) {
log_err("cannot open %s: %s", cfg->resolvconf, strerror(errno));
return NULL;
}
prline(out, "# Generated by " PACKAGE_STRING "\n");
if(cfg->rescf_domain) {
snprintf(line, sizeof(line), "domain %s\n", cfg->rescf_domain);
prline(out, line);
}
if(cfg->rescf_search) {
snprintf(line, sizeof(line), "search %s\n", cfg->rescf_search);
prline(out, line);
}
return out;
}
static void close_rescf(struct cfg* cfg, FILE* out)
{
fclose(out);
/* make resolv.conf readonly */
if(chmod(cfg->resolvconf, 0444)<0) {
log_err("chmod(%s) failed: %s", cfg->resolvconf,
strerror(errno));
}
#if defined(HAVE_CHFLAGS) && !defined(HOOKS_OSX)
r_immutable_bsd(cfg->resolvconf);
#elif !defined(HAVE_CHFLAGS) && !defined(HOOKS_OSX)
r_immutable_efs(cfg->resolvconf);
#endif
}
#endif /* !USE_WINSOCK */
#if !defined(USE_WINSOCK) && !defined(HOOKS_OSX)
/** check argument on line matches config option */
static int
check_line_arg(char* line, const char* opt)
{
if(!opt) /* has opt in file but should not */
return 0;
if(strncmp(line, opt, strlen(opt)) != 0)
/* file has wrong content */
return 0;
if(strcmp(line+strlen(opt), "\n") != 0)
/* stuff after opt (too many domains) */
return 0;
return 1;
}
/** check if resolv.conf is set to 127.0.0.1 like we want */
static int
really_set_to_localhost(struct cfg* cfg) {
FILE* in = fopen(cfg->resolvconf, "r");
int saw_127 = 0, saw_search = 0, saw_domain = 0;
char line[1024];
if(!in) {
verbose(VERB_DETAIL, "fopen %s: %s",
cfg->resolvconf, strerror(errno));
return 0;
}
if(!fgets(line, (int)sizeof(line), in)) {
fclose(in); /* failed to read first line */
return 0;
}
/* we want the first line to be 'Generated by me' */
if(strcmp(line, "# Generated by " PACKAGE_STRING "\n") != 0) {
fclose(in);
return 0;
}
/* must contain 127.0.0.1 and nothing else */
while(fgets(line, (int)sizeof(line), in)) {
line[sizeof(line)-1] = 0; /* robust end of string */
if(strcmp(line, "nameserver 127.0.0.1\n") == 0) {
saw_127 = 1;
} else if(strncmp(line, "nameserver", 10) == 0) {
/* not 127.0.0.1 but in resolv.conf, bad! */
fclose(in);
return 0;
} else if(strncmp(line, "search ", 7) == 0) {
if(!check_line_arg(line+7, cfg->rescf_search)) {
fclose(in);
return 0;
}
saw_search = 1;
} else if(strncmp(line, "domain ", 7) == 0) {
if(!check_line_arg(line+7, cfg->rescf_domain)) {
fclose(in);
return 0;
}
saw_domain = 1;
}
}
fclose(in);
if(cfg->rescf_search && !saw_search)
return 0;
if(cfg->rescf_domain && !saw_domain)
return 0;
return saw_127;
}
#endif /* no USE_WINSOCK, no OSX */
void hook_resolv_localhost(struct cfg* cfg)
{
#ifndef USE_WINSOCK
FILE* out;
#endif
set_to_localhost = 1;
if(cfg->noaction) {
return;
}
#ifdef HOOKS_OSX
set_dns_osx(cfg, "127.0.0.1");
#endif
#ifdef USE_WINSOCK
win_set_resolv("127.0.0.1");
#else /* not on windows */
# ifndef HOOKS_OSX /* on Linux/BSD */
if (system("/usr/libexec/dnssec-trigger-script --setup") == 0)
return;
if(really_set_to_localhost(cfg)) {
/* already done, do not do it again, that would open
* a brief moment of mutable resolv.conf */
verbose(VERB_ALGO, "resolv.conf localhost already set");
return;
}
verbose(VERB_ALGO, "resolv.conf localhost write");
# endif
out = open_rescf(cfg);
if(!out) return;
/* write the nameserver records */
prline(out, "nameserver 127.0.0.1\n");
close_rescf(cfg, out);
#endif /* not on windows */
}
void hook_resolv_iplist(struct cfg* cfg, struct probe_ip* list)
{
#ifndef USE_WINSOCK
char line[1024];
FILE* out;
#endif
#if defined(HOOKS_OSX) || defined(USE_WINSOCK)
char iplist[10240];
iplist[0] = 0;
#else
if (system("/usr/libexec/dnssec-trigger-script --restore") == 0)
return;
#endif
set_to_localhost = 0;
if(cfg->noaction)
return;
#ifndef USE_WINSOCK
out = open_rescf(cfg);
if(!out) return;
#endif
/* write the nameserver records */
while(list) {
if(probe_is_cache(list)) {
#ifndef USE_WINSOCK
snprintf(line, sizeof(line), "nameserver %s\n",
list->name);
prline(out, line);
#endif
#if defined(HOOKS_OSX) || defined(USE_WINSOCK)
snprintf(iplist+strlen(iplist),
sizeof(iplist)-strlen(iplist), "%s%s",
((iplist[0]==0)?"":" "), list->name);
#endif
}
list = list->next;
}
#ifndef USE_WINSOCK
close_rescf(cfg, out);
#endif
#ifdef HOOKS_OSX
set_dns_osx(cfg, iplist);
#endif
#ifdef USE_WINSOCK
win_set_resolv(iplist);
#endif
}
void hook_resolv_flush(struct cfg* cfg)
{
/* attempt to flush OS specific caches, because we go from
* insecure to secure mode */
(void)cfg;
#ifdef HOOKS_OSX
/* dscacheutil on 10.5 an later, lookupd before that */
system("dscacheutil -flushcache || lookupd -flushcache || discoveryutil udnsflushcaches");
system("discoveryutil mdnsflushcache");
#elif defined(USE_WINSOCK)
win_run_cmd("ipconfig /flushdns");
#else
/* TODO */
#endif
}
#ifdef HOOKS_OSX
static void osx_uninit(void)
{
char cmd[10240];
snprintf(cmd, sizeof(cmd), "%s/dnssec-trigger-setdns.sh uninit",
LIBEXEC_DIR);
verbose(VERB_QUERY, "%s", cmd);
system(cmd);
}
#endif /* HOOKS_OSX */
void hook_resolv_uninstall(struct cfg* cfg)
{
#ifdef HOOKS_OSX
/* on OSX: do the OSX thing */
(void)cfg;
osx_uninit();
#elif defined(USE_WINSOCK)
/* on Windows: edit registry */
(void)cfg;
win_clear_resolv();
#elif defined(HAVE_CHFLAGS)
/* on BSD: make file mutable again */
r_mutable_bsd(cfg->resolvconf);
#else
/* other (unix) systems, make it via ext2fs mutable again */
r_mutable_efs(cfg->resolvconf);
#endif
}