/*
 * Copyright (c) 2002 Olaf Dietsche
 *
 * Filesystem capabilities for linux, user space tool.
 *
 * This tool uses libcap. Compile as:
 * gcc -Wall -o chcap chcap.c -lcap
 *
 * usage: chcap capabilities file ...
 * example: chcap cap_net_raw+ep /bin/ping
 *
 * Thanks to Steve Baur for the idea to locate_mountpoint().
 */

#include <asm/types.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <sys/capability.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

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

static char *locate_mountpoint(char *dir, const char *file)
{
	char *slash;
	struct stat buf;
	dev_t dev;
	int err = stat(file, &buf);
	if (err)
		return NULL;

	dev = buf.st_dev;
	strcpy(dir, file);
	do {
		slash = strrchr(dir, '/');
		*slash = 0;
		err = stat(slash == dir ? "/" : dir, &buf);
		if (err)
			return NULL;
	} while (buf.st_dev == dev && slash != dir);

	*slash = '/';
	if (buf.st_dev == dev)
		dir[1] = 0;

	return dir;
}

static int open_capabilities(const char *name)
{
	char file[PATH_MAX], mnt[PATH_MAX];
	if (!realpath(name, file))
		return -1;

	if (!locate_mountpoint(mnt, file))
		return -1;

	strcat(mnt, "/.capabilities");
	return open(mnt, O_CREAT | O_WRONLY, 0666);
}

static void chcap(const char *name, __u32 *caps)
{
	struct stat buf;
	int fd = open_capabilities(name);
	if (fd < 0) {
		perror(name);
		return;
	}

	if (access(name, W_OK) || stat(name, &buf)) {
		perror(name);
	} else {
		__u32 set[3][4];
		off_t rc = lseek(fd, buf.st_ino * sizeof(set), SEEK_SET);
		if (rc == -1)
			perror(name);
		else {
			memset(set, 0, sizeof(set));
			set[0][0] = caps[0];
			set[1][0] = caps[1];
			set[2][0] = caps[2];
			write(fd, set, sizeof(set));
		}
	}

	close(fd);
}

static __u32 cap_to_u32(cap_t cap, cap_flag_t flag)
{
	__u32 c = 0;
	int i;
	for (i = 0; i <= 28; ++i) {
		cap_flag_value_t on;
		int err = cap_get_flag(cap, i, flag, &on);
		if (err)
			fatal("cap_to_u32()");

		if (on)
			c |= 1 << i;
	}

	return c;
}

int main(int argc, char **argv)
{
	cap_t cap;
	__u32 caps[3];
	int i;

	if (argc < 3) {
		fprintf(stderr, "usage: %s capabilities file ...\n", argv[0]);
		exit(2);
	}

	cap = cap_from_text(argv[1]);
	caps[0] = cap_to_u32(cap, CAP_EFFECTIVE);
	caps[1] = cap_to_u32(cap, CAP_INHERITABLE);
	caps[2] = cap_to_u32(cap, CAP_PERMITTED);

	for (i = 2; i < argc; ++i) {
		chcap(argv[i], caps);
	}

	return 0;
}
