Logo Search packages:      
Sourcecode: dchroot version File versions  Download package

dchroot.c

/*
   dchroot - Execute commands under different root filesystems.

   Copyright (C) 2002-2004 David Kimdon <dwhedon@debian.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.
   
 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/wait.h>
#include <limits.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>

#define PROGNAME "dchroot"
#define CHROOTS_CONF "/etc/dchroot.conf"
#define WHITE_CHARS " \t\n\r\f\v"

#ifndef PATH_MAX
#define PATH_MAX 512
#endif

struct chroot_map;

00042 struct chroot_map {
      char name[128];
      char newroot[PATH_MAX];
      struct chroot_map *next;
};

00048 static struct dchroot_opts {
      char *newroot;
      int quiet, all;
      char dir[PATH_MAX];
} opts = { 0 };


static void dchroot_perror(char *s)
{
      fprintf(stderr, PROGNAME ": ");
      perror(s);
}


static int dchroot_printf(const char *fmt, ...)
{
      va_list ap;
      int bytes;

      bytes = fprintf(stdout, PROGNAME ": ");

      va_start(ap, fmt);
      bytes += vfprintf(stdout, fmt, ap);
      va_end(ap);

      return bytes;
}


static void free_chroots(struct chroot_map *chroots)
{
      struct chroot_map *temp;

      while (chroots) {
            temp = chroots;
            chroots = chroots->next;
            free(temp);
      }
}


/*
   Read chroot config file.  Lines starting with '#' and lines containing only 
   whilespace are ignored.  Valid input lines contain a name and a path 
   separated by whitespace.  
   
   On success return a malloc'd linked list of chroots.
   On error return NULL.
*/
static struct chroot_map *read_chroots(char *conffile)
{
      FILE *fp;
      char buffer[BUFSIZ], *name, *newroot;
      struct stat statbuf;
      int line = 0;
      struct chroot_map *new, *chroots = NULL;
      struct stat buf;

      if (stat(conffile, &buf)) {
            dchroot_printf("stat '%s': %s\n", conffile,
                         strerror(errno));
            return NULL;
      }

      if (buf.st_mode & S_IWOTH || buf.st_uid != 0) {
            dchroot_printf("Bad permissions on '%s'\n", conffile);
            if (buf.st_mode & S_IWOTH) {
                  dchroot_printf("'%s' must not be writable by other.\n",
                              conffile);
            }
            if (buf.st_uid != 0) {
                  dchroot_printf("'%s' must be owned by root.\n",
                              conffile);
            }
            return NULL;

      }

      if ((fp = fopen(conffile, "r")) == NULL) {
            dchroot_printf("fopen '%s': %s\n", conffile,
                         strerror(errno));
            return NULL;
      }

      while (fgets(buffer, sizeof(buffer), fp)) {
            line++;

            if (buffer[0] == '#')
                  continue;

            if ((name = strtok(buffer, WHITE_CHARS)))
                  newroot = strtok(NULL, WHITE_CHARS);
            else
                  continue;

            if (!newroot || strtok(NULL, WHITE_CHARS)
                || stat(newroot, &statbuf)
                || !S_ISDIR(statbuf.st_mode)) {
                  dchroot_printf("Invalid input line %s:%d\n",
                               conffile, line);
                  goto failure;
            }

            if (!(new = malloc(sizeof(struct chroot_map)))) {
                  dchroot_perror("malloc");
                  goto failure;
            }

            memset(new, 0, sizeof(struct chroot_map));

            if (chroots) {
                  struct chroot_map *ptr = chroots;
                  while (ptr->next)
                        ptr = ptr->next;
                  ptr->next = new;
            } else {
                  chroots = new;
            }

            new->next = NULL;

            if ((snprintf(new->name, sizeof(new->name), "%s", name))
                > (sizeof(new->name) - 1)) {
                  dchroot_printf("Name too long %s:%d\n",
                               conffile, line);
                  goto failure;
            }

            if ((snprintf
                 (new->newroot, sizeof(new->newroot), "%s", newroot))
                > (sizeof(new->newroot) - 1)) {
                  dchroot_printf("Path too long %s:%d\n",
                               conffile, line);
                  goto failure;
            }
      }

      fclose(fp);

      if (chroots) {
            return chroots;
      } else {
            dchroot_printf("No chroots found in config file '%s'.\n",
                        conffile);
            return NULL;
      }

      failure:

      dchroot_printf("Error reading config file '%s'.\n", conffile);

      fclose(fp);
      free_chroots(chroots);
      return NULL;
}

/*
    Execute the command described by 'argv' in the chroot pointed to by
    'chroot_to', or if 'argv' is NULL invoke the shell of the current user.

    return 0 on success
    return -1 on failure
*/
static int
do_chroot(struct chroot_map *chroot_to, char *argv[], char *username)
{
      int status;
      pid_t pid;
      char **cmd;

      if ((pid = fork()) == -1) {
            dchroot_perror("fork");
            return -1;
      } else if (pid == 0) {

            if (!argv) {
                  /* Will execute a shell in the new chroot. */

                  cmd = malloc(sizeof(char *) * 4);

                  if (!cmd) {
                        dchroot_perror("malloc");
                        exit(EXIT_FAILURE);
                  }

                  if (!opts.quiet) {
                        printf("Executing shell in '%s' chroot.\n",
                               chroot_to->name);
                        fflush(NULL);
                  }

                  cmd[0] = "/bin/su";

                  if (opts.dir[0] == '\0') {
                        /* /bin/su, -, username, NULL */
                        cmd[1] = "-";
                  } else {
                        /* /bin/su, -p, username, NULL */
                        cmd[1] = "-p";
                  }
                  cmd[2] = username;
                  cmd[3] = NULL;

            } else {
                  /* Will execute the command in argv[] in the
                   * new chroot. 
                   */

                  int command_len = 0;
                  char *command = NULL;
                  int i = 0;
                  
                  while (argv[i] != NULL) {
                        command_len += strlen(argv[i++])+1;
                  }
                  
                  cmd = malloc(sizeof(char *) * 6);
                  command = malloc(sizeof(char) * command_len);
                  
                  if (!cmd || !command) {
                        dchroot_perror("malloc");
                        exit(EXIT_FAILURE);
                  }
                  
                  command[0] = '\0';
                  for (i=0; argv[i] != NULL; i++) {
                        if (command[0] != '\0') {
                              strcat(command, " ");
                        }
                        strcat(command, argv[i]);
                  }
                  
                  i = 0;
                  cmd[i++] = "/bin/su";
                  
                  if (opts.dir[0] == '\0') {
                        /* /bin/su - username -c "argv[0] argv[1] ..." */
                        cmd[i++] = "-";
                  } else {
                        /* /bin/su -p username -c "argv[0] argv[1] ..." */
                        cmd[i++] = "-p";
                  }
                  
                  cmd[i++] = username;
                  cmd[i++] = "-c";
                  cmd[i++] = command;
                  cmd[i] = NULL;

                  if (!opts.quiet) {
                        printf("(%s) ", chroot_to->name);
                        i = 4;
                        while (cmd[i]) {
                              printf("%s ", cmd[i]);
                              i++;
                        }
                        printf("\n");
                        fflush(NULL);
                  }
            }

            if (chdir(chroot_to->newroot)) {
                  dchroot_perror("chdir");
                  exit(EXIT_FAILURE);
            }

            if (chroot(".") == -1) {
                  dchroot_perror("chroot");
                  exit(EXIT_FAILURE);
            }
      
            /* Effective UID is currently 0, but real UID is still
             * the same as the UID that invoked dchroot.  Set real
             * UID so /bin/su doesn't ask for a password. */
            if (setuid(0) == -1) {
                  dchroot_perror("setuid");
                  exit(EXIT_FAILURE);
            }

            if (opts.dir[0] != '\0') {
                  if (chdir(opts.dir)) {
                        dchroot_perror("chdir");
                  }
            }

            execv(cmd[0], cmd);
            fprintf(stderr, PROGNAME ": Failed to exec() '%s' : %s\n",
                  cmd[0], strerror(errno));
            exit(EXIT_FAILURE);
      }

      if (wait(&status) != pid) {
            dchroot_perror("wait");
            return -1;
      }

      if (!WIFEXITED(status)) {
            dchroot_printf("Child exited abnormally.\n");
            return -1;
      }

      if (WEXITSTATUS(status) && !opts.quiet)
            dchroot_printf("Child exited non-zero.\n");

      return WEXITSTATUS(status);
}


static void list_chroots(struct chroot_map *chroots)
{
      int first = 1;

      printf("Available chroots: ");
      while (chroots) {
            printf("%s", chroots->name);

            if (first) {
                  printf(" [default]");
                  first = 0;
            }

            if ((chroots = chroots->next))
                  printf(", ");
      }
}

static int list_chroot(struct chroot_map *chroots,
                  const char        *chroot)
{
      while (chroots) {
            if (strcmp(chroot, chroots->name) == 0) {
                  printf("%s\n", chroots->newroot);
                  return EXIT_SUCCESS;
            }
            chroots = chroots->next;
      }
      fprintf(stderr, "%s: chroot not found\n", chroot);
      return EXIT_FAILURE;
}

static void usage(char *cmd)
{
      printf("Usage: %s [OPTION...] [COMMAND]\n", cmd);
      printf
          ("Execute COMMAND under a different root filesystem, " 
           "or if no command is given\n");
      printf("invoke a shell.\n");
      printf("\n");
      printf("  -a               Execute in all known chroots.\n");
      printf("  -c newroot       Execute in specified chroot.\n");
      printf("  -l               List available chroots.\n");
      printf("  -p chroot        Print path to specified chroot.\n");
      printf("  -d               In chroot, preserve envrionment.\n"
             "                   New shell is not a login shell.\n");
      printf("  -q               Be quiet.\n");
      printf("  -h               Print help message.\n");
      printf("  -V               Print program version.\n");

}


int main(int argc, char *argv[])
{
      struct chroot_map *chroots, *chroot_to = NULL, *ptr;
      int rv = 0;
      char **cmd;
      struct passwd *pwd;
      int opt;

      while ((opt = getopt(argc, argv, "ac:ldqhVp:")) != -1) {
            switch (opt) {
            case 'a':
                  opts.all = 1;
                  break;
            case 'c':
                  opts.newroot = optarg;
                  break;
            case 'd':
                  if (getcwd(opts.dir, PATH_MAX) == NULL) {
                        dchroot_perror("getcwd");
                        exit(EXIT_FAILURE);
                  }
                  break;
            case 'l':
                  if ((chroots = read_chroots(CHROOTS_CONF)) == NULL) {
                        printf("No chroots found.\n");
                  } else {
                        list_chroots(chroots);
                        printf("\n");
                  }
                  exit(EXIT_SUCCESS);
                  break;
            case 'q':
                  opts.quiet = 1;
                  break;
            case 'V':
                  printf("dchroot " DCHROOT_VERSION "\n");
                  exit(EXIT_SUCCESS);
                  break;
            case 'p':
                  if ((chroots = read_chroots(CHROOTS_CONF)) == NULL) {
                        printf("No chroots found.\n");
                        exit(EXIT_FAILURE);
                  }
                  exit(list_chroot(chroots, optarg));
                  break;
            case 'h':
                  usage(argv[0]);
                  exit(EXIT_SUCCESS);
                  break;
            default:
                  usage(argv[0]);
                  exit(EXIT_FAILURE);
                  break;
            }
            if (opts.all && opts.newroot) {
                  printf("Options -c and -a are incompatible.\n");
                  usage(argv[0]);
                  exit(EXIT_FAILURE);
            }
      }

      if ((chroots = read_chroots(CHROOTS_CONF)) == NULL) {
            rv = -1;
            goto cleanup;
      }

      cmd = (argc - optind) ? argv + optind : NULL;

      if ((pwd = getpwuid(getuid())) == NULL) {
            dchroot_perror("getpwuid");
            exit(EXIT_FAILURE);
      }

      if (opts.all) {
            /* Execute the command in all chroots. */
            ptr = chroots;

            while (ptr) {
                  if ((rv = do_chroot(ptr, cmd, pwd->pw_name))) {
                        dchroot_printf("Operation failed.\n");
                  }

                  ptr = ptr->next;
            }
            goto cleanup;
      }

      if (opts.newroot == NULL) {
            /* default to first chroot */
            chroot_to = chroots;
      } else {
            ptr = chroots;

            while (ptr) {
                  if (!strncmp
                      (ptr->name, opts.newroot, sizeof(ptr->name))) {
                        chroot_to = ptr;
                        break;
                  } else
                        ptr = ptr->next;
            }

            if (chroot_to == NULL) {
                  printf("Invalid chroot '%s'\n", opts.newroot);
                  list_chroots(chroots);
                  printf("\n");
                  rv = -1;
                  goto cleanup;
            }
      }

      rv = do_chroot(chroot_to, cmd, pwd->pw_name);
      if (rv != 0 && !opts.quiet) {
            dchroot_printf("Operation failed.\n");
      }

      cleanup:
      free_chroots(chroots);
      return rv;
}

Generated by  Doxygen 1.6.0   Back to index