From: Davin McCall Date: Mon, 4 Jan 2016 13:50:24 +0000 (+0000) Subject: Add socket-uid and socket-gid service settings for controlling X-Git-Tag: v0.01~54 X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=0c4790a44e5ca89e1c43bf5a551dbb3dc683bf57;p=oweals%2Fdinit.git Add socket-uid and socket-gid service settings for controlling activation socket ownership. --- diff --git a/README b/README index 5ef5256..d39a847 100644 --- 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 diff --git a/load_service.cc b/load_service.cc index 626f5b9..79972ff 100644 --- a/load_service.cc +++ b/load_service.cc @@ -1,9 +1,16 @@ -#include "service.h" #include #include #include #include #include +#include + +#include +#include +#include +#include + +#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 ¶m, 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::max() <= (uintmax_t)std::numeric_limits::max(), "uid_t is too large"); + unsigned long long v = std::stoull(param, &ind, 0); + if (v > static_cast(std::numeric_limits::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 ¶m, 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(std::numeric_limits::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; } diff --git a/service.cc b/service.cc index 14685dd..4fec13b 100644 --- a/service.cc +++ b/service.cc @@ -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); diff --git a/service.h b/service.h index 024a380..cbeab72 100644 --- 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(); }