Add socket-uid and socket-gid service settings for controlling
authorDavin McCall <davmac@davmac.org>
Mon, 4 Jan 2016 13:50:24 +0000 (13:50 +0000)
committerDavin McCall <davmac@davmac.org>
Mon, 4 Jan 2016 13:50:24 +0000 (13:50 +0000)
activation socket ownership.

README
load_service.cc
service.cc
service.h

diff --git a/README b/README
index 5ef52567578f7521c0e0b30a6459cdc960ce701b..d39a84710c4a1f8ff71797e5caa77b3dfee84ff6 100644 (file)
--- a/README
+++ b/README
@@ -150,6 +150,17 @@ socket-permissions = (octal permissions mask)
    Normally this will be 600 (user access only), 660 (user and group
    access), or 666 (all users).
 
+socket-uid = (numeric user id or username)
+   Specifies the user that should own the activation socket. If socket-uid
+   is specified without also specifying socket-gid, then the socket group
+   is the primary group of the specified user (as found in the system user
+   database, normally /etc/passwd). If the socket owner is not specified,
+   the socket will be owned by the user id of the Dinit process.
+
+socket-gid = (numeric group id or group name)
+   Specifies the group of the activation socket. See discussion of
+   socket-uid.
+
 termsignal = HUP | INT | QUIT | USR1 | USR2
    Specifies an additional signal to send to the process when requesting it
    to terminate (applies to 'process' services only). SIGTERM is always
index 626f5b96039f18f746ba0054e67ba1be045e5eb7..79972ffe0e9a66475905315ae57a276e9b544a6e 100644 (file)
@@ -1,9 +1,16 @@
-#include "service.h"
 #include <algorithm>
 #include <string>
 #include <fstream>
 #include <locale>
 #include <iostream>
+#include <limits>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include "service.h"
 
 typedef std::string string;
 typedef std::string::iterator string_iterator;
@@ -172,6 +179,101 @@ static int signalNameToNumber(std::string &signame)
     return -1;
 }
 
+static const char * uid_err_msg = "Specified user id contains invalid numeric characters or is outside allowed range.";
+
+// Parse a userid parameter which may be a numeric user ID or a username. If a name, the
+// userid is looked up via the system user database (getpwnam() function). In this case,
+// the associated group is stored in the location specified by the group_p parameter iff
+// it is not null and iff it contains the value -1.
+static uid_t parse_uid_param(const std::string &param, const std::string &service_name, gid_t *group_p)
+{
+    // Could be a name or a numeric id. But we should assume numeric first, just in case
+    // a user manages to give themselves a username that parses as a number.
+    std::size_t ind = 0;
+    try {
+        // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
+        // is is probably safe to assume that valid values are positive. We'll also assume
+        // that the value range fits with "unsigned long long" since it seems unlikely
+        // that would ever not be the case.
+        //
+        // TODO perhaps write a number parser, since even the unsigned variants of the C/C++
+        //      functions accept a leading minus sign...
+        static_assert((uintmax_t)std::numeric_limits<uid_t>::max() <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "uid_t is too large");
+        unsigned long long v = std::stoull(param, &ind, 0);
+        if (v > static_cast<unsigned long long>(std::numeric_limits<uid_t>::max()) || ind != param.length()) {
+            throw ServiceDescriptionExc(service_name, uid_err_msg);
+        }
+        return v;
+    }
+    catch (std::out_of_range &exc) {
+        throw ServiceDescriptionExc(service_name, uid_err_msg);
+    }
+    catch (std::invalid_argument &exc) {
+        // Ok, so it doesn't look like a number: proceed...
+    }
+
+    errno = 0;
+    struct passwd * pwent = getpwnam(param.c_str());
+    if (pwent == nullptr) {
+        // Maybe an error, maybe just no entry.
+        if (errno == 0) {
+            throw new ServiceDescriptionExc(service_name, "Specified user \"" + param + "\" does not exist in system database.");
+        }
+        else {
+            throw new ServiceDescriptionExc(service_name, std::string("Error accessing user database: ") + strerror(errno));
+        }
+    }
+    
+    if (group_p && *group_p != (gid_t)-1) {
+        *group_p = pwent->pw_gid;
+    }
+    
+    return pwent->pw_uid;
+}
+
+static const char * gid_err_msg = "Specified group id contains invalid numeric characters or is outside allowed range.";
+
+static gid_t parse_gid_param(const std::string &param, const std::string &service_name)
+{
+    // Could be a name or a numeric id. But we should assume numeric first, just in case
+    // a user manages to give themselves a username that parses as a number.
+    std::size_t ind = 0;
+    try {
+        // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
+        // is is probably safe to assume that valid values are positive. We'll also assume
+        // that the value range fits with "unsigned long long" since it seems unlikely
+        // that would ever not be the case.
+        //
+        // TODO perhaps write a number parser, since even the unsigned variants of the C/C++
+        //      functions accept a leading minus sign...
+        unsigned long long v = std::stoull(param, &ind, 0);
+        if (v > static_cast<unsigned long long>(std::numeric_limits<gid_t>::max()) || ind != param.length()) {
+            throw ServiceDescriptionExc(service_name, gid_err_msg);
+        }
+        return v;
+    }
+    catch (std::out_of_range &exc) {
+        throw ServiceDescriptionExc(service_name, gid_err_msg);
+    }
+    catch (std::invalid_argument &exc) {
+        // Ok, so it doesn't look like a number: proceed...
+    }
+
+    errno = 0;
+    struct group * grent = getgrnam(param.c_str());
+    if (grent == nullptr) {
+        // Maybe an error, maybe just no entry.
+        if (errno == 0) {
+            throw new ServiceDescriptionExc(service_name, "Specified group \"" + param + "\" does not exist in system database.");
+        }
+        else {
+            throw new ServiceDescriptionExc(service_name, std::string("Error accessing group database: ") + strerror(errno));
+        }
+    }
+    
+    return grent->gr_gid;
+}
+
 // Find a service record, or load it from file. If the service has
 // dependencies, load those also.
 //
@@ -222,6 +324,10 @@ ServiceRecord * ServiceSet::loadServiceRecord(const char * name)
     bool smooth_recovery = false;
     string socket_path;
     int socket_perms = 0666;
+    // Note: Posix allows that uid_t and gid_t may be unsigned types, but eg chown uses -1 as an
+    // invalid value, so it's safe to assume that we can do the same:
+    uid_t socket_uid = -1;
+    gid_t socket_gid = -1;
     
     string line;
     ifstream service_file;
@@ -278,6 +384,14 @@ ServiceRecord * ServiceSet::loadServiceRecord(const char * name)
                         throw ServiceDescriptionExc(name, "socket-permissions: Badly-formed or out-of-range numeric value");
                     }
                 }
+                else if (setting == "socket-uid") {
+                    string sock_uid_s = read_setting_value(i, end, nullptr);
+                    socket_uid = parse_uid_param(sock_uid_s, name, &socket_gid);
+                }
+                else if (setting == "socket-gid") {
+                    string sock_gid_s = read_setting_value(i, end, nullptr);
+                    socket_gid = parse_gid_param(sock_gid_s, name);
+                }
                 else if (setting == "stop-command") {
                     stop_command = read_setting_value(i, end, &stop_command_offsets);
                 }
@@ -376,7 +490,7 @@ ServiceRecord * ServiceSet::loadServiceRecord(const char * name)
                 rval->setOnstartFlags(onstart_flags);
                 rval->setExtraTerminationSignal(term_signal);
                 rval->set_pid_file(std::move(pid_file));
-                rval->set_socket_details(std::move(socket_path), socket_perms);
+                rval->set_socket_details(std::move(socket_path), socket_perms, socket_uid, socket_gid);
                 *iter = rval;
                 break;
             }
index 14685dde6661798a4820669b5ce37250e59a7f16..4fec13be7f7a51d20fa81a7e054f64dfa4389958 100644 (file)
@@ -367,6 +367,14 @@ bool ServiceRecord::open_socket() noexcept
     
     free(name);
     
+    // POSIX (1003.1, 2013) says that fchown and fchmod don't necesarily work on sockets. We have to
+    // use chown and chmod instead.
+    if (chown(saddrname, socket_uid, socket_gid)) {
+        log(LogLevel::ERROR, service_name, ": Error setting activation socket owner/group: ", strerror(errno));
+        close(sockfd);
+        return false;
+    }
+    
     if (chmod(saddrname, socket_perms) == -1) {
         log(LogLevel::ERROR, service_name, ": Error setting activation socket permissions: ", strerror(errno));
         close(sockfd);
index 024a38095efdf2cff3ef06759baf386099e9e42e..cbeab725f0b0da35db41555c744fcf67f1aeb534 100644 (file)
--- a/service.h
+++ b/service.h
@@ -32,7 +32,8 @@
  *
  * A scripted service is in the STARTING/STOPPING states during the script execution.
  * A process service is in the STOPPING state when it has been signalled to stop, and is
- *       in the STARTING state when waiting for dependencies to start.
+ * in the STARTING state when waiting for dependencies to start or for the exec() call in
+ * the forked child to complete and return a status.
  */
 
 struct OnstartFlags {
@@ -168,9 +169,9 @@ class ServiceRecord
     string logfile;           // log file name, empty string specifies /dev/null
     bool auto_restart : 1;    // whether to restart this (process) if it dies unexpectedly
     bool smooth_recovery : 1; // whether the service process can restart without bringing down service
+    
     bool pinned_stopped : 1;
     bool pinned_started : 1;
-    
     bool waiting_for_deps : 1;  // if STARTING, whether we are waiting for dependencies (inc console) to start
     bool waiting_for_execstat : 1;  // if we are waiting for exec status after fork()
     bool doing_recovery : 1;    // if we are currently recovering a BGPROCESS (restarting process, while
@@ -209,6 +210,8 @@ class ServiceRecord
     
     string socket_path; // path to the socket for socket-activation service
     int socket_perms;   // socket permissions ("mode")
+    uid_t socket_uid = -1;  // socket user id or -1
+    gid_t socket_gid = -1;  // sockget group id or -1
 
     // Implementation details
     
@@ -397,10 +400,12 @@ class ServiceRecord
         this->pid_file = pid_file;
     }
     
-    void set_socket_details(string &&socket_path, int socket_perms) noexcept
+    void set_socket_details(string &&socket_path, int socket_perms, uid_t socket_uid, uid_t socket_gid) noexcept
     {
         this->socket_path = socket_path;
         this->socket_perms = socket_perms;
+        this->socket_uid = socket_uid;
+        this->socket_gid = socket_gid;
     }
 
     const char *getServiceName() const noexcept { return service_name.c_str(); }