paperbag.in 4.45 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/perl -wT
# 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

14
15
16
17
18
#
# Configure variables
#
my $TB		= "@prefix@";

19
20
# List of allowed commands - Mapping is from command entered by the user
# to the actual binary to run
21
22
%allowed = (	"avail"		=> "$TB/bin/avail",
		"inuse"		=> "$TB/bin/inuse",
23
		"if2port"	=> "$TB/sbin/if2port",
24
25
26
27
28
29
30
31
32
33
34
35
36
		"mac2if"	=> "$TB/bin/mac2if",
		"nalloc"	=> "$TB/bin/nalloc",
		"nfree"		=> "$TB/bin/nfree",
		"nodeip"	=> "$TB/bin/nodeip",
		"ns"		=> "$TB/bin/ns",
		"power"		=> "$TB/bin/power",
		"snmpit"	=> "$TB/bin/snmpit",
		"tbprerun"	=> "$TB/bin/tbprerun",
		"tbreport"	=> "$TB/bin/tbreport",
		"tbrun"		=> "$TB/bin/tbrun",
		"tbend"		=> "$TB/bin/tbend",
		"vpower"	=> "$TB/bin/vpower",
		"vsnmpit"	=> "$TB/bin/vsnmpit");
37

38
# Need to provide a simple path, because some scripts we call need one
39
$ENV{PATH} = "$TB/bin:/bin:/usr/bin:/usr/local/bin";
40
41
42
43
44
45
46
47
48
49
# 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";

50
$debug = 0;
51
$| = 1; # No line buffering, so that we can see the prompt
52
$interactive = 1;
53

54
if (@ARGV && ($ARGV[0] eq "-c")) { # We were called by sshd - transform args into a useful form
55
56
57
58
59
60
61
	my $bigarg = pop @ARGV;
	push(@ARGV,split(/\s+/,$bigarg));
	shift @ARGV; # Dispose of -c
	$interactive = 0;
	&debug("New args are: " . join(",",@ARGV) . "\n");
}
	
62
63
64
65
# 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
66
67
68
69
70
71
72
73
74
75
76
77
78
} elsif($ARGV[0] eq "cd") { # also understand 'cd dir &&' or 'cd dir ;' syntax to be a little more compatible
	  # 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);
	}
79
80
81
82
83
}

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

if ($interactive) {
	print $message;
}

do {{
	if ($interactive) {
		print $prompt;
		($command, @args) = split /\s+/,<>;
	}
	
99
	next unless $command; # Don't complain if they leave a blank command
100
101
	# Don't allow any naughty characters - kick the user off if they try
	foreach $string ($command, @args) {
102
		if ($string !~ m|^([A-Za-z0-9._\-/=]*)$|) {
103
			print "Sorry, you used a forbidden character\n";
104
			&debug("String was $string\n");
105
106
107
108
109
110
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
			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;
149
	chdir $dirname or warn "Unable to change directories to $dirname\n";
150
151
152
153
154
155

}

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