core: Fixing regression in PrivilegeHelper that prevented running as non-root

PrivilegeHelper was properly initialized only when general section was
present.  When section was omitted, s_privilegedUid/s_privilegedGid/s_normalUid/s_normalGid
variables was not properly initialized (set to 0), resulting in
exception during the subsequent PrivilegeHelper::drop call.

Change-Id: Iffd7f05e7e6c86d227da2c9b7920ca6e3b607211
diff --git a/core/privilege-helper.cpp b/core/privilege-helper.cpp
new file mode 100644
index 0000000..6f87f43
--- /dev/null
+++ b/core/privilege-helper.cpp
@@ -0,0 +1,195 @@
+/* -*- 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 = ::geteuid();
+gid_t PrivilegeHelper::s_normalGid = ::getegid();
+
+uid_t PrivilegeHelper::s_privilegedUid = ::geteuid();
+gid_t PrivilegeHelper::s_privilegedGid = ::getegid();
+
+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 << "\"");
+
+  // 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