Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Vim swap files
.*.swp

# Object files
*.o
*.ko
Expand Down Expand Up @@ -32,3 +35,6 @@
*.dSYM/

su-exec
su-exec-static
su-exec-debug
license.inc
32 changes: 26 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,17 +1,37 @@

CFLAGS ?= -Wall -Werror -g
CFLAGS ?= -Wall -Werror
LDFLAGS ?=

PROG := su-exec
SRCS := $(PROG).c
INCS := license.inc

PREFIX := /usr/local
INSTALL_DIR := $(PREFIX)/bin
MAN_DIR := $(PREFIX)/share/man/man8

all: $(PROG)

$(PROG): $(SRCS)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
license.inc: LICENSE
xxd -i $^ > $@

$(PROG): $(SRCS) $(INCS)
$(CC) $(CFLAGS) -o $@ $(SRCS) $(LDFLAGS)
strip $@

$(PROG)-static: $(SRCS) $(INCS)
$(CC) $(CFLAGS) -o $@ $(SRCS) -static $(LDFLAGS)
strip $@

$(PROG)-static: $(SRCS)
$(CC) $(CFLAGS) -o $@ $^ -static $(LDFLAGS)
$(PROG)-debug: $(SRCS) $(INCS)
$(CC) -g $(CFLAGS) -o $@ $(SRCS) $(LDFLAGS)

install:
install -d 0755 $(DESTDIR)$(INSTALL_DIR)
install -m 0755 $(PROG) $(DESTDIR)$(INSTALL_DIR)
install -d 0755 $(DESTDIR)$(MAN_DIR)
install -m 0644 su-exec.1 $(DESTDIR)$(MAN_DIR)

clean:
rm -f $(PROG) $(PROG)-static
rm -f $(PROG) $(PROG)-static $(PROG)-debug $(INCS)

69 changes: 69 additions & 0 deletions su-exec.1
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
.TH SU-EXEC 8 "14 Oct 2017"

.SH NAME
su-exec \- change user id and group id before executing a program

.SH SYNOPSIS
\fBsu-exec\fP \fIuser-spec\fP \fIcommand\fP [ \fIarguments...\fP ]

\fBsu-exec\fP \fI-l\fP

.SH DESCRIPTION
\fBsu-exec\fP executes a program with modified privileges. The program
will be exceuted directly and not run as a child, like su and sudo does,
which avoids TTY and signal issues.

Notice that su-exec depends on being run by the root user, non-root
users do not have permission to change uid/gid.

.SH OPTIONS
.TP
\fIuser-spec\fP
is either a user name (e.g. \fBnobody\fP) or user name and group name
separated with colon (e.g. \fBnobody:ftp\fP). Numeric uid/gid values
can be used instead of names.

.TP
\fIcommand\fP
is the program to execute. Can be either absolute or relative path.

.TP
\fI-l\fP
Print license information and exits.

.SH EXAMPLES

.TP
Execute httpd as user \fIapache\fP and gid value 1000 with the two specified arguments:

$ \fBsu-exec apache:1000 /usr/sbin/httpd -f /opt/www/httpd.conf\fP

.SH ENVIRONMENT VARIABLES

.TP
\fBHOME\fP
Is updated to the value matching the user entry in \fC/etc/passwd\fP.

.TP
\fBPATH\fP
Is used for searching for the program to execute.

Since su-exec is not running as a suid binary, the dynamic linker or
libc will not strip or ignore variables like LD_LIBRARY_PATH etc.

.SH EXIT STATUS
.TP
\fB0\fP
When printing license information.

.TP
\fB1\fP
If \fbsu-exec\fR fails to change priveledges or execute the program it
will return \fB1\fP. In the successfull case the exit value will be
whatever the executed program returns.

.SH "SEE ALSO"
su(1), runuser(8), sudo(8), gosu(1)

.SH BUGS
\fBUSER\fP and \fBLOGNAME\fP environmental variables are not updated.
66 changes: 54 additions & 12 deletions su-exec.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,44 @@
#include <string.h>
#include <unistd.h>


static char *argv0;

static void usage(int exitcode)
{
printf("Usage: %s user-spec command [args]\n", argv0);
printf("Usage: %s -l\n\tShows license.\n", argv0);
exit(exitcode);
}

#include "license.inc"
static void print_license()
{
unsigned int i;
for (i=0; i<LICENSE_len; i++)
putchar(LICENSE[i]);
}

#ifdef linux
#define WARNING_setgroups " (on Linux you need CAP_SETGID)"
#define WARNING_setgid " (on Linux you need CAP_SETGID)"
#define WARNING_setuid " (on Linux you need CAP_SETUID)"
#else
#define WARNING_setgroups ""
#define WARNING_setgid ""
#define WARNING_setuid ""
#endif

static void print_eperm_warning(const char *extra_warn)
{
int saved_errno;
if (errno != EPERM)
return;
saved_errno = errno;
fprintf(stderr, "Insufficient privilege, %s needs to run as root%s.\n", argv0, extra_warn);
errno = saved_errno;
}

int main(int argc, char *argv[])
{
char *user, *group, **cmdargv;
Expand All @@ -28,8 +58,12 @@ int main(int argc, char *argv[])
gid_t gid = getgid();

argv0 = argv[0];
if (argc == 2 && strcmp(argv[1], "-l") == 0) {
print_license();
return EXIT_SUCCESS;
}
if (argc < 3)
usage(0);
usage(EXIT_FAILURE);

user = argv[1];
group = strchr(user, ':');
Expand Down Expand Up @@ -73,8 +107,10 @@ int main(int argc, char *argv[])
}

if (pw == NULL) {
if (setgroups(1, &gid) < 0)
err(1, "setgroups(%i)", gid);
if (setgroups(1, &gid) < 0) {
print_eperm_warning(WARNING_setgroups);
err(EXIT_FAILURE, "setgroups(%i)", gid);
}
} else {
int ngroups = 0;
gid_t *glist = NULL;
Expand All @@ -83,25 +119,31 @@ int main(int argc, char *argv[])
int r = getgrouplist(pw->pw_name, gid, glist, &ngroups);

if (r >= 0) {
if (setgroups(ngroups, glist) < 0)
err(1, "setgroups");
if (setgroups(ngroups, glist) < 0) {
print_eperm_warning(WARNING_setgroups);
err(EXIT_FAILURE, "setgroups");
}
break;
}

glist = realloc(glist, ngroups * sizeof(gid_t));
if (glist == NULL)
err(1, "malloc");
err(EXIT_FAILURE, "malloc");
}
}

if (setgid(gid) < 0)
err(1, "setgid(%i)", gid);
if (setgid(gid) < 0) {
print_eperm_warning(WARNING_setgid);
err(EXIT_FAILURE, "setgid(%i)", gid);
}

if (setuid(uid) < 0)
err(1, "setuid(%i)", uid);
if (setuid(uid) < 0) {
print_eperm_warning(WARNING_setuid);
err(EXIT_FAILURE, "setuid(%i)", uid);
}

execvp(cmdargv[0], cmdargv);
err(1, "%s", cmdargv[0]);
err(EXIT_FAILURE, "%s", cmdargv[0]);

return 1;
return EXIT_FAILURE;
}