/*
    dchroot.c - enter a chroot environment on a Debian host.
    Copyright (c) 1999-2002  Ben Collins <bcollins@debian.org>
                  2003-2004,6  Martin Schulze <joey@infodrom.org>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <pwd.h>
#include <grp.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include "dchroot.h"
#include "config.h"

#define CONFFILE "/etc/dchroot.conf"

/* Permit programs other than the shell to be executed */
#define EXECUTE_COMMAND

struct chroot_map **chroots;

extern char *__progname;

int permitted (char *chroot, const char *name) {
    char *cp = chroot;
    char *end;

    while ((end = strchr(cp, ',')) != NULL) {
	if (!strncmp(cp, name, end-cp))
	    return 1;
	cp = ++end;
    }
    if (!strcmp(cp, name))
	return 1;

    return 0;
}

void list_chroots(const char *name) {
	int i;
	struct stat buf;

	fputs("Available chroots:", stderr);
	for (i = 0; chroots[i] != NULL; i++) {
	    if (stat(chroots[i]->subdir, &buf) == 0 && S_ISDIR(buf.st_mode))
		if (!chroots[i]->users || permitted(chroots[i]->users, name))
			fprintf(stderr, " %s", chroots[i]->name);
	}
	fputs("\n", stderr);
}

void list_chroot_paths() {
    int i;
    struct stat buf;
    int k;
    char **singular = NULL;

    for (i = 0; chroots[i]; i++) {
	if (stat(chroots[i]->subdir, &buf) == 0 && S_ISDIR(buf.st_mode)) {
	    if (singular != NULL) {
		for (k = 0; singular[k] != NULL && strcmp(singular[k], chroots[i]->subdir); k++);
		if (singular[k] == NULL) {
		    if ((singular = (char **)realloc(singular, (k+2)*sizeof(char *))) == NULL) {
			perror("realloc");
			exit(1);
		    }
		    singular[k++] = chroots[i]->subdir;
		    singular[k] = NULL;
		}
	    } else {
		if ((singular = (char **)malloc(2*sizeof(char *))) == NULL) {
		    perror("malloc");
		    exit(1);
		}
		singular[0] = chroots[i]->subdir;
		singular[1] = NULL;
	    }
	}
    }
    for (k = 0; singular[k] != NULL; k++)
	printf("%s\n", singular[k]);
    if (singular != NULL)
	free(singular);
}

void usage(const char *name) {
#ifndef EXECUTE_COMMAND
	fprintf(stderr, "Usage: %s <chroot>\n", __progname);
#else
	fprintf(stderr, "Usage: %s <chroot> [<command>]\n", __progname);
#endif
	list_chroots(name);
}

int main (int argc, char *argv[]) {
	char chroot_to[PATH_MAX] = "";
#ifndef USE_LOGIN
	char shell[PATH_MAX];
#endif
	int exist = 0;
	struct passwd *pwd = getpwuid(getuid());
	struct stat buf;
	int i;
	int ret;

	if (pwd == NULL) {
		fprintf(stderr, "FATAL: could not get passwd entry for current user!\n");
		exit(10);
	}
	if ((chroots = parse_config(CONFFILE)) == NULL) {
		fprintf(stderr, "FATAL: no chroot environments available!\n");
		exit(10);
	}

#ifdef EXECUTE_COMMAND
	if (argc < 2) {
#else
	if (argc != 2) {
#endif
		usage(pwd->pw_name);
		exit(1);
	}
	if (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")) {
		usage(pwd->pw_name);
		exit(0);
	} else if (!strcmp(argv[1], "--list") || !strcmp(argv[1], "-l")) {
		list_chroots(pwd->pw_name);
		exit(0);
	} else if (!strcmp(argv[1], "--listpaths")) {
		list_chroot_paths();
		exit(0);
	}

	for (i = 0; chroots[i]; i++)
		if (!strcmp(chroots[i]->name, argv[1])) {
			strncpy(chroot_to, chroots[i]->subdir, sizeof(chroot_to)-1);
			exist = 1;
			break;
		}

	if (!exist) {
		usage(pwd->pw_name);
		exit(1);
	}

	if (getuid() && chroots[i]->users && !permitted(chroots[i]->users, pwd->pw_name)) {
		fprintf(stderr, "You are not authorized to access the `%s' chroot!\n",
			argv[1]);
		exit(1);
	}

	/* First we chdir to the chroot, so our '.' is the proper inode */
	if (chdir(chroot_to)) {
		perror("chdir");
		exit(1);
	}

	/* All is well, so far, chroot */
	if (chroot(".") == -1) {
		perror("chroot");
		exit(1);
	}

	if (chdir("/")) {
		perror("chdir");
		exit(1);
	}

#ifndef USE_LOGIN
        /* Revert to /bin/sh if the login shell is not found */
        if (stat(pwd->pw_shell, &buf) && (errno == ENOENT)) {
                pwd->pw_shell = "/bin/sh";
                printf("Your default shell is not available. "
			"Reverting to /bin/sh.\n");
        }
        else if (errno) {
                perror("login shell");
                exit(1);
        }

	/* Now switch to the home directory */
	if (chdir(pwd->pw_dir)) {
		perror("chdir");
		exit(1);
	}

#ifdef EXECUTE_COMMAND
	if (argc == 3) {
		if (stat(argv[2], &buf) && (errno == ENOENT)) {
			perror("stat");
			exit(1);
		}

		ret = snprintf(shell, sizeof(shell), "%s", basename(argv[2]));
		if (ret == -1 || ret > sizeof(shell) - 1) {
			perror("snprintf");
			exit(1);
		}
	} else {
#endif
	ret = snprintf(shell, sizeof(shell), "-%s", basename(pwd->pw_shell));
	if (ret == -1 || ret > sizeof(shell) - 1) {
		perror("snprintf");
		exit(1);
	}
#ifdef EXECUTE_COMMAND
	}
#endif

	if (initgroups(pwd->pw_name, pwd->pw_gid)) {
		perror("initgroups");
		exit(1);
	}
	if (setuid(pwd->pw_uid)) {
		perror("setuid");
		exit(1);
	}

	/* Sanity check */
	if (geteuid() != pwd->pw_uid) {
		fprintf(stderr, "WHOA, we aren't running as your uid...something gone astray!\n");
		exit(10);
	}

#ifdef EXECUTE_COMMAND
        if (argc == 3) {
		fprintf(stderr, "Executing %s in chroot: %s\n", shell, chroot_to);
		execle(argv[2], shell, NULL, environ);
	} else {
#endif

	fprintf(stderr, "Executing shell in chroot: %s\n", chroot_to);
	execle(pwd->pw_shell, shell, NULL, environ);

#ifdef EXECUTE_COMMAND
	}
#endif

#else
	fprintf(stderr, "Logging in to chroot: %s\n", chroot_to);
	/* We need full root privs for this to work */
	setuid(0);
	execle("/bin/login", "login", "-f", pwd->pw_name, NULL, environ);
#endif

	perror("execle");
	exit(1);
}
