/*
 * runcon [ context |
 *         ( [ -c ] [ -r role ] [-t type] [ -u user ] [ -l levelrange ] )
 *         command [arg1 [arg2 ...] ]
 *
 * attempt to run the specified command with the specified context.
 *
 * -r role  : use the current context with the specified role
 * -t type  : use the current context with the specified type
 * -u user  : use the current context with the specified user
 * -l level : use the current context with the specified level range
 * -c       : compute process transition context before modifying
 *
 * Contexts are interpreted as follows:
 *
 * Number of       MLS
 * components    system?
 *
 *     1            -         type
 *     2            -         role:type
 *     3            Y         role:type:range
 *     3            N         user:role:type
 *     4            Y         user:role:type:range
 *     4            N         error
 */

#include <config.h>
#include <stdio.h>
#include <getopt.h>
#include <selinux/selinux.h>
#include <selinux/context.h>
#ifdef HAVE_SELINUX_FLASK_H
# include <selinux/flask.h>
#else
# define SECCLASS_PROCESS 0
#endif
#include <sys/types.h>
#include "system.h"
#include "error.h"
#include "quote.h"
#include "quotearg.h"

/* The official name of this program (e.g., no `g' prefix).  */
#define PROGRAM_NAME "runcon"

#define AUTHORS "Russell Coker"

static struct option long_options[] = {
  {"role", required_argument, NULL, 'r'},
  {"type", required_argument, NULL, 't'},
  {"user", required_argument, NULL, 'u'},
  {"range", required_argument, NULL, 'l'},
  {"compute", no_argument, NULL, 'c'},
  {GETOPT_HELP_OPTION_DECL},
  {GETOPT_VERSION_OPTION_DECL},
  {NULL, 0, NULL, 0}
};

/* The name the program was run with. */
char *program_name;

void
usage (int status)
{
  if (status != EXIT_SUCCESS)
    fprintf (stderr, _("Try `%s --help' for more information.\n"),
	     program_name);
  else
    {
      printf (_("\
Usage: %s CONTEXT COMMAND [args]\n\
  or:  %s [ -c ] [-u USER] [-r ROLE] [-t TYPE] [-l RANGE] COMMAND [args]\n\
"), program_name, program_name);
      fputs (_("\
Run a program in a different security context.\n\
With neither CONTEXT nor COMMAND, print the current security context.\n\
\n\
  CONTEXT            Complete security context\n\
  -c, --compute      compute process transition context before modifying\n\
  -t, --type=TYPE    type (for same role as parent)\n\
  -u, --user=USER    user identity\n\
  -r, --role=ROLE    role\n\
  -l, --range=RANGE  levelrange\n\
\n\
"), stdout);
      fputs (HELP_OPTION_DESCRIPTION, stdout);
      fputs (VERSION_OPTION_DESCRIPTION, stdout);
    }
  exit (status);
}

int
main (int argc, char **argv, char **envp)
{
  char *role = NULL;
  char *range = NULL;
  char *user = NULL;
  char *type = NULL;
  char *context = NULL;
  security_context_t cur_context = NULL;
  security_context_t file_context = NULL;
  security_context_t new_context = NULL;
  bool compute_trans = false;

  context_t con;

  initialize_main (&argc, &argv);
  program_name = argv[0];
  setlocale (LC_ALL, "");
  bindtextdomain (PACKAGE, LOCALEDIR);
  textdomain (PACKAGE);

  atexit (close_stdout);

  while (1)
    {
      int option_index = 0;
      int c = getopt_long (argc, argv, "+r:t:u:l:c", long_options,
			   &option_index);
      if (c == -1)
	break;
      switch (c)
	{
	case 'r':
	  if (role)
	    error (EXIT_FAILURE, 0, _("multiple roles"));
	  role = optarg;
	  break;
	case 't':
	  if (type)
	    error (EXIT_FAILURE, 0, _("multiple types"));
	  type = optarg;
	  break;
	case 'u':
	  if (user)
	    error (EXIT_FAILURE, 0, _("multiple users"));
	  user = optarg;
	  break;
	case 'l':
	  if (range)
	    error (EXIT_FAILURE, 0, _("multiple levelranges"));
	  range = optarg;
	  break;
	case 'c':
	  compute_trans = true;
	  break;

	case_GETOPT_HELP_CHAR;
	case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
	default:
	  usage (EXIT_FAILURE);
	  break;
	}
    }

  if (argc - optind == 0)
    {
      if (getcon (&cur_context) < 0)
	error (EXIT_FAILURE, errno, _("failed to get current context"));
      fputs (cur_context, stdout);
      fputc ('\n', stdout);
      exit (EXIT_SUCCESS);
    }

  if (!(user || role || type || range || compute_trans))
    {
      if (optind >= argc)
	{
	  error (0, 0, _("you must specify -c, -t, -u, -l, -r, or context"));
	  usage (1);
	}
      context = argv[optind++];
    }

  if (optind >= argc)
    {
      error (0, 0, _("no command specified"));
      usage (1);
    }

  if (is_selinux_enabled () != 1)
    error (EXIT_FAILURE, 0,
	   _("runcon may be used only on a SELinux kernel"));

  if (context)
    {
      con = context_new (context);
      if (!con)
	error (EXIT_FAILURE, errno, _("failed to create security context: %s"),
	       quotearg_colon (context));
    }
  else
    {
      if (getcon (&cur_context) < 0)
	error (EXIT_FAILURE, errno, _("failed to get current context"));

      /* We will generate context based on process transition */
      if (compute_trans)
	{
	  /* Get context of file to be executed */
	  if (getfilecon (argv[optind], &file_context) == -1)
	    error (EXIT_FAILURE, errno,
		   _("failed to get security context of %s"),
		   quote (argv[optind]));
	  /* compute result of process transition */
	  if (security_compute_create (cur_context, file_context,
				       SECCLASS_PROCESS, &new_context) != 0)
	    error (EXIT_FAILURE, errno,
		   _("failed to compute a new context"));
	  /* free contexts */
	  freecon (file_context);
	  freecon (cur_context);

	  /* set cur_context equal to new_context */
	  cur_context = new_context;
	}

      con = context_new (cur_context);
      if (!con)
	error (EXIT_FAILURE, errno, _("failed to create security context: %s"),
	       quotearg_colon (cur_context));
      if (user && context_user_set (con, user))
	error (EXIT_FAILURE, errno, _("failed to set new user %s"), user);
      if (type && context_type_set (con, type))
	error (EXIT_FAILURE, errno, _("failed to set new type %s"), type);
      if (range && context_range_set (con, range))
	error (EXIT_FAILURE, errno, _("failed to set new range %s"), range);
      if (role && context_role_set (con, role))
	error (EXIT_FAILURE, errno, _("failed to set new role %s"), role);
    }

  if (security_check_context (context_str (con)) < 0)
    error (EXIT_FAILURE, errno, _("invalid context: %s"),
	   quotearg_colon (context_str (con)));

  if (setexeccon (context_str (con)) != 0)
    error (EXIT_FAILURE, errno, _("unable to set security context %s"),
	   quote (context_str (con)));
  if (cur_context != NULL)
    freecon (cur_context);

  execvp (argv[optind], argv + optind);

  {
    int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
    error (0, errno, "%s", argv[optind]);
    exit (exit_status);
  }
}
