| /* |
| * $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; |
| } |