core: add support for temporary privilege drop and elevation

Added "user" and "group" options to general section of configuration file.
NFD will attempt to set the effective group and user id to these values
after initializing all management modules.

Added privilege helper to drop and temporarily elevate privileges on demand.

Updated README.md with instructions to configure NFD to drop privileges.

Added handler for general confguration file section.

refs: #1370

Change-Id: Id27140ad2dc2ca14751058691511132a35649d58
diff --git a/daemon/core/privilege-helper.cpp b/daemon/core/privilege-helper.cpp
new file mode 100644
index 0000000..f48c4d0
--- /dev/null
+++ b/daemon/core/privilege-helper.cpp
@@ -0,0 +1,203 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "privilege-helper.hpp"
+#include "core/logger.hpp"
+
+#include <pwd.h>
+#include <grp.h>
+
+namespace nfd {
+
+NFD_LOG_INIT("PrivilegeHelper");
+
+uid_t PrivilegeHelper::s_normalUid;
+gid_t PrivilegeHelper::s_normalGid;
+
+uid_t PrivilegeHelper::s_privilegedUid;
+gid_t PrivilegeHelper::s_privilegedGid;
+
+void
+PrivilegeHelper::initialize(const std::string& userName, const std::string& groupName)
+{
+  static const size_t MAX_GROUP_BUFFER_SIZE = 16384; // 16kB
+  static const size_t MAX_PASSWD_BUFFER_SIZE = 16384;
+
+  static const size_t FALLBACK_GROUP_BUFFER_SIZE = 1024;
+  static const size_t FALLBACK_PASSWD_BUFFER_SIZE = 1024;
+
+  NFD_LOG_TRACE("initializing privilege helper with user \"" << userName << "\""
+                << " group \"" << groupName << "\"");
+
+  s_privilegedUid = ::geteuid();
+  s_privilegedGid = ::getegid();
+
+  NFD_LOG_TRACE("saving effective uid=" << s_privilegedUid << " gid=" << s_privilegedGid);
+
+  s_normalUid = s_privilegedUid;
+  s_normalGid = s_privilegedGid;
+
+  // workflow from man getpwnam_r
+
+  if (!groupName.empty())
+    {
+      static int groupSize = ::sysconf(_SC_GETGR_R_SIZE_MAX);
+
+      if (groupSize == -1)
+        {
+          groupSize = FALLBACK_GROUP_BUFFER_SIZE;
+        }
+
+      std::vector<char> groupBuffer(groupSize);
+      struct group group;
+      struct group* groupResult = 0;
+
+      int errorCode = getgrnam_r(groupName.c_str(), &group,
+                                 &groupBuffer[0], groupSize, &groupResult);
+
+      while (errorCode == ERANGE)
+        {
+          if (groupBuffer.size() * 2 > MAX_GROUP_BUFFER_SIZE)
+            {
+              throw PrivilegeHelper::Error("Cannot allocate large enough buffer for struct group");
+            }
+
+          groupBuffer.resize(groupBuffer.size() * 2);
+
+          errorCode = getgrnam_r(groupName.c_str(), &group,
+                                 &groupBuffer[0], groupBuffer.size(), &groupResult);
+        }
+
+      if (errorCode != 0 || !groupResult)
+        {
+          throw PrivilegeHelper::Error("Failed to get gid for \"" + groupName + "\"");
+        }
+
+      s_normalGid = group.gr_gid;
+    }
+
+  if (!userName.empty())
+    {
+      static int passwdSize = ::sysconf(_SC_GETPW_R_SIZE_MAX);
+
+      if (passwdSize == -1)
+        {
+          passwdSize = FALLBACK_PASSWD_BUFFER_SIZE;
+        }
+
+      std::vector<char> passwdBuffer(passwdSize);
+      struct passwd passwd;
+      struct passwd* passwdResult = 0;
+
+      int errorCode =
+        getpwnam_r(userName.c_str(), &passwd,
+                   &passwdBuffer[0], passwdBuffer.size(), &passwdResult);
+
+      while (errorCode == ERANGE)
+        {
+          if (passwdBuffer.size() * 2 > MAX_PASSWD_BUFFER_SIZE)
+            {
+              throw PrivilegeHelper::Error("Cannot allocate large enough buffer for struct passwd");
+            }
+
+          passwdBuffer.resize(passwdBuffer.size() * 2);
+
+          errorCode = getpwnam_r(userName.c_str(), &passwd,
+                                 &passwdBuffer[0], passwdBuffer.size(), &passwdResult);
+        }
+
+      if (errorCode != 0 || !passwdResult)
+        {
+          throw PrivilegeHelper::Error("Failed to get uid for \"" + userName + "\"");
+        }
+
+      s_normalUid = passwd.pw_uid;
+    }
+}
+
+void
+PrivilegeHelper::drop()
+{
+  NFD_LOG_TRACE("dropping to effective gid=" << s_normalGid);
+  if (::setegid(s_normalGid) != 0)
+    {
+      std::stringstream error;
+      error << "Failed to drop to effective gid=" << s_normalGid;
+
+      throw PrivilegeHelper::Error(error.str());
+    }
+
+  NFD_LOG_TRACE("dropping to effective uid=" << s_normalUid);
+  if (::seteuid(s_normalUid) != 0)
+    {
+      std::stringstream error;
+      error << "Failed to drop to effective uid=" << s_normalUid;
+
+      throw PrivilegeHelper::Error(error.str());
+    }
+
+  NFD_LOG_INFO("dropped to effective uid=" << ::geteuid() << " gid=" << ::getegid());
+}
+
+void
+PrivilegeHelper::raise()
+{
+  NFD_LOG_TRACE("elevating to effective uid=" << s_privilegedUid);
+  if (::seteuid(s_privilegedUid) != 0)
+    {
+      std::stringstream error;
+      error << "Failed to elevate to effective uid=" << s_privilegedUid;
+
+      throw PrivilegeHelper::Error(error.str());
+    }
+
+  NFD_LOG_TRACE("elevating to effective gid=" << s_privilegedGid);
+  if (::setegid(s_privilegedGid) != 0)
+    {
+      std::stringstream error;
+      error << "Failed to elevate to effective gid=" << s_privilegedGid;
+
+      throw PrivilegeHelper::Error(error.str());
+    }
+  NFD_LOG_INFO("elevated to effective uid=" << ::geteuid() << " gid=" << ::getegid());
+}
+
+void
+PrivilegeHelper::runElevated(function<void()> f)
+{
+  raise();
+
+  try
+    {
+      f();
+    }
+  catch (...)
+    {
+      drop();
+      throw;
+    }
+  drop();
+}
+
+} // namespace nfd
diff --git a/daemon/core/privilege-helper.hpp b/daemon/core/privilege-helper.hpp
new file mode 100644
index 0000000..92bd53d
--- /dev/null
+++ b/daemon/core/privilege-helper.hpp
@@ -0,0 +1,84 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#ifndef NFD_CORE_PRIVILEGE_HELPER_HPP
+#define NFD_CORE_PRIVILEGE_HELPER_HPP
+
+#include "common.hpp"
+
+#include <unistd.h>
+
+namespace nfd {
+
+class PrivilegeHelper
+{
+public:
+
+  /// \brief PrivilegeHelper::Error represents a serious seteuid/gid failure and
+  ///        should only be caught by main in as part of a graceful program termination.
+  class Error
+  {
+  public:
+    explicit
+    Error(const std::string& what)
+      : m_whatMessage(what)
+    {
+    }
+
+    const char*
+    what() const
+    {
+      return m_whatMessage.c_str();
+    }
+
+  private:
+    const std::string m_whatMessage;
+  };
+
+  static void
+  initialize(const std::string& userName, const std::string& groupName);
+
+  static void
+  drop();
+
+  static void
+  runElevated(function<void()> f);
+
+private:
+
+  static void
+  raise();
+
+private:
+
+  static uid_t s_normalUid;
+  static gid_t s_normalGid;
+
+  static uid_t s_privilegedUid;
+  static gid_t s_privilegedGid;
+};
+
+} // namespace nfd
+
+#endif // NFD_CORE_PRIVILEGE_HELPER_HPP
diff --git a/daemon/main.cpp b/daemon/main.cpp
index ebea105..fdf14b5 100644
--- a/daemon/main.cpp
+++ b/daemon/main.cpp
@@ -28,6 +28,7 @@
 #include "version.hpp"
 #include "core/logger.hpp"
 #include "core/global-io.hpp"
+#include "core/privilege-helper.hpp"
 #include "fw/forwarder.hpp"
 #include "mgmt/internal-face.hpp"
 #include "mgmt/fib-manager.hpp"
@@ -35,6 +36,7 @@
 #include "mgmt/strategy-choice-manager.hpp"
 #include "mgmt/status-server.hpp"
 #include "core/config-file.hpp"
+#include "mgmt/general-config-section.hpp"
 
 namespace nfd {
 
@@ -60,6 +62,8 @@
     m_forwarder = make_shared<Forwarder>();
 
     initializeManagement(configFile);
+
+    PrivilegeHelper::drop();
   }
 
 
@@ -118,6 +122,8 @@
                                                boost::ref(*m_forwarder));
 
     ConfigFile config((IgnoreRibAndLogSections()));
+
+    general::setConfigFile(config);
     m_internalFace->getValidator().setConfigFile(config);
 
     m_forwarder->addFace(m_internalFace);
@@ -289,6 +295,16 @@
     NFD_LOG_FATAL(e.what());
     return 2;
   }
+  catch (const PrivilegeHelper::Error& e) {
+    // PrivilegeHelper::Errors do not inherit from std::exception
+    // and represent seteuid/gid failures
+
+    NFD_LOG_FATAL(e.what());
+    return 3;
+  }
+
+
+
 
   boost::asio::signal_set signalSet(getGlobalIoService());
   signalSet.add(SIGINT);
@@ -302,9 +318,13 @@
   try {
     getGlobalIoService().run();
   }
-  catch (std::exception& e) {
+  catch (const std::exception& e) {
     NFD_LOG_FATAL(e.what());
-    return 3;
+    return 4;
+  }
+  catch (const PrivilegeHelper::Error& e) {
+    NFD_LOG_FATAL(e.what());
+    return 5;
   }
 
   return 0;
diff --git a/daemon/mgmt/general-config-section.cpp b/daemon/mgmt/general-config-section.cpp
new file mode 100644
index 0000000..02f45e8
--- /dev/null
+++ b/daemon/mgmt/general-config-section.cpp
@@ -0,0 +1,106 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "general-config-section.hpp"
+
+#include "common.hpp"
+#include "core/logger.hpp"
+#include "core/privilege-helper.hpp"
+#include "core/config-file.hpp"
+
+namespace nfd {
+
+namespace general {
+
+NFD_LOG_INIT("GeneralConfigSection");
+
+static void
+onConfig(const ConfigSection& configSection,
+         bool isDryRun,
+         const std::string& filename)
+{
+  // general
+  // {
+  //    ; user "ndn-user"
+  //    ; group "ndn-user"
+  // }
+
+  std::string user;
+  std::string group;
+
+  for (ConfigSection::const_iterator i = configSection.begin();
+       i != configSection.end();
+       ++i)
+    {
+      if (i->first == "user")
+        {
+          try
+            {
+              user = i->second.get_value<std::string>("user");
+
+              if (user.empty())
+                {
+                  throw ConfigFile::Error("Invalid value for \"user\""
+                                          " in \"general\" section");
+                }
+            }
+          catch (const boost::property_tree::ptree_error& error)
+            {
+              throw ConfigFile::Error("Invalid value for \"user\""
+                                      " in \"general\" section");
+            }
+        }
+      else if (i->first == "group")
+        {
+          try
+            {
+              group = i->second.get_value<std::string>("group");
+
+              if (group.empty())
+                {
+                  throw ConfigFile::Error("Invalid value for \"group\""
+                                          " in \"general\" section");
+                }
+            }
+          catch (const boost::property_tree::ptree_error& error)
+            {
+              throw ConfigFile::Error("Invalid value for \"group\""
+                                      " in \"general\" section");
+            }
+        }
+    }
+  NFD_LOG_TRACE("using user \"" << user << "\" group \"" << group << "\"");
+
+  PrivilegeHelper::initialize(user, group);
+}
+
+void
+setConfigFile(ConfigFile& configFile)
+{
+  configFile.addSectionHandler("general", &onConfig);
+}
+
+} // namespace general
+
+} // namespace nfd
diff --git a/daemon/mgmt/general-config-section.hpp b/daemon/mgmt/general-config-section.hpp
new file mode 100644
index 0000000..6ce5473
--- /dev/null
+++ b/daemon/mgmt/general-config-section.hpp
@@ -0,0 +1,41 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#ifndef NFD_MGMT_GENERAL_CONFIG_SECTION_HPP
+#define NFD_MGMT_GENERAL_CONFIG_SECTION_HPP
+
+namespace nfd {
+
+class ConfigFile;
+
+namespace general {
+
+void
+setConfigFile(ConfigFile& configFile);
+
+} // namespace general
+
+} // namespace nfd
+
+#endif // NFD_MGMT_GENERAL_CONFIG_SECTION_HPP
diff --git a/docs/FAQ.rst b/docs/FAQ.rst
index 6004e85..e0b3ce9 100644
--- a/docs/FAQ.rst
+++ b/docs/FAQ.rst
@@ -69,6 +69,32 @@
 How to run NFD as non-root user?
 --------------------------------
 
+How to configure automatic dropping of privileges?
+++++++++++++++++++++++++++++++++++++++++++++++++++
+
+NFD can be configured to drop privileges whenever possible.  You can specify a user and/or
+group for NFD to change its *effective* user/group ID to in the ``general`` section of the
+configuration file. For example:
+
+::
+
+    general
+    {
+      user nobody
+      group nogroup
+    }
+
+will configure NFD to drop its effective user and group IDs to ``nobody`` and ``nogroup``,
+respectively.
+
+.. note::
+
+    **IMPORTANT:** NFD may regain elevated permissions as needed during normal
+    execution. Dropping privileges in this manner should not be considered a security
+    mechanism (a compromised NFD that was started as root can trivially return to
+    root). However, reducing privileges may limit any damaged caused by well intentioned,
+    but buggy, code.
+
 
 How to enable Ethernet Face Support?
 ++++++++++++++++++++++++++++++++++++
diff --git a/nfd.conf.sample.in b/nfd.conf.sample.in
index 7e18358..9a081e4 100644
--- a/nfd.conf.sample.in
+++ b/nfd.conf.sample.in
@@ -1,7 +1,13 @@
 ; The general section contains settings of nfd process.
-; general
-; {
-; }
+general
+{
+  ; Specify a user and/or group for NFD to drop privileges to
+  ; when not performing privileged tasks. NFD does not drop
+  ; privileges by default.
+
+  ; user ndn-user
+  ; group ndn-user
+}
 
 log
 {
diff --git a/tests/mgmt/general-config-section.cpp b/tests/mgmt/general-config-section.cpp
new file mode 100644
index 0000000..e3c8cc0
--- /dev/null
+++ b/tests/mgmt/general-config-section.cpp
@@ -0,0 +1,135 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014  Regents of the University of California,
+ *                     Arizona Board of Regents,
+ *                     Colorado State University,
+ *                     University Pierre & Marie Curie, Sorbonne University,
+ *                     Washington University in St. Louis,
+ *                     Beijing Institute of Technology
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "mgmt/general-config-section.hpp"
+
+#include "tests/test-common.hpp"
+
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(GeneralSectionConfig)
+
+BOOST_AUTO_TEST_CASE(TestConfig)
+{
+  const std::string CONFIG =
+    "general\n"
+    "{\n"
+    "  user nobody\n"
+    "  group nogroup\n"
+    "}\n";
+
+  ConfigFile configFile;
+
+  general::setConfigFile(configFile);
+  BOOST_CHECK_NO_THROW(configFile.parse(CONFIG, true, "test-general-config-section"));
+
+}
+
+BOOST_AUTO_TEST_CASE(TestDefaultConfig)
+{
+  const std::string CONFIG =
+    "general\n"
+    "{\n"
+    "}\n";
+
+  ConfigFile configFile;
+
+  general::setConfigFile(configFile);
+  BOOST_CHECK_NO_THROW(configFile.parse(CONFIG, true, "test-general-config-section"));
+}
+
+BOOST_AUTO_TEST_CASE(TestNoUserConfig)
+{
+  const std::string CONFIG =
+    "general\n"
+    "{\n"
+    "  group nogroup\n"
+    "}\n";
+
+  ConfigFile configFile;
+
+  general::setConfigFile(configFile);
+  BOOST_CHECK_NO_THROW(configFile.parse(CONFIG, true, "test-general-config-section"));
+}
+
+BOOST_AUTO_TEST_CASE(TestNoGroupConfig)
+{
+  const std::string CONFIG =
+    "general\n"
+    "{\n"
+    "  user nobody\n"
+    "}\n";
+
+  ConfigFile configFile;
+
+  general::setConfigFile(configFile);
+  BOOST_CHECK_NO_THROW(configFile.parse(CONFIG, true, "test-general-config-section"));
+}
+
+static bool
+checkExceptionMessage(const ConfigFile::Error& error, const std::string& expected)
+{
+  return error.what() == expected;
+}
+
+BOOST_AUTO_TEST_CASE(TestInvalidUserConfig)
+{
+  const std::string CONFIG =
+    "general\n"
+    "{\n"
+    "  user\n"
+    "}\n";
+
+  ConfigFile configFile;
+  general::setConfigFile(configFile);
+
+  const std::string expected = "Invalid value for \"user\" in \"general\" section";
+  BOOST_REQUIRE_EXCEPTION(configFile.parse(CONFIG, true, "test-general-config-section"),
+                          ConfigFile::Error,
+                          bind(&checkExceptionMessage, _1, expected));
+}
+
+BOOST_AUTO_TEST_CASE(TestInvalidGroupConfig)
+{
+  const std::string CONFIG =
+    "general\n"
+    "{\n"
+    "  group\n"
+    "}\n";
+
+  ConfigFile configFile;
+  general::setConfigFile(configFile);
+
+  const std::string expected = "Invalid value for \"group\" in \"general\" section";
+  BOOST_REQUIRE_EXCEPTION(configFile.parse(CONFIG, true, "test-general-config-section"),
+                          ConfigFile::Error,
+                          bind(&checkExceptionMessage, _1, expected));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+
+} // namespace nfd