/*
 * This program creates a new filesystem namespace, bind mounts /bin/bash
 * over /bin/sh and runs its arguments.
 * It needs to be installed setuid root and will drop the extra privileges
 * before running the argument.
 *
 * gcc -O2 -s switchsh.c -o switchsh
 * chown root:root switchsh
 * chmod u+s switchsh
 * ./switchsh /bin/sh --version
 *
 * Written by Marco d'Itri <md@linux.it>, released to the public domain.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>
#include <sys/syscall.h>	/* syscall */
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>		/* waitpid */
#include <sys/mount.h>		/* mount */

# include <sched.h>		/* unshare */

#ifndef USE_CLONE

/* old kernel headers may lack the system call number */
# if defined(__NR_unshare)
# elif defined(__i386__)
#  define __NR_unshare		310
# elif defined(__x86_64__)
#  define __NR_unshare		272
# elif defined(__powerpc__) || defined(__powerpc64__)
#  define __NR_unshare		282
# else
   /* update the kernel headers or add the number for the architecture */
#  error "unshare unsupported on this architecture!"
# endif

# ifndef SYS_unshare
#  define SYS_unshare __NR_unshare
# endif

# if !(__GLIBC__ >= 2 && __GLIBC_MINOR__ >= 4)
static inline int unshare(int flags)
{
    return syscall(SYS_unshare, flags);
}
# endif

#endif

/* Prototypes */
void err_quit(const char *, ...)
    __attribute__ ((noreturn, format(printf, 1, 2)));
void err_sys(const char *, ...)
    __attribute__ ((noreturn, format(printf, 1, 2)));

int main(int argc, char *argv[])
{
    int pid;

    if (!*++argv)
	err_quit("Usage: switchsh PROGRAM [ARG]...");

#ifdef USE_CLONE
    /* think about this as fork(2)... */
    pid = syscall(SYS_clone, CLONE_NEWNS | SIGCHLD, 0);
#else
    pid = fork();
#endif

    if (pid < 0) { /* error */
	if (errno == EPERM)
	    err_quit("This program must be setuid root!");
	err_sys("fork");
    }

    if (pid > 0) {
	/* parent */
	int status;

	/* wait for the child */
	waitpid(pid, &status, 0);
	exit(WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status) + 128);
    }

    /* child */
#ifndef USE_CLONE
    if (unshare(CLONE_NEWNS) < 0) {
	if (errno == ENOSYS)
	    err_quit("unshare requires a kernel >= 2.6.16");
	else
	    err_sys("unshare");
    }
#endif

    if (mount("/bin/bash", "/bin/sh", NULL, MS_BIND, NULL) < 0) {
	if (errno == EPERM)
	    err_quit("This program must be setuid root!");
	err_sys("mount");
    }

    /* permanently drop privileges */
    if (setgid(getgid()) < 0)
	err_sys("cannot drop GID 0");
    if (setuid(getuid()) < 0)
	err_sys("cannot drop UID 0");

    execv(*argv, argv);
    err_sys("Can't exec %s", argv[0]);
}

/* Error routines */
void err_sys(const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    vfprintf(stderr, fmt, ap);
    fprintf(stderr, ": %s\n", strerror(errno));
    va_end(ap);
    exit(1);
}

void err_quit(const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    vfprintf(stderr, fmt, ap);
    fputs("\n", stderr);
    va_end(ap);
    exit(1);
}

