/*
 * Copyright (c) 2002 Olaf Dietsche
 *
 * Filesystem capabilities for linux, user space tool.
 *
 * This tool uses libcap. Compile as:
 * gcc -Wall -o inhcaps incaps.c -lcap
 *
 * usage: inhcaps capabilities uid[:gid] program [args]
 * example:
 * # chcap cap_net_bind_service=ei /usr/sbin/sendmail
 * # inhcaps cap_net_bind_service=i smmta:mail /usr/sbin/sendmail
 * 
 */

#include <sys/capability.h>
#include <sys/prctl.h>
#include <pwd.h>
#include <grp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static void usage(char **argv)
{
	fprintf(stderr, "usage: %s capabilities uid[:gid] program [args]\n", argv[0]);
	exit(1);
}

static void fatal(const char *msg)
{
	perror(msg);
	exit(1);
}

static void changecaps(const char *capstxt)
{
	static cap_value_t needed[] = { CAP_SETGID, CAP_SETUID };
	cap_t caps;
	caps = cap_from_text(capstxt);
	if (!caps)
		fatal("Unable to create capability set");

	cap_set_flag(caps, CAP_EFFECTIVE, 2, needed, CAP_SET);
	cap_set_flag(caps, CAP_PERMITTED, 2, needed, CAP_SET);
	if (cap_set_proc(caps))
		fatal("Unable to set capabilities");

	cap_free(caps);
	
	if (prctl(PR_SET_KEEPCAPS, 1))
		fatal("Unable to keep capabilities");
}

static void changeuid(const char *userspec)
{
	char *user, *grp, *endp;
	int uid = -1, gid = -1;
	user = strdup(userspec);
	user = strtok(user, ":");
	grp = strtok(NULL, ":");
	if (grp) {
		gid = strtol(grp, &endp, 0);
		if (*endp != 0) {
			struct group *g = getgrnam(grp);
			if (!g)
				fatal(grp);

			gid = g->gr_gid;
		}
	}
	
	uid = strtol(user, &endp, 0);
	if (*endp != 0) {
		struct passwd *pw = getpwnam(user);
		if (!pw)
			fatal(user);

		uid = pw->pw_uid;
		if (grp == NULL)
			gid = pw->pw_gid;
	}

	if (gid >= 0) {
		if (setgid(gid))
			fatal("setgid");

		if (initgroups(user, gid))
			fatal("initgroups");
	}

	if (setuid(uid))
		fatal("setuid");
}

int main(int argc, char **argv)
{
	if (argc < 3)
		usage(argv);

	changecaps(argv[1]);
	changeuid(argv[2]);

	execvp(argv[3], argv + 3);
	perror(argv[3]);
	return 1;
}
