paperbag.in 5.52 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-2004 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
#
# Configure variables
#
my $TB		= "@prefix@";
24
my $USERNODE    = "@USERNODE@";
25

26
27
# List of allowed commands - Mapping is from command entered by the user
# to the actual binary to run
28
%allowed = (	"node_reboot"	=> "$TB/bin/node_reboot",
29
		"node_update"	=> "$TB/bin/node_update",
Leigh B. Stoller's avatar
Leigh B. Stoller committed
30
		"node_control"	=> "$TB/bin/node_control",
31
		"os_load"	=> "$TB/bin/os_load",
32
		"create_image"	=> "$TB/bin/create_image",
33
		"node_admin"	=> "$TB/bin/node_admin",
Mike Hibler's avatar
Mike Hibler committed
34
		"node_list"	=> "$TB/bin/node_list",
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
		"eventsys_control" => "$TB/bin/eventsys_control",
Leigh B. Stoller's avatar
Leigh B. Stoller committed
40
		"batchexp"	=> "$TB/bin/batchexp",
41
42
		"nscheck"	=> "$TB/bin/nscheck",
		"swapexp"	=> "$TB/bin/swapexp",
43
		"endexp"	=> "$TB/bin/endexp",
44
		"tbreport"	=> "$TB/bin/tbreport",
45
		"xmlrpc/experiment" => "$TB/sbin/sshxmlrpc_server.py",
46
	   );
47

48
49
50
51
52
53
54
55
56
57
58
59
60
#
# Scrub the environment - delete all but a few variables we consider to be
# safe.
#
my %SAFE_ENV_VARS = (LOGNAME => 1, TERM => 1, SHELL => 1, HOME => 1, USER => 1,
    SSH_CLIENT => 1, SSH_CONNECTION => 1, SSH_AUTH_SOCK => 1, SSH_TTY => 1);

foreach my $var (keys %ENV) {
    if (!$SAFE_ENV_VARS{$var}) {
	delete $ENV{$var};
    }
}

61
# Need to provide a simple path, because some scripts we call need one
62
$ENV{PATH} = "$TB/bin:/bin:/usr/bin:/usr/local/bin";
63
64
65
66
67
68

$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
69
$USERNODE\n";
70

71
72
73
# Whether or not to allow interactive sessions
$allow_interactive = 0;

74
$debug = 0;
75
$| = 1; # No line buffering, so that we can see the prompt
76
77
78
79
80
if ($allow_interactive) {
    $interactive = 1;
} else {
    $interactive = 0;
}
81

82
if (@ARGV && ($ARGV[0] eq "-c")) { # We were called by sshd - transform args into a useful form
83
84
85
86
87
88
89
	my $bigarg = pop @ARGV;
	push(@ARGV,split(/\s+/,$bigarg));
	shift @ARGV; # Dispose of -c
	$interactive = 0;
	&debug("New args are: " . join(",",@ARGV) . "\n");
}
	
90
91
92
93
# 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
94
} elsif(@ARGV && ($ARGV[0] eq "cd")) { # also understand 'cd dir &&' or 'cd dir ;' syntax to be a little more compatible
95
96
97
98
99
100
101
102
103
104
105
106
	  # 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);
	}
107
108
109
110
111
}

if (@ARGV) { # We were given command line arguments
	$interactive = 0;
	$command = $ARGV[0];
112
	&debug("NON-INTERACTIVE: command is $command\n");
113
	@args = @ARGV[1 .. $#ARGV];
114
	&debug("NON-INTERACTIVE: args are " . join(",",@args) ."\n");
115
116
117
118
119
120
121
122
123
124
125
126
}

if ($interactive) {
	print $message;
}

do {{
	if ($interactive) {
		print $prompt;
		($command, @args) = split /\s+/,<>;
	}
	
127
	next unless $command; # Don't complain if they leave a blank command
128
129
	# Don't allow any naughty characters - kick the user off if they try
	foreach $string ($command, @args) {
130
		if ($string !~ m|^([A-Za-z0-9._\-/=\+,\,]*)$|) {
131
132
			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";
133
			&debug("String was $string\n");
134
135
136
137
138
139
140
			exit(-1);
		} else {
			$string = $1; # Untaint
			&debug("No forbidden characters\n");
		}
	}

141
142
143
144
145
146
147
148
149
150
	# xmlrpc is special for now, while we use paperbag for it.
	# Pass the second token as the first arg to the server.
	if ($command =~ /^xmlrpc\/(\w*)$/) {
	        unshift(@args, $1);
		$command = "xmlrpc/$1";
	}
	else {
	    # Strip off all path information from the command
	    $command =~ /([^\/]+)$/; $command = $1;
	}
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
	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;
185
	chdir $dirname or warn "Unable to change directories to $dirname\n";
186
187
188
189
190
191

}

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