blob: 366a093bc6c8784d7be8e050aba55427755b5d7a [file] [log] [blame]
/*
* $Id: sucap.c,v 1.1.1.1 1999/04/17 22:16:31 morgan Exp $
*
* This was written by Finn Arne Gangstad <finnag@guardian.no>
*
* This is a program that is intended to exec a subsequent program.
* The purpose of this 'sucap' wrapper is to change uid but keep all
* privileges. All environment variables are inherited.
*/
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#undef _POSIX_SOURCE
#include <sys/capability.h>
#include <pwd.h>
#define __USE_BSD
#include <grp.h>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
static void usage(void)
{
fprintf(stderr,
"usage: sucap <user> <group> <command-path> [command-args...]\n\n"
" This program is a wrapper that change UID but not privileges of a\n"
" program to be executed.\n"
" Note, this wrapper is intended to assist in overcoming a lack of support\n"
" for filesystem capability attributes and should be used to launch other\n"
" files. This program should _NOT_ be made setuid-0.\n\n"
"[Copyright (c) 1998 Finn Arne Gangstad <finnag@guardian.no>]\n");
exit(1);
}
static void
wait_on_fd(int fd)
{
/* Wait until some data is available on a file descriptor, or until
* end of file or an error is detected */
char buf[1];
while (read(fd, buf, sizeof(buf)) == -1 && errno == EINTR) {
/* empty loop */
}
}
int main(int argc, char **argv)
{
cap_t old_caps;
uid_t uid;
pid_t pid, parent_pid;
gid_t gid;
int pipe_fds[2];
/* this program should not be made setuid-0 */
if (getuid() && !geteuid()) {
usage();
}
/* check that we have at least 3 arguments */
if (argc < 4) {
usage();
}
/* Convert username to uid */
{
struct passwd *pw = getpwnam(argv[1]);
if (!pw) {
fprintf(stderr, "sucap: No such user: %s\n", argv[1]);
exit(1);
}
uid = pw->pw_uid;
}
/* Convert groupname to gid */
{
struct group *gr = getgrnam(argv[2]);
if (!gr) {
fprintf(stderr, "sucap: No such group: %s\n", argv[2]);
exit(1);
}
gid = gr->gr_gid;
}
/* set process group to current pid */
if (setpgid(0, getpid())) {
perror("sucap: Failed to set process group");
exit(1);
}
if (pipe(pipe_fds)) {
perror("sucap: pipe() failed");
exit(1);
}
parent_pid = getpid();
old_caps = cap_init();
if (capgetp(0, old_caps)) {
perror("sucap: capgetp");
exit(1);
}
{
ssize_t x;
printf("Caps: %s\n", cap_to_text(old_caps, &x));
}
/* fork off a child to do the hard work */
fflush(NULL);
pid = fork();
if (pid == -1) {
perror("sucap: fork failed");
exit(1);
}
/* 1. mother process sets gid and uid
* 2. child process sets capabilities of mother process
* 3. mother process execs whatever is to be executed
*/
if (pid) {
/* Mother process. */
close(pipe_fds[0]);
/* Get rid of any supplemental groups */
if (!getuid() && setgroups(0, 0)) {
perror("sucap: setgroups failed");
exit(1);
}
/* Set gid and uid (this probably clears capabilities) */
setregid(gid, gid);
setreuid(uid, uid);
{
ssize_t x;
cap_t cap = cap_init();
capgetp(0, cap);
printf("Caps: %s\n", cap_to_text(cap, &x));
}
printf("[debug] uid:%d, real uid:%d\n", geteuid(), getuid());
/* Signal child that we want our privileges updated */
close(pipe_fds[1]); /* Child hangs in blocking read */
/* Wait for child process to set our privileges */
{
int status = 0;
if (wait(&status) == -1) {
perror("sucap: wait failed");
}
if (!WIFEXITED(status) || WEXITSTATUS(status)) {
fprintf(stderr, "sucap: child did not exit cleanly.\n");
exit(1);
}
}
{
ssize_t x;
cap_t cap = cap_init();
capgetp(0, cap);
printf("Caps: %s\n", cap_to_text(cap, &x));
}
/* printf("[debug] uid:%d, real uid:%d\n", geteuid(), getuid()); */
/* exec the program indicated by args 2 ... */
execvp(argv[3], argv+3);
/* if we fall through to here, our exec failed -- announce the fact */
fprintf(stderr, "Unable to execute command: %s\n", strerror(errno));
usage();
} else {
/* Child process */
close(pipe_fds[1]);
/* Wait for mother process to setuid */
wait_on_fd(pipe_fds[0]);
/* Set privileges on mother process */
if (capsetp(parent_pid, old_caps)) {
perror("sucaps: capsetp");
_exit(1);
}
/* exit to signal mother process that we are ready */
_exit(0);
}
return 0;
}