paperbag.in 4.74 KB
Newer Older
1
#!/usr/bin/perl -wT
Leigh B. Stoller's avatar
Leigh B. Stoller committed
2 3
#
# EMULAB-COPYRIGHT
4
# Copyright (c) 2000-2003 University of Utah and the Flux Group.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
5 6 7
# All rights reserved.
#

8 9 10 11 12 13 14 15 16 17 18 19
# paperbag - Limited shell for use on ops.emulab.net
# Allows execution of commands found in a permitted list
# Also checks arguments for potentially dangerous characters (semicolons,
# pipes, redirects, etc)
# Robert Ricci, <ricci@cs.utah.edu>
# Novemeber 17, 2000

# TODO:
# Turn off coredumps in ourself, and our children
# Check to make sure only files/directories under user's $HOME can be
#  passed as arguments, CD'ed to, etc

20 21 22 23 24
#
# Configure variables
#
my $TB		= "@prefix@";

25 26
# List of allowed commands - Mapping is from command entered by the user
# to the actual binary to run
27
%allowed = (	"nalloc"	=> "$TB/bin/nalloc",
28
		"nfree"		=> "$TB/bin/nfree",
29
		"node_reboot"	=> "$TB/bin/node_reboot",
30
		"node_update"	=> "$TB/bin/node_update",
Leigh B. Stoller's avatar
Leigh B. Stoller committed
31
		"node_control"	=> "$TB/bin/node_control",
32
		"os_load"	=> "$TB/bin/os_load",
33
		"create_image"	=> "$TB/bin/create_image",
34
		"node_admin"	=> "$TB/bin/node_admin",
35
		"delay_config"	=> "$TB/bin/delay_config",
36
		"savelogs"	=> "$TB/bin/savelogs",
Leigh B. Stoller's avatar
Leigh B. Stoller committed
37
		"portstats"	=> "$TB/bin/portstats",
38
		"readycount"	=> "$TB/bin/readycount",
39 40
		"eventsys_control" => "$TB/bin/eventsys_control",
		"tbresize"	=> "$TB/bin/tbresize"
41
	   );
42

43
# Need to provide a simple path, because some scripts we call need one
44
$ENV{PATH} = "$TB/bin:/bin:/usr/bin:/usr/local/bin";
45 46 47 48 49 50 51 52 53 54
# Clean the environment of potentially nasty variables
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};

$prompt = "paperbag> "; # Prompt for interactive commands

# Following message gets displayed to interactive users
$message = "This is a restricted shell, and will only allow you to run
a limited set of commands. For an unrestrictive shell, log into
ops.emulab.net\n";

55
$debug = 0;
56
$| = 1; # No line buffering, so that we can see the prompt
57
$interactive = 1;
58

59
if (@ARGV && ($ARGV[0] eq "-c")) { # We were called by sshd - transform args into a useful form
60 61 62 63 64 65 66
	my $bigarg = pop @ARGV;
	push(@ARGV,split(/\s+/,$bigarg));
	shift @ARGV; # Dispose of -c
	$interactive = 0;
	&debug("New args are: " . join(",",@ARGV) . "\n");
}
	
67 68 69 70
# Check for a leading dir= option, which tells us which directory to start from
if (@ARGV && ($ARGV[0] =~ /dir=(.*)/)) {
	shift @ARGV;
	&cd($1); # Change to given directory
71
} elsif(@ARGV && ($ARGV[0] eq "cd")) { # also understand 'cd dir &&' or 'cd dir ;' syntax to be a little more compatible
72 73 74 75 76 77 78 79 80 81 82 83
	  # with other shells
	# Discard the cd
	shift @ARGV;
	my $dir = shift @ARGV;
	my $trash = shift @ARGV;
	if (($trash ne "&&") && ($trash ne ";")) {
		die "Syntax error: expected && or ; after cd <dir>\n";
	}
	# Untaint directory name - changing directories isn't a security risk in our situation
	if ($dir =~ /^(.*)$/) {
		&cd($1);
	}
84 85 86 87 88
}

if (@ARGV) { # We were given command line arguments
	$interactive = 0;
	$command = $ARGV[0];
89
	&debug("NON-INTERACTIVE: command is $command\n");
90
	@args = @ARGV[1 .. $#ARGV];
91
	&debug("NON-INTERACTIVE: args are " . join(",",@args) ."\n");
92 93 94 95 96 97 98 99 100 101 102 103
}

if ($interactive) {
	print $message;
}

do {{
	if ($interactive) {
		print $prompt;
		($command, @args) = split /\s+/,<>;
	}
	
104
	next unless $command; # Don't complain if they leave a blank command
105 106
	# Don't allow any naughty characters - kick the user off if they try
	foreach $string ($command, @args) {
107
		if ($string !~ m|^([A-Za-z0-9._\-/=\+,\,]*)$|) {
108 109
			print "Sorry, you used a forbidden character in your command line\n";
			print "Please contact testbed-ops\@emulab.net if you believe this message is in error\n";
110
			&debug("String was $string\n");
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
			exit(-1);
		} else {
			$string = $1; # Untaint
			&debug("No forbidden characters\n");
		}
	}

	# Strip off all path information from the command
	$command =~ /([^\/]+)$/; $command = $1;
	next unless $command; # Don't complain if they leave a blank command
	&debug("Command = $command, @args = " . join ",",@args . "\n");

	# 'builtin' commands
	if ($command eq "exit") { $interactive = 0; last; } # Quit loop
	if ($command eq "cd") {
		if (@args > 1) {
			print "cd: Too many arguments\n";
		} else {
			&cd($args[0]);
		}
		next;
	}

	if (!$allowed{$command}) {
		print "$command is not in the allowed list, sorry\n";
	} else {
		# Exec ourselves to be SURE that a shell doesn't get called
		# and do something insecure
		my $pid = fork();
		if ($pid == -1) { # fork failed
			print "Unable to fork process - Error number $?\n";
		} elsif ($pid) { # parent - wait for child to exit
			wait;
		} else { # child process
			exec ($allowed{$command},@args) or
				die "Unable to execute $command: $?\n";
		}
	}
}} while ($interactive);

# Change directory to the given directory, (TODO) checking whether
# it is appropriate to do so or not
sub cd {
	my $dirname = shift;
155
	chdir $dirname or warn "Unable to change directories to $dirname\n";
156 157 158 159 160 161

}

sub debug {
	if ($debug) { print @_; }
}