aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am3
-rw-r--r--src/com_start.c1
-rw-r--r--src/genrc.825
-rw-r--r--src/genrc.c10
-rw-r--r--src/genrc.h2
-rw-r--r--src/runas.c191
-rw-r--r--src/sentinel.c2
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
@@ -27,24 +27,25 @@ genrc_SOURCES = \
pid_ps.c\
pidlist.c\
procscan.c\
com_status.c\
com_start.c\
com_stop.c\
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
AM_CPPFLAGS += -DHAVE_PCRE=1
endif
dist_man_MANS=genrc.8
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
@@ -103,24 +103,25 @@ com_start(void)
if ((p = getenv("GENRC_SENTINEL")) && *p == '1')
return sentinel();
pid = fork();
if (pid == -1) {
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)) {
genrc_error("timed out waiting for %s to return",
genrc_program);
return 1;
}
if (!(WIFEXITED(status) && WEXITSTATUS(status) == 0)) {
report_exec_error(status, genrc_program);
diff --git a/src/genrc.8 b/src/genrc.8
index f900639..00522ee 100644
--- a/src/genrc.8
+++ b/src/genrc.8
@@ -9,42 +9,47 @@
.\" Genrc 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 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\
|\
\fBrestart\fR\
|\
\fBreload\fR\
|\
\fBstatus\fR\
@@ -156,24 +161,33 @@ Command line to run.
When used together with \fB\-\-sentinel\fR, the PID of the command
being run will be stored in file \fINAME\fR. The
\fB\-\-pid\-from=FILE:\fINAME\fR will be assumed, unless the
\fB\-\-pid\-from\fR is given explicitly (or the \fBGENRC_PID_FROM\fR
variable is set).
.TP
\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
\fB\-P\fR, \fB\-\-pid\-from=\fISOURCE\fR
Where to look for PIDs of the running programs.
.TP
\fB\-\-sentinel\fR
\fIPROGRAM\fR runs in foreground; disconnect from the controlling
terminal, run it and act as a sentinel.
@@ -183,24 +197,33 @@ Signal to send on reload (default: \fBSIGHUP\fR). Setting it to 0 is
equivalent to \fB\-\-no\-reload\fR.
.TP
\fB\-\-signal\-stop=\fISIG\fR
Signal to send in order to terminate the program (default:
\fBSIGTERM\fR).
.TP
\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
.TP
\fBFILE:\fIFILENAME\fR
Read PID from the file \fIFILENAME\fR.
.TP
\fBCONFIG:\fILANG\fB:\fIFILENAME\fB:\fIFQRN\fR
Name of the PID file is stored in relation \fIFQRN\fR of the configuration
@@ -273,24 +296,26 @@ and \fIFLAGS\fR are as described above.
Influential environment variables and corresponding options:
.sp
.nf
.ta 5n 35n
.ul
Variable Option
\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
Copyright \(co 2018 Sergey Poznyakoff
.br
.na
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
.br
.ad
diff --git a/src/genrc.c b/src/genrc.c
index 92b0fac..d4904b6 100644
--- a/src/genrc.c
+++ b/src/genrc.c
@@ -34,27 +34,29 @@ struct option longopts[] = {
{ "command", required_argument, 0, 'c' },
{ "program", required_argument, 0, 'p' },
{ "pid-from", required_argument, 0, 'P' },
{ "pidfile", required_argument, 0, 'F' },
{ "timeout", required_argument, 0, 't' },
{ "signal-reload", required_argument, 0, OPT_SIGNAL_RELOAD },
{ "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;
};
#define S(s) { #s, s }
static struct sigdefn sigdefn[] = {
S (SIGHUP),
S (SIGINT),
S (SIGQUIT),
S (SIGILL),
@@ -377,42 +379,48 @@ main(int argc, char **argv)
case 'p':
setenv("GENRC_PROGRAM", optarg, 1);
break;
case 'P':
setenv("GENRC_PID_FROM", optarg, 1);
break;
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;
case 'S':
setenv("GENRC_SENTINEL", "1", 1);
break;
case OPT_NO_RELOAD:
no_reload = 1;
break;
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);
}
}
if ((p = getenv("GENRC_COMMAND")) != NULL)
genrc_command = p;
if ((p = getenv("GENRC_PROGRAM")) != NULL)
genrc_program = p;
diff --git a/src/genrc.h b/src/genrc.h
index a6e81e8..9842016 100644
--- a/src/genrc.h
+++ b/src/genrc.h
@@ -22,24 +22,25 @@ There is NO WARRANTY, to the extent permitted by law.
#include "wordsplit.h"
void setprogname(char const *s);
void genrc_error(char const *fmt, ...);
void usage_error(char const *fmt, ...);
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;
char *transform_string(TRANSFORM tf, const char *input);
char *transform_string_if_match(TRANSFORM tf, const char *input);
TRANSFORM compile_transform_expr(const char *expr, int cflags);
struct pidlist {
pid_t *pidv;
@@ -52,24 +53,25 @@ void pidlist_init(PIDLIST *);
void pidlist_free(PIDLIST *);
void pidlist_clear(PIDLIST *plist);
void pidlist_add(PIDLIST *, pid_t);
ssize_t pidlist_index(PIDLIST *plist, pid_t p);
int pidlist_member(PIDLIST *plist, pid_t p);
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 */
};
#define MATCH_DEFAULT MATCH_REGEX
enum {
PROCF_ICASE = 0x01, /* ignore case */
PROCF_ALL = 0x02, /* use all pids */
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
@@ -140,24 +140,26 @@ start_command(int p[])
xpipe(errpipe);
xpipe(outpipe);
pid = fork();
if (pid == -1) {
system_error(errno, "pipe");
return -1;
}
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]);
i = max(max(outpipe[0],outpipe[1]),
max(errpipe[0],errpipe[1]));
while (--i > 2)
close(i);

Return to:

Send suggestions and report system problems to the System administrator.