From: Davin McCall Date: Sat, 20 Jan 2018 20:42:11 +0000 (+0000) Subject: Support for running services as a different user (run-as=...). X-Git-Tag: v0.08~14 X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=bd7762913aadb274dd89b92f15c31d0986d6d9c0;p=oweals%2Fdinit.git Support for running services as a different user (run-as=...). --- diff --git a/README b/README index 9d3fbd1..925d8d9 100644 --- a/README +++ b/README @@ -135,11 +135,16 @@ Parameter values are interpreted literally, except that: non-collapsing whitespace, double-quote marks, and backslashes in the parameter value. -Parameters are: +Some available parameters are: type = process | bgprocess | scripted | internal command = ... +stop-command = ... +run-as = (user-id) +restart = (boolean) +smooth-recovery = (boolean) logfile = ... +pid-file = ... options = ... depends-on = (service name) depends-ms = (service name) @@ -152,6 +157,11 @@ command = (external script or executable, and arguments) stop-command = (external script or executable, and arguments) For a 'scripted' service, this command is run to stop the service. +run-as = (user-id) + Specifies which user to run the process(es) for this service as. The group + id for the process will also be set to the primary group of the specified + user. + restart = yes | true | no | false Specifies whether the service should automatically restart if it becomes stopped (for any reason, including being explicitly requested to stop). @@ -165,36 +175,9 @@ smooth-recovery = yes | true | no | false the service does not reach the stopped state when the process terminates unexpectedly). -restart-delay = XXX.YYYY - Specifies the minimum time in seconds between automatic restarts. The - default is 0.2 (i.e. 200ms). This prevents Dinit from consuming processor - cycles when a process continuously fails immediately after it starts. - -restart-limit-interval = XXX.YYYY - Specifies the interval, in seconds, over which restarts are limited. If a - process automatically restarts more than a certain number of times (default - 3) in this time interval, it will not restart again. The default value is - 10 seconds. Use this to prevent broken services from continuously - restarting ad infinitum. - -restart-limit-count = NNN - Specifies the maximum number of times that a service can automatically - restart over the interval specified by restart-limit-interval (default of - 10 seconds). Specify a value of 0 to disable the restart limit. - -stop-timeout = XXX.YYYY (or XXX,YYYY) - Specifies the time in seconds allowed for the service to stop. If the - service takes longer than this, its process group is sent a SIGKILL signal - which should cause it to terminate immediately. The timeout period begins - only when all dependent services have already stopped. The default stop - timeout is 10 seconds. - -start-timeout = XXX.YYYY (or XXX,YYYY) - Specifies the time in seconds allowed for the service to start. IF the - service startup takes longer than this, its process group is sent a - SIGINT signal and transitions to the "stopping" state. if it fails to stop - within the period specified by the stop-timeout setting, it is sent a - SIGKILL signal. +logfile = (log file path) + Specifies the log file for the service. Output from the service process + will go this file. pid-file = (path to file) For "bgprocess" type services only; specifies the path of the file where @@ -219,35 +202,6 @@ waits-for = (service name) for this service. Starting this service will automatically start the named service. -socket-listen = (socket path) - Pre-open a socket for the service and pass it to the service using the - Systemd activation protocol. This by itself does not give so called - "socket activation", but does allow that any process trying to connect - to the specified socket will be able to do so, even before the service - is properly prepared to accept connections. - -socket-permissions = (octal permissions mask) - Gives the permissions for the socket specified using socket-listen. - 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 - sent along with the specified signal, unless the 'nosigterm' setting is - set true. - options = ( runs-on-console | nosigterm | starts-rwfs | starts-log ) ... Specifies various options for this service: @@ -302,9 +256,7 @@ options = ( runs-on-console | nosigterm | starts-rwfs | starts-log ) ... startup. This is meaningful only for scripted and bgprocess services. -logfile = (log file path) - Specifies the log file for the service. Output from the service process - will go this file. +Please see the manual page for a full list of service parameters and options. Controlling services diff --git a/doc/manpages/dinit.8 b/doc/manpages/dinit.8 index 4a477a8..099cb4a 100644 --- a/doc/manpages/dinit.8 +++ b/doc/manpages/dinit.8 @@ -118,6 +118,10 @@ services. Specifies the command to stop the service. Applicable only to \fBscripted\fR services. .TP +\fBrun-as\fR = \fIuser-id\fR +Specifies which user to run the process(es) for this service as. The group id +for the process will also be set to the primary group of the specified user. +.TP \fBrestart\fR = {yes | true | no | false} Indicates whether the service should automatically restart if it stops for any reason (including unexpected process termination, service dependency diff --git a/src/baseproc-service.cc b/src/baseproc-service.cc index 14933a5..481e9f6 100644 --- a/src/baseproc-service.cc +++ b/src/baseproc-service.cc @@ -121,7 +121,8 @@ bool base_process_service::start_ps_process(const std::vector &cmd } if (forkpid == 0) { - run_child_proc(cmd.data(), logfile, on_console, pipefd[1], control_socket[1], socket_fd); + run_child_proc(cmd.data(), logfile, on_console, pipefd[1], control_socket[1], socket_fd, + run_as_uid, run_as_gid); } else { // Parent process diff --git a/src/includes/proc-service.h b/src/includes/proc-service.h index 8074e8a..606c5b1 100644 --- a/src/includes/proc-service.h +++ b/src/includes/proc-service.h @@ -1,3 +1,5 @@ +#include + #include "service.h" // Given a string and a list of pairs of (start,end) indices for each argument in that string, @@ -22,6 +24,7 @@ class process_restart_timer : public eventloop_t::timer_impl). 0 to disable. time_val start_timeout = {60, 0}; // default of 1 minute + uid_t run_as_uid = -1; + gid_t run_as_gid = -1; + pid_t pid = -1; // PID of the process. If state is STARTING or STOPPING, // this is PID of the service script; otherwise it is the // PID of the process itself (process service). @@ -172,10 +178,18 @@ class base_process_service : public service_record this->term_signal = signo; } + // Set the uid/gid that the service process will be run as + void set_run_as_uid_gid(uid_t uid, gid_t gid) noexcept + { + run_as_uid = uid; + run_as_gid = gid; + } + // The restart/stop timer expired. void timer_expired() noexcept; }; +// Standard process service. class process_service : public base_process_service { virtual void handle_exit_status(int exit_status) noexcept override; @@ -197,6 +211,7 @@ class process_service : public base_process_service } }; +// Bgproc (self-"backgrounding", i.e. double-forking) process service class bgproc_service : public base_process_service { virtual void handle_exit_status(int exit_status) noexcept override; @@ -226,6 +241,7 @@ class bgproc_service : public base_process_service } }; +// Service which is started and stopped via separate commands class scripted_service : public base_process_service { virtual void handle_exit_status(int exit_status) noexcept override; diff --git a/src/includes/service.h b/src/includes/service.h index 1b2bd4e..64da4bc 100644 --- a/src/includes/service.h +++ b/src/includes/service.h @@ -330,8 +330,17 @@ class service_record // dep_failed: whether failure is recorded due to a dependency failing void failed_to_start(bool dep_failed = false) noexcept; + // Run a child process (call after forking). + // - args specifies the program arguments including the executable (argv[0]) + // - logfile specifies the logfile + // - on_console: if true, process is run with access to console + // - wpipefd: if the exec is unsuccessful, or another error occurs beforehand, the + // error number (errno) is written to this file descriptor + // - csfd: the control socket fd; may be -1 to inhibit passing of control socket + // - socket_fd: the pre-opened socket file descriptor (may be -1) + // - uid/gid: the identity to run the process as (may be both -1, otherwise both must be valid) void run_child_proc(const char * const *args, const char *logfile, bool on_console, int wpipefd, - int csfd, int socket_fd) noexcept; + int csfd, int socket_fd, uid_t uid, gid_t gid) noexcept; // A dependency has reached STARTED state void dependency_started() noexcept; diff --git a/src/load_service.cc b/src/load_service.cc index 82dae1a..59322ea 100644 --- a/src/load_service.cc +++ b/src/load_service.cc @@ -411,6 +411,9 @@ service_record * dirload_service_set::load_service(const char * name) timespec stop_timeout = { .tv_sec = 10, .tv_nsec = 0 }; timespec start_timeout = { .tv_sec = 60, .tv_nsec = 0 }; + uid_t run_as_uid = -1; + gid_t run_as_gid = -1; + string line; ifstream service_file; service_file.exceptions(ios::badbit | ios::failbit); @@ -586,6 +589,10 @@ service_record * dirload_service_set::load_service(const char * name) string starttimeout_str = read_setting_value(i, end, nullptr); parse_timespec(starttimeout_str, name, "start-timeout", start_timeout); } + else if (setting == "run-as") { + string run_as_str = read_setting_value(i, end, nullptr); + run_as_uid = parse_uid_param("run-as", name, &run_as_gid); + } else { throw service_description_exc(name, "Unknown setting: " + setting); } @@ -614,6 +621,7 @@ service_record * dirload_service_set::load_service(const char * name) rvalps->set_start_timeout(start_timeout); rvalps->set_start_interruptible(start_is_interruptible); rvalps->set_extra_termination_signal(term_signal); + rvalps->set_run_as_uid_gid(run_as_uid, run_as_gid); rval = rvalps; } else if (service_type == service_type_t::BGPROCESS) { @@ -626,6 +634,7 @@ service_record * dirload_service_set::load_service(const char * name) rvalps->set_start_timeout(start_timeout); rvalps->set_start_interruptible(start_is_interruptible); rvalps->set_extra_termination_signal(term_signal); + rvalps->set_run_as_uid_gid(run_as_uid, run_as_gid); rval = rvalps; } else if (service_type == service_type_t::SCRIPTED) { @@ -636,6 +645,7 @@ service_record * dirload_service_set::load_service(const char * name) rvalps->set_start_timeout(start_timeout); rvalps->set_start_interruptible(start_is_interruptible); rvalps->set_extra_termination_signal(term_signal); + rvalps->set_run_as_uid_gid(run_as_uid, run_as_gid); rval = rvalps; } else { diff --git a/src/run-child-proc.cc b/src/run-child-proc.cc index 66f5199..c1c43bb 100644 --- a/src/run-child-proc.cc +++ b/src/run-child-proc.cc @@ -10,7 +10,7 @@ #include "service.h" void service_record::run_child_proc(const char * const *args, const char *logfile, bool on_console, - int wpipefd, int csfd, int socket_fd) noexcept + int wpipefd, int csfd, int socket_fd, uid_t uid, gid_t gid) noexcept { // Child process. Must not allocate memory (or otherwise risk throwing any exception) // from here until exit(). @@ -55,7 +55,7 @@ void service_record::run_child_proc(const char * const *args, const char *logfil } if (socket_fd != -1) { - + // If we passing a pre-opened socket, it has to be fd number 3. (Thanks, systemd). if (dup2(socket_fd, 3) == -1) goto failure_out; if (socket_fd != 3) { close(socket_fd); @@ -118,6 +118,11 @@ void service_record::run_child_proc(const char * const *args, const char *logfil tcsetpgrp(0, getpgrp()); } + if (uid != -1) { + if (setreuid(uid, uid) != 0) goto failure_out; + if (setregid(gid, gid) != 0) goto failure_out; + } + sigprocmask(SIG_SETMASK, &sigwait_set, nullptr); execvp(args[0], const_cast(args)); diff --git a/src/tests/test-run-child-proc.cc b/src/tests/test-run-child-proc.cc index db4dc32..2817cc6 100644 --- a/src/tests/test-run-child-proc.cc +++ b/src/tests/test-run-child-proc.cc @@ -3,7 +3,7 @@ // Stub out run_child_proc function, for testing purposes. void service_record::run_child_proc(const char * const *args, const char *logfile, bool on_console, - int wpipefd, int csfd, int socket_fd) noexcept + int wpipefd, int csfd, int socket_fd, uid_t uid, gid_t gid) noexcept { }