/*
 * ppy: privileged wrapper for python
 *
 * Usage:
 *  ppy script [args]
 *  #!/path/to/ppy (shebang usage)
 *
 * Needs python binary with needed (by scripts)
 *  inheritable caps set. Can be created like this:
 *  cp /usr/bin/{python,cpy} &&\
 *    chown root:privileged_group /usr/bin/cpy &&\
 *    chmod 750 /usr/bin/cpy &&\
 *    setcap 'all=i' /usr/bin/cpy
 *
 * - this binary and capped-py locations are hard-coded.
 * - checks self, script and capped-py binary for uid=root and go-w.
 *
 * Mike Kazantsev <mk.fraggod@gmail.com>
 * 26.12.2009
*/

#include <pwd.h>
#include <grp.h>
#include <stdlib.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include <cap-ng.h>


// Hardcoded paths to binaries
const char *self = "/usr/bin/ppy"; // argv[0] means nothing
const char *py = "/usr/sbin/cpy";


int main(int argc, char **argv) {
	if ( argc < 2 ) return(10);

	struct stat fstat; int ncap;
	const char *target = argv[1]; // script to be executed

	if (target[0] == '-' || !strcmp(self, target)) return(10); // invalid invocation
	if (stat(target, &fstat)) return(11); // path doesn't exists
	if (fstat.st_mode & (S_IWGRP|S_IWOTH)) return(13); // file is user-writable at most
	unsigned int uid = fstat.st_mode & S_ISUID ? fstat.st_uid : getpwnam("nobody")->pw_uid;
	unsigned int gid = fstat.st_mode & S_ISGID ? fstat.st_gid : getgrnam("nogroup")->gr_gid;

	// Copy caps from target script to current process' sets (all four of them)
	// Weird libcap{,-ng} only allow exporting these as strings,
	//  so it have to be parsed in order to apply them to another object
	// Also, no idea why it's disallowed to set/clear single set of caps (e.g. inherited)
	// libcap (non-ng) allows simplier cap_{to,from}_text interface,
	//  but I don't want to link both libs, and -ng is easier on capng_change_id
	if ((ncap = open(target, O_RDONLY)) == -1) return(11);

	if (!capng_get_caps_fd(ncap)) {
		(void) close(ncap);

		// Transfer caps from file to process
		char *target_caps = capng_print_caps_text(
			CAPNG_PRINT_BUFFER, CAPNG_INHERITABLE );
		if (capng_get_caps_process()) return(14);
		capng_clear(CAPNG_BOUNDING_SET);
		char *cap = (char*) strtok(target_caps, ", ");
		while(1) {
			if (cap == NULL) break;
			if ((ncap = capng_name_to_capability(cap)) >= 0)
				capng_update( CAPNG_ADD,
					CAPNG_INHERITABLE, ncap );
			cap = (char*) strtok(NULL, ", "); }
		capng_apply(CAPNG_SELECT_BOTH);
		free(target_caps); free(cap);

		// Switch uid/gid
		if (uid || gid) {
			if (capng_change_id( uid ? uid : getuid(), gid ? gid : getgid(),
				CAPNG_DROP_SUPP_GRP | CAPNG_CLEAR_BOUNDING )) return(14); } }

	else {
		(void) close(ncap);

		if (uid) if (setgid(gid)) return(15);
		if (gid) if (setuid(uid)) return(15); }



	int chk_stat(const char *file) {
		if (stat(file, &fstat)) return(1); // path exists
		if (fstat.st_uid) return(2); // file is owned by root
		if (fstat.st_mode & (S_IWGRP|S_IWOTH)) return(3); // file is not world-writable
		return(0); };

	// Security checks
	if (ncap = chk_stat(self)) return(20 + ncap); // wrapper check
	if (ncap = chk_stat(py)) return(30 + ncap); // py check

	execv(py, argv); };
