/*
 * $Id: sucap.c,v 1.1.1.1.2.1 1999/05/01 23:26:32 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 <fcntl.h>

static void usage(void)
{
    fprintf(stderr,
"usage: sucap <user> <group> <caps> <command-path> [command-args...]\n\n"
"  This program is a wrapper that can invoke a program with the uid/gid of\n"
"  a specified user, but with a specified set of capabilities raised.\n\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"
"  This program will only work if it is invoked with the following\n"
"  capability raised: CAP_SETPCAP. Systems that permit this capability\n"
"  should be closely monitored.\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 */
    }
}


void main(int argc, char **argv)
{
    cap_t raised_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 4 arguments */
    if (argc < 5) {
        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();

    raised_caps = cap_from_text(argv[3]);
    if (raised_caps == 0) {
	perror("sucap: unable to create capability set");
        usage();
    }

    {
	char *text = cap_to_text(raised_caps, NULL);
	printf("Caps: %s\n", text);
	cap_free(text);
    }


    /* fork off a child to do the capability setting - the parent will
       become the exec'd process */

    fflush(NULL);
    pid = fork();
    if (pid == -1) {
	perror("sucap: fork failed");
	exit(1);
    }

    /* 1. mother process sets gid and uid
     * 2. mother process execs whatever is to be executed
     * 3*. child process sets capabilities of mother process
     * (*) note, there is a race condition here - it might show
     *     up on SMP systems.
     */

    if (pid) {
	/* Mother process. */
	close(pipe_fds[0]);

	/* Get rid of any supplemental groups */
	if (setgroups(0, 0)) {
	    perror("sucap: setgroups failed");
	    exit(1);
	}

	/* Set gid and uid (this may clear capabilities) */
	setregid(gid, gid);
	setreuid(uid, uid);

	/* close on exec */
	fcntl(pipe_fds[1], F_SETFD, 1);

	printf("[debug] uid:%d, real uid:%d\n", geteuid(), getuid());
	/* exec the program indicated by args 3 ... */
	execvp(argv[4], argv+4);
	
	/* 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 exec the command */
	wait_on_fd(pipe_fds[0]);

	/* Set privileges on mother process */
	if (capsetp(parent_pid, raised_caps)) {
	    perror("sucaps: capsetp");
	    _exit(1);
	}

	/* exit */
	_exit(0);
    }
}
