diff options
-rw-r--r-- | src/Makefile.am | 3 | ||||
-rw-r--r-- | src/com_start.c | 1 | ||||
-rw-r--r-- | src/genrc.8 | 25 | ||||
-rw-r--r-- | src/genrc.c | 10 | ||||
-rw-r--r-- | src/genrc.h | 2 | ||||
-rw-r--r-- | src/runas.c | 191 | ||||
-rw-r--r-- | src/sentinel.c | 2 |
7 files changed, 232 insertions, 2 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index f78a979..b565303 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -33,13 +33,14 @@ genrc_SOURCES = \ com_restart.c\ com_reload.c\ transform.c\ match_exact.c\ match_glob.c\ match_regex.c\ - sentinel.c + sentinel.c\ + runas.c AM_CPPFLAGS = @GRECS_INCLUDES@ LDADD = @GRECS_LDADD@ if COND_PCRE genrc_SOURCES += match_pcre.c diff --git a/src/com_start.c b/src/com_start.c index 65c1775..5744e39 100644 --- a/src/com_start.c +++ b/src/com_start.c @@ -109,12 +109,13 @@ com_start(void) system_error(errno, "fork"); return 1; } if (pid == 0) { char *argv[] = { SHELL, "-c", NULL, NULL }; argv[2] = genrc_command; + runas(); execvp(SHELL, argv); system_error(errno, "failed to exec %s", genrc_program); exit(127); } if (timedwaitpid(pid, &status)) { diff --git a/src/genrc.8 b/src/genrc.8 index f900639..00522ee 100644 --- a/src/genrc.8 +++ b/src/genrc.8 @@ -15,30 +15,35 @@ .\" along with genrc. If not, see <http://www.gnu.org/licenses/>. .TH GENRC 8 "May 17, 2018" "GENRC" "Genrc User Manual" .SH NAME genrc \- generic system initialization script helper .SH SYNOPSIS .nh +.na \fBgenrc\fR\ [\fB\-hv\fR]\ [\fB\-F\fR \fIPIDFILE\fR]\ [\fB\-P\fR \fISOURCE\fR]\ [\fB\-c\fR \fICOMMAND\fR]\ + [\fB\-g\fR \fIGROUP\fR[,\fIGROUP\fR...]]\ [\fB\-p\fR \fIPROGRAM\fR]\ [\fB\-t\fR \fISECONDS\fR]\ + [\fB\-u\fR \fIUSER\fR]\ [\fB\-\-command=\fICOMMAND\fR]\ [\fB\-\-create\-pidfile=\fIPIDFILE\fR]\ + [\fB\-\-group=\fIGROUP\fR[,\fIGROUP\fR...]]\ [\fB\-\-help\fR]\ [\fB\-\-no\-reload\fR]\ [\fB\-\-pid\-from=\fISOURCE\fR]\ [\fB\-\-pidfile=\fIPIDFILE\fR]\ [\fB\-\-program=\fIPROGRAM\fR]\ [\fB\-\-sentinel\fR]\ [\fB\-\-signal\-reload=\fISIG\fR]\ [\fB\-\-signal\-stop=\fISIG\fR]\ [\fB\-\-timeout=\fISECONDS\fR]\ + [\fB\-\-user=\fIUSER\fR]\ [\fB\-\-usage\fR]\ [\fB\-\-verbose\fR]\ {\ \fBstart\fR\ |\ \fBstop\fR\ @@ -162,12 +167,21 @@ variable is set). \fB\-F\fR, \fB\-\-pidfile=\fINAME\fR Name of the PID file (same as \fB\-\-pid\-from=FILE:\fINAME\fR) .TP \fB\-h\fR, \fB\-\-help\fR Display a short help list. .TP +\fB\-g\fR, \fB\-\-group=\fIGROUP\fR[,\fIGROUP\fR...] +Run program with this \fIGROUP\fR privileges. If the argument is a +list of groups, the first group becomes the principal, and the +rest of them supplementary groups. Each \fIGROUP\fR is either a group +name or a numeric group number prefixed with a plus sign. Whatever +notation is used, it must exist in the system group database. + +See also the \fB\-\-user\fR option. +.TP \fB\-\-no\-reload\fR Makes \fBreload\fR equivalent to \fBrestart\fR. .TP \fB\-p\fR, \fB\-\-program=\fIPROGRAM\fR Name of the program to run. .TP @@ -189,12 +203,21 @@ Signal to send in order to terminate the program (default: \fB\-t\fR, \fB\-\-timeout=\fISECONDS\fR Time to wait for the program to start up or terminate. .TP \fB\-\-usage\fR Display a short usage summary. .TP +\fB\-u\fR, \fB\-\-user=\fIUSER\fR +Run with this user privileges. The argument is either a login +name or a numeric UID prefixed with the plus sign. Whatever form is +used, it must correspond to a valid user from the system user +database. + +Unless \fB\-\-group\fR option is also given, the primary and +supplementary groups of \fIUSER\fR will be used. +.TP \fB\-\-version\fR Display program version and exit. .TP \fB\-v\fR, \fB\-\-verbose\fR Print verbose messages (e.g. "Starting \fIPROGNAME\fR"). .SH PID SOURCES @@ -279,12 +302,14 @@ Influential environment variables and corresponding options: \fBGENRC_COMMAND=\fICOMMAND\fR \fB\-\-command=\fICOMMAND\fR \fBGENRC_PROGRAM=\fINAME\fR \fB\-\-program=\fINAME\fR \fBGENRC_PID_FROM=\fISOURCE\fR \fB\-\-pid\-from=\fISOURCE\fR \fBGENRC_TIMEOUT=\fISECONDS\fR \fB\-\-timeout=\fISECONDS\fR \fBGENRC_SENTINEL=1\fR \fB\-\-sentinel\fR \fBGENRC_CREATE_PIDFILE=\fINAME\fR \fB\-\-create\-pidfile=\fINAME\fR + \fBGENRC_USER=\fINAME\fR \fB\-\-user=\fINAME\fR + \fBGENRC_GROUP=\fIGROUPS\fR \fB\-\-group=\fIGROUPS\fR .fi .SH AUTHORS Sergey Poznyakoff .SH "BUG REPORTS" Report bugs to <gray@gnu.org>. .SH COPYRIGHT diff --git a/src/genrc.c b/src/genrc.c index 92b0fac..d4904b6 100644 --- a/src/genrc.c +++ b/src/genrc.c @@ -40,15 +40,17 @@ struct option longopts[] = { { "no-reload", no_argument, 0, OPT_NO_RELOAD }, { "signal-stop", required_argument, 0, OPT_SIGNAL_STOP }, { "sentinel", no_argument, 0, 'S' }, { "create-pidfile", required_argument, 0, OPT_CREATE_PIDFILE }, { "version", no_argument, 0, OPT_VERSION }, { "verbose", no_argument, 0, 'v' }, + { "user", required_argument, 0, 'u' }, + { "group", required_argument, 0, 'g' }, { NULL } }; -char shortopts[] = "c:hF:P:p:St:v"; +char shortopts[] = "c:hF:g:P:p:St:u:v"; struct sigdefn { char const *sig_name; int sig_no; }; @@ -383,12 +385,15 @@ main(int argc, char **argv) case 'F': p = xmalloc(6 + strlen(optarg)); strcat(strcpy(p, "FILE:"), optarg); setenv("GENRC_PID_FROM", p, 1); free(p); break; + case 'g': + setenv("GENRC_GROUP", optarg, 1); + break; case OPT_CREATE_PIDFILE: setenv("GENRC_CREATE_PIDFILE", optarg, 1); break; case 't': setenv("GENRC_TIMEOUT", optarg, 1); break; @@ -401,12 +406,15 @@ main(int argc, char **argv) case OPT_SIGNAL_RELOAD: setenv("GENRC_SIGNAL_RELOAD", optarg, 1); break; case OPT_SIGNAL_STOP: setenv("GENRC_SIGNAL_STOP", optarg, 1); break; + case 'u': + setenv("GENRC_USER", optarg, 1); + break; case 'v': genrc_verbose++; break; default: exit(1); } diff --git a/src/genrc.h b/src/genrc.h index a6e81e8..9842016 100644 --- a/src/genrc.h +++ b/src/genrc.h @@ -28,12 +28,13 @@ void system_error(int ec, char const *fmt, ...); #define xmalloc grecs_malloc #define xzalloc grecs_zalloc #define xcalloc grecs_calloc #define xrealloc grecs_realloc #define xstrdup grecs_strdup +void *x2nrealloc(void *p, size_t *pn, size_t s); #define SHELL "/bin/sh" pid_t file_read_pid(char const *filename); typedef struct transform *TRANSFORM; @@ -58,12 +59,13 @@ int pidlist_remove(PIDLIST *plist, size_t i); void pidlist_kill(PIDLIST *plist, int sig); pid_t strtopid(char const *str); int pid_is_running(pid_t pid); +void runas(void); enum { MATCH_REGEX, /* extended POSIX regexp match (default) */ MATCH_PCRE, /* PCRE match (not implemented) */ MATCH_GLOB, /* glob pattern match */ MATCH_EXACT, /* exact match */ diff --git a/src/runas.c b/src/runas.c new file mode 100644 index 0000000..9d2a6c1 --- /dev/null +++ b/src/runas.c @@ -0,0 +1,191 @@ +/* This file is part of genrc +Copyryght (C) 2018 Sergey Poznyakoff +License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> +This is free software: you are free to change and redistribute it. +There is NO WARRANTY, to the extent permitted by law. +*/ +#include "genrc.h" +#include <pwd.h> +#include <grp.h> + +static void +addgid(gid_t **pgv, size_t *pgc, size_t *pgi, gid_t gid) +{ + gid_t *gv = *pgv; + size_t gc = *pgc; + size_t gi = *pgi; + + if (gi == gc) + gv = x2nrealloc(gv, &gc, sizeof(gv[0])); + + gv[gi++] = gid; + *pgv = gv; + *pgc = gc; + *pgi = gi; +} + +static int +member(gid_t *gv, size_t gc, gid_t gid) +{ + size_t i; + + for (i = 0; i < gc; i++) + if (gv[i] == gid) + return 1; + return 0; +} + +static size_t +get_user_groups(const char *user, gid_t **pgv, size_t *pgc) +{ + struct group *gr; + size_t gi = 0; + + setgrent(); + while ((gr = getgrent())) { + char **p; + for (p = gr->gr_mem; *p; p++) + if (strcmp(*p, user) == 0) + addgid(pgv, pgc, &gi, gr->gr_gid); + } + endgrent(); + return gi; +} + +static gid_t +strtogid(char const *str) +{ + struct group *gr; + + if (str[0] == '+') { + char *end; + unsigned long n; + + errno = 0; + n = strtoul(str, &end, 10); + if (errno || *end) { + genrc_error("invalid group name %s", str); + exit(1); + } + + gr = getgrgid(n); + } else + gr = getgrnam(str); + + if (!gr) { + genrc_error("%s: no such group", str); + exit(1); + } + + return gr->gr_gid; +} + +void +runas(void) +{ + struct passwd *pw; + uid_t uid; + gid_t gid; + gid_t *gv; + size_t gc, gn; + char const *runas_user; + char const *runas_groups; + + runas_user = getenv("GENRC_USER"); + runas_groups = getenv("GENRC_GROUP"); + + if (!(runas_user || runas_groups)) + return; + if (getuid() != 0) { + genrc_error("not root: can't switch to user privileges"); + exit(1); + } + + if (!runas_user) { + pw = getpwuid(0); + runas_user = "root"; + } else if (runas_user[0] == '+') { + char *end; + unsigned long n; + + errno = 0; + n = strtoul(runas_user + 1, &end, 10); + if (errno || *end) { + genrc_error("invalid user name %s", runas_user); + exit(1); + } + + pw = getpwuid(n); + } else + pw = getpwnam(runas_user); + + if (!pw) { + genrc_error("%s: no such user", runas_user); + exit(1); + } + runas_user = pw->pw_name; + + uid = pw->pw_uid; + gid = pw->pw_gid; + + gv = NULL; + gc = 0; + + if (runas_groups && runas_groups[0]) { + struct wordsplit ws; + size_t i; + + ws.ws_delim = ","; + ws.ws_error = genrc_error; + if (wordsplit(runas_groups, &ws, + WRDSF_NOCMD + | WRDSF_NOVAR + | WRDSF_DELIM + | WRDSF_ENOMEMABRT + | WRDSF_SHOWERR + | WRDSF_ERROR)) + exit(1); + + if (ws.ws_wordc == 0) { + genrc_error("bad group list: '%s'", runas_groups); + exit(1); + } + + gid = strtogid(ws.ws_wordv[0]); + for (i = 1; i < ws.ws_wordc; i++) + addgid(&gv, &gc, &gn, strtogid(ws.ws_wordv[i])); + + wordsplit_free(&ws); + } + + if (gc == 0) { + gn = get_user_groups(runas_user, &gv, &gc); + if (!member(gv, gn, gid)) + addgid(&gv, &gc, &gn, gid); + } + + /* Reset group permissions */ + if (setgroups(gn, gv)) { + system_error(errno, "setgroups"); + exit(1); + } + free(gv); + + if (gid) { + /* Switch to the user's gid. */ + if (setgid(gid)) { + system_error(errno, "setgid(%lu) failed", + (unsigned long) gid); + exit(1); + } + } + + /* Now reset uid */ + if (uid) { + if (setuid(uid)) { + system_error(errno, "setuid(%lu) failed: %s", + (unsigned long) uid); + exit(1); + } + } +} diff --git a/src/sentinel.c b/src/sentinel.c index 1d44763..59b89cc 100644 --- a/src/sentinel.c +++ b/src/sentinel.c @@ -146,12 +146,14 @@ start_command(int p[]) } if (pid == 0) { char *argv[] = { SHELL, "-c", NULL, NULL }; int i; + runas(); + close(0); close(1); close(2); open("/dev/null", O_RDWR); dup(outpipe[1]); dup(errpipe[1]); |