]> granicus.if.org Git - pdns/commitdiff
Fix chroot() issues
authorPieter Lexis <pieter.lexis@powerdns.com>
Tue, 22 Dec 2015 11:39:53 +0000 (12:39 +0100)
committerPieter Lexis <pieter.lexis@powerdns.com>
Wed, 23 Dec 2015 13:59:35 +0000 (14:59 +0100)
We now create the sockets and pid-files inside the chroot. The *_control
tools also know about the chroot and act accordingly.

Closes #191
Closes #148

docs/manpages/rec_control.1.md
docs/markdown/authoritative/settings.md
docs/markdown/recursor/settings.md
pdns/common_startup.cc
pdns/dynlistener.cc
pdns/dynloader.cc
pdns/pdns.conf-dist
pdns/pdns_recursor.cc
pdns/rec_channel_rec.cc
pdns/rec_control.cc
pdns/receiver.cc

index a9167ce5c2a7ded635f189ddbeecf35c8c7495d9..2d2ce201a69038ffac06763211dcd409c2bd224c 100644 (file)
@@ -10,7 +10,7 @@ rec_control - control pdns_recursor
 
 DESCRIPTION
 -----------
-rec_control(1) allows the operator to control a running instance
+**rec_control** allows the operator to control a running instance
 of the pdns_recursor.
 
 The commands that can be passed to the recursor are described on
@@ -29,8 +29,11 @@ To dump the cache to disk, execute:
 --help
 :    provide this helpful message.
 
+--config-dir=*PATH*
+:    Directory where the recursor.conf lives.
+
 --socket-dir=*PATH*
-:    Where the controlsocket will live.
+:    Where the controlsocket will live, please use **--config-dir** instead.
 
 --socket-pid=*PID*
 :    When running in SMP mode, pid of **pdns_recursor** to control.
index ebbeb0dab25e71344e3bb4df9fd167e8be81fee6..d293945537e8d7ddd4348bbba056571363cafb1a 100644 (file)
@@ -114,6 +114,13 @@ If sending carbon updates, this is the interval between them in seconds. See
 If set, chroot to this directory for more security. See
 ["Security settings & considerations"](../common/security.md).
 
+Make sure that `/dev/log` is available from within the chroot. Logging will
+silently fail over time otherwise (on logrotate).
+
+When setting `chroot`, all other paths in the config (except for
+[`config-dir`](#config-dir) and [`module-dir`](#module-dir)) set in the configuration
+are relative to the new root.
+
 ## `config-dir`
 * Path
 
index d2fad962d58391364e78931a4dcdef29bd36f5b4..d5ca00f92fa680cb5993be50a07eda3346a0e351 100644 (file)
@@ -123,6 +123,12 @@ If set, chroot to this directory for more security. See [Security](../common/sec
 Make sure that `/dev/log` is available from within the chroot. Logging will
 silently fail over time otherwise (on logrotate).
 
+When using `chroot`, all other paths (except for [`config-dir`](#config-dir) set
+in the configuration are relative to the new root.
+
+When using `chroot` and the API ([`webserver`](#webserver)), [`api-readonly`](#api-readonly)
+must be set and [`api-config-dir`](#api-config-dir) unset.
+
 ## `client-tcp-timeout`
 * Integer
 * Default: 2
@@ -607,6 +613,8 @@ Use only a single socket for outgoing queries.
 Where to store the control socket and pidfile. The default depends on
 `LOCALSTATEDIR` during compile-time (usually `/var/run` or `/run`).
 
+When using [`chroot`](#chroot) the default becomes to `/`.
+
 ## `socket-owner`, `socket-group`, `socket-mode`
 Owner, group and mode of the controlsocket. Owner and group can be specified by
 name, mode is in octal.
index 06871f9d13d0e86e60e0f2c8c420651253ab97d6..2381a3c3abc968d896d45dbdd074aa73acb23f30 100644 (file)
@@ -472,7 +472,7 @@ void mainthread()
 
    secPollParseResolveConf();
 
-   if(!::arg()["chroot"].empty()) {  
+   if(!::arg()["chroot"].empty()) {
      triggerLoadOfLibraries();
      if(::arg().mustDo("master") || ::arg().mustDo("slave"))
         gethostbyname("a.root-servers.net"); // this forces all lookup libraries to be loaded
index b160a056f977aad82db3fb0eeccd70ce98d43755..eab787f9441f5b00a99529c833145a466149a272 100644 (file)
@@ -177,7 +177,16 @@ DynListener::DynListener(const string &progname)
   d_s=-1;
 
   if(!progname.empty()) {
-    string socketname=arg()["socket-dir"]+"/";
+    string socketname = ::arg()["socket-dir"];
+    if (::arg()["socket-dir"].empty()) {
+      if (::arg()["chroot"].empty())
+        socketname = LOCALSTATEDIR;
+      else
+        socketname = ::arg()["chroot"];
+    } else if (!::arg()["socket-dir"].empty() && !::arg()["chroot"].empty()) {
+      socketname = ::arg()["chroot"] + ::arg()["socket-dir"];
+    }
+    socketname += "/";
     cleanSlashes(socketname);
     
     if(!mkdir(socketname.c_str(),0700)) // make /var directory, if needed
index 282b919bece3d9713e51209fc5a614cce82dbfec..f1785cbce7231d1eab9e020c9bbb59d2adc104df 100644 (file)
@@ -59,7 +59,7 @@ int main(int argc, char **argv)
   string s_programname="pdns";
 
   ::arg().set("config-dir","Location of configuration directory (pdns.conf)")=SYSCONFDIR;
-  ::arg().set("socket-dir","Where the controlsocket will live")=LOCALSTATEDIR;
+  ::arg().set("socket-dir",string("Where the controlsocket will live, ")+LOCALSTATEDIR+" when unset and not chrooted" )="";
   ::arg().set("remote-address","Remote address to query");
   ::arg().set("remote-port","Remote port to query")="53000";
   ::arg().set("secret","Secret needed to connect to remote PowerDNS");
@@ -94,8 +94,18 @@ int main(int argc, char **argv)
     ::arg().laxFile(configname.c_str());
     ::arg().laxParse(argc,argv); // reparse so the commandline still wins
   }
-  
-  string socketname=::arg()["socket-dir"]+"/"+s_programname+".controlsocket";
+
+  string socketname=::arg()["socket-dir"];
+  if (::arg()["socket-dir"].empty()) {
+    if (::arg()["chroot"].empty())
+      socketname = LOCALSTATEDIR;
+    else
+      socketname = ::arg()["chroot"] + "/";
+  } else if (!::arg()["socket-dir"].empty() && !::arg()["chroot"].empty()) {
+    socketname = ::arg()["chroot"] + ::arg()["socket-dir"];
+  }
+
+  socketname += "/" + s_programname + ".controlsocket";
   cleanSlashes(socketname);
   
   try {
index 69ccfa6f4446b94fc0928ff14385a93110f07455..0cebcfeb9d3a919a3eb238d1237d17a774ea4cd4 100644 (file)
 # soa-retry-default=3600
 
 #################################
-# socket-dir   Where the controlsocket will live
+# socket-dir   Where the controlsocket will live, /var/run when unset and not chrooted
 #
-# socket-dir=/var/run
+# socket-dir=
 
 #################################
 # tcp-control-address  If set, PowerDNS can be controlled over TCP on this address
index 87f1472737075897531a47d88e71bc31b1e58264..a002446cf8b5c5c62c32b9f8db6a2d0619758589 100644 (file)
@@ -1774,6 +1774,13 @@ void handleRCC(int fd, FDMultiplexer::funcparam_t& var)
   RecursorControlParser::func_t* command;
 
   string answer=rcp.getAnswer(msg, &command);
+
+  // If we are inside a chroot, we need to strip
+  if (!arg()["chroot"].empty()) {
+    int len = arg()["chroot"].length();
+    remote = remote.substr(len);
+  }
+
   try {
     s_rcc.send(answer, &remote);
     command();
@@ -2320,10 +2327,6 @@ int serviceMain(int argc, char*argv[])
       break;
   }
 
-  s_pidfname=::arg()["socket-dir"]+"/"+s_programname+".pid";
-  if(!s_pidfname.empty())
-    unlink(s_pidfname.c_str()); // remove possible old pid file
-
   if(::arg().mustDo("daemon")) {
     L<<Logger::Warning<<"Calling daemonize, going to background"<<endl;
     L.toConsole(Logger::Critical);
@@ -2332,8 +2335,6 @@ int serviceMain(int argc, char*argv[])
   signal(SIGUSR1,usr1Handler);
   signal(SIGUSR2,usr2Handler);
   signal(SIGPIPE,SIG_IGN);
-  writePid();
-  makeControlChannelSocket( ::arg().asNum("processes") > 1 ? forks : -1);
   g_numThreads = ::arg().asNum("threads") + ::arg().mustDo("pdns-distributes-queries");
   g_numWorkerThreads = ::arg().asNum("threads");
   g_maxMThreads = ::arg().asNum("max-mthreads");
@@ -2353,8 +2354,17 @@ int serviceMain(int argc, char*argv[])
       L<<Logger::Error<<"Unable to chroot to '"+::arg()["chroot"]+"': "<<strerror (errno)<<", exiting"<<endl;
       exit(1);
     }
+    else
+      L<<Logger::Error<<"Chrooted to '"<<::arg()["chroot"]<<"'"<<endl;
   }
 
+  s_pidfname=::arg()["socket-dir"]+"/"+s_programname+".pid";
+  if(!s_pidfname.empty())
+    unlink(s_pidfname.c_str()); // remove possible old pid file
+  writePid();
+
+  makeControlChannelSocket( ::arg().asNum("processes") > 1 ? forks : -1);
+
   Utility::dropUserPrivs(newuid);
 
   makeThreadPipes();
@@ -2580,7 +2590,7 @@ int main(int argc, char **argv)
     ::arg().set("socket-group","Group of socket")="";
     ::arg().set("socket-mode", "Permissions for socket")="";
 
-    ::arg().set("socket-dir","Where the controlsocket will live")=LOCALSTATEDIR;
+    ::arg().set("socket-dir",string("Where the controlsocket will live, ")+LOCALSTATEDIR+" when unset and not chrooted" )="";
     ::arg().set("delegation-only","Which domains we only accept delegations from")="";
     ::arg().set("query-local-address","Source IP address for sending queries")="0.0.0.0";
     ::arg().set("query-local-address6","Source IPv6 address for sending queries. IF UNSET, IPv6 WILL NOT BE USED FOR OUTGOING QUERIES")="";
@@ -2655,6 +2665,18 @@ int main(int argc, char **argv)
 
     ::arg().parse(argc,argv);
 
+    if( !::arg()["chroot"].empty() && !::arg()["api-config-dir"].empty() && !::arg().mustDo("api-readonly") )  {
+      L<<Logger::Error<<"Using chroot and a writable API is not possible"<<endl;
+      exit(EXIT_FAILURE);
+    }
+
+    if (::arg()["socket-dir"].empty()) {
+      if (::arg()["chroot"].empty())
+        ::arg().set("socket-dir") = LOCALSTATEDIR;
+      else
+        ::arg().set("socket-dir") = "/";
+    }
+
     ::arg().set("delegation-only")=toLower(::arg()["delegation-only"]);
 
     if(::arg().asNum("threads")==1)
index c80d643971c0a1550e47e836f9ab3806216ee685..3123a49358da4969004ad3676cb2308771bd7b2d 100644 (file)
@@ -935,6 +935,11 @@ string RecursorControlParser::getAnswer(const string& question, RecursorControlP
   }
 
   if(cmd=="reload-acls") {
+    if(!::arg()["chroot"].empty()) {
+      L<<Logger::Error<<"Unable to reload ACL when chroot()'ed, requested via control channel"<<endl;
+      return "Unable to reload ACL when chroot()'ed, please restart\n";
+    }
+
     try {
       parseACLs();
     } 
@@ -983,6 +988,10 @@ string RecursorControlParser::getAnswer(const string& question, RecursorControlP
   }
 
   if(cmd=="reload-zones") {
+    if(!::arg()["chroot"].empty()) {
+      L<<Logger::Error<<"Unable to reload zones and forwards when chroot()'ed, requested via control channel"<<endl;
+      return "Unable to reload zones and forwards when chroot()'ed, please restart\n";
+    }
     return reloadAuthAndForwards();
   }
 
index df370e0fd28867f6b75da5b0e8d43e9d161edf4c..207c364ae96a26a81ca1a5b9935103789ef2a599 100644 (file)
@@ -44,7 +44,8 @@ static void initArguments(int argc, char** argv)
 {
   arg().set("config-dir","Location of configuration directory (recursor.conf)")=SYSCONFDIR;
 
-  arg().set("socket-dir","Where the controlsocket will live")=LOCALSTATEDIR;
+  arg().set("socket-dir",string("Where the controlsocket will live, ")+LOCALSTATEDIR+" when unset and not chrooted" )="";
+  arg().set("chroot","switch to chroot jail")="";
   arg().set("process","When controlling multiple recursors, the target process number")="";
   arg().set("timeout", "Number of seconds to wait for the recursor to respond")="5";
   arg().set("config-name","Name of this virtual configuration - will rename the binary image")="";
@@ -64,9 +65,21 @@ static void initArguments(int argc, char** argv)
   
   cleanSlashes(configname);
 
-  if(!::arg().preParseFile(configname.c_str(), "socket-dir", LOCALSTATEDIR)) 
+  if(!::arg().preParseFile(configname.c_str(), "socket-dir", ""))
     cerr<<"Warning: unable to parse configuration file '"<<configname<<"'"<<endl;
+  if(!::arg().preParseFile(configname.c_str(), "chroot", ""))
+    cerr<<"Warning: unable to parse configuration file '"<<configname<<"'"<<endl;
+
   arg().laxParse(argc,argv);   // make sure the commandline wins
+
+  if (::arg()["socket-dir"].empty()) {
+    if (::arg()["chroot"].empty())
+      ::arg().set("socket-dir") = LOCALSTATEDIR;
+    else
+      ::arg().set("socket-dir") = ::arg()["chroot"] + "/";
+  } else if (!::arg()["chroot"].empty()) {
+    ::arg().set("socket-dir") = ::arg()["chroot"] + "/" + ::arg()["socket-dir"];
+  }
 }
 
 int main(int argc, char** argv)
index 4ad1e6d966eebff0d09117795f01e429d891daa9..7a2b99b2a7d07223b3da7e7f2c7f324f96e5e5a9 100644 (file)
@@ -133,7 +133,17 @@ static void writePid(void)
   if(!::arg().mustDo("write-pid"))
     return;
 
-  string fname=::arg()["socket-dir"]+"/"+s_programname+".pid";
+  string fname=::arg()["socket-dir"];
+  if (::arg()["socket-dir"].empty()) {
+    if (::arg()["chroot"].empty())
+      fname = LOCALSTATEDIR;
+    else
+      fname = ::arg()["chroot"] + "/";
+  } else if (!::arg()["socket-dir"].empty() && !::arg()["chroot"].empty()) {
+    fname = ::arg()["chroot"] + ::arg()["socket-dir"];
+  }
+
+  fname += + "/" + s_programname + ".pid";
   ofstream of(fname.c_str());
   if(of)
     of<<getpid()<<endl;
@@ -347,7 +357,7 @@ static void UNIX_declareArguments()
 {
   ::arg().set("config-dir","Location of configuration directory (pdns.conf)")=SYSCONFDIR;
   ::arg().set("config-name","Name of this virtual configuration - will rename the binary image")="";
-  ::arg().set("socket-dir","Where the controlsocket will live")=LOCALSTATEDIR;
+  ::arg().set("socket-dir",string("Where the controlsocket will live, ")+LOCALSTATEDIR+" when unset and not chrooted" )="";
   ::arg().set("module-dir","Default directory for modules")=PKGLIBDIR;
   ::arg().set("chroot","If set, chroot to this directory for more security")="";
   ::arg().set("logging-facility","Log under a specific facility")="";