#!/usr/local/bin/perl # ===================================================================== # scpjail - restricts a user account to doing nothing but scp'ing to # and from a "jailed" directory. Adapted from (and improved # on) a script included in the Snailbook FAQ. # --------------------------------------------------------------------- # $Id: scpjail,v 1.9 2004/08/10 17:46:55 ssklar Exp ssklar $ # ===================================================================== use strict; use Sys::Syslog; # ===================================================================== # the following options should be defined in this script: # ===================================================================== # $scp : the location of scp on this system ... my $scp = "/usr/local/bin/scp"; # $jail : the directory UNDERNEATH the user's home directory that the # user's account will be restricted to ... my $jail = "JAIL"; # $logfac : the facility to be used by syslog when logging messages from # this program ... my $logfac = "auth"; # ===================================================================== # change nothing below here (except in the RCS version, of course) # ===================================================================== # what's my name? ( my $me = $0 ) =~ s|\S+/||g; # who is running me? my $user = getpwuid($<); # open syslog for logging ... openlog ("$me", "pid", "$logfac") or die ("$me : couldn't open syslog, dying now.\n"); # log at priority info ... syslog ("info", "starting $me for $user"); # get the user's home directory ... my $home = sub { $_[7] } -> (getpwuid($<)) or fail ("info", "couldn't get user home directory, dying now."); # make sure that $SSH_ORIGINAL_COMMAND has a value ... unless ( $ENV{SSH_ORIGINAL_COMMAND} ) { fail ("info", "environment variable SSH_ORIGINAL_COMMAND undefined, dying now.") }; # split $SSH_ORIGINAL_COMMAND on whitespace ... my @command = split ( /\s+/, $ENV{SSH_ORIGINAL_COMMAND} ); # empty out the environment for safety's sake ... undef %ENV; # die unless the first element of @command is not "scp" ... unless ( $command[0] eq "scp" ) { fail ("info", "account rescticted to scp only, dying now.") }; shift @command; # start looping through the contents of @command ... my ($action, $file); while (@command) { # figure out the "action" and the "file" ... if ($command[0] eq "-d") { # the user is "putting" multiple files ... $action = "-d -t"; # $file is the destination directory and (possibily) the name that # the file is to be called on the server ... $file = "$command[$#command]"; last } elsif ($command[0] eq "-t") { # the user is "putting" a single file ... $action = "-t"; # $file is the destination directory and (possibily) the name that # the file is to be called on the server ... $file = "$command[$#command]"; last } elsif ($command[0] eq "-f") { # the user is "getting" a file or files from the server ... $action = "-f"; shift @command; # $file is either a single file or multiple files (space-separated) # that the user is retrieving ... $file = join(" ", @command); last } else { # the user either specified "-v" with their command, or is trying to # do evil things ... shift @command } }; # unless both $action and $file are defined, something went wrong ... unless (defined($action) && defined($file)) { fail ("info", "action and/or file is not defined, dying now.") }; # fix up potential weird values of $file ... if ($file eq "." || $file eq "~") { $file = "$home/$jail" }; if ($file eq "$home" || $file eq "$home/$jail") { $file = "$home/$jail" }; # check for possible shell escapes in the contents of $file ... if ( $file =~ /;|\(|\|\>|&/ ) { fail ("info", "file contains suspicious character: $file") }; # if we made it this far, log our success and do the requested # scp operation ... syslog ("notice", "executing $scp $action $file for user $user"); closelog (); exec ( "$scp", "$action", "$file" ); # --------------------------------------------------------------------- # fail : subroutine that logs an error to syslog, prints it to the, # user, and dies. # --------------------------------------------------------------------- sub fail { my ($priority, $msg) = @_; syslog ("$priority", "$msg"); closelog (); die ("$me : $msg\n") }; # --------------------------------------------------------------------- # here be POD ... # --------------------------------------------------------------------- =pod =head1 NAME scpjail - forces a restricted scp-only account within a jailed directory. =head1 USAGE =over 2 =item * Create the user account in the normal way, with a real shell, and a real home directory. Do NOT set a password for the account (i.e., have a "*" or "!" in /etc/security/user | /etc/shadow | whatever.) =item * Set the permissions on the user's home directory so that the user DOES NOT have write access. If other users on the system will need read or write access to content in the jailed user's home, that is fine, but it is critical that the jailed user does not have write access. # chmod 500 /home/luser ; ls -ld /home/luser dr-x------ 4 luser staff 512 May 09 21:41 /home/luser =item * Confirm that any files directly in the user's home directory are NOT writable by the user account. This includes any shell startup files. In fact, there is no reason to have any shell startup files, so if it is easier to just delete .login or .profile, go for it. =item * Create the .ssh directory in the user's home directory. This directory MUST be owned by the user account, and MUST be chmod'ed 500 (so that it is not writable by anyone, and is readable/executable only for the user.) # mkdir /home/luser/.ssh ; chmod 500 /home/luser/.ssh # ls -ld /home/luser/.ssh dr-x------ 2 luser system 512 May 07 19:50 /home/luser/.ssh =item * In that .ssh directory, place the user's public half of their keypair into the file "authorized_keys". THIS IS IMPORTANT: insert, on the same line as the key, before the key, the text: command="/path/to/scpjail". This is important, because it restricts any use of this key to the execution of this scpjail script, no matter what the user tries to do. It is also important to again make sure that the authorized_keys file is NOT writable by the user account (or anyone.) # chmod 400 /home/luser/.ssh/authorized_keys # ls -ld /home/luser/.ssh/authorized_keys -r-------- 1 luser system 1291 May 09 21:41 /home/luser/.ssh/authorized_keys # cat /home/luser/.ssh/authorized_keys command="/usr/local/sbin/scpjail" ssh-dss AAAAB3NMAAACBAIIPIu9j2 ... blah blah blah ... LQwxMc7k6xoIE1qFBWWXjMZeQ== luser@foobar =item * Finally, create the "jail" directory, inside the home directory of the user. By default, the name of this directory is "JAIL", but this can be changed by modifying the scpjail script. The user must be able to write to this directory (in fact, it should be the only place that the user can write to.) # mkdir /home/luser/JAIL ; chmod 740 /home/luser/JAIL # ls -ld /home/luser/JAIL drwxr----- 2 luser system 512 May 09 22:04 /home/luser/JAIL And that is it! =back =head1 LOGGING scpjail will write a message to syslog at facility "auth", priority "notice" each time it is used sucessfully: May 9 22:04:49 whippet scpjail[43364]: executing /usr/local/bin/scp -t /home/luser/JAIL for user luser scpjail also logs messages at priority "info" at numerous places in the script where the attempted connection might fail for various reasons: May 9 21:48:09 whippet scpjail[31482]: environment variable SSH_ORIGINAL_COMMAND undefined, dying now. =head1 CONFIGURATION There are three configurable items, all set by modifying the scpjail script: I<$scp> contains the full path and name of the scp program on the server. By default, it is defined as "/usr/local/bin/scp". I<$jail> contains the name of the directory within the user's home that the user will be jailed into. By default, it is defined as "JAIL". I<$logfac> contains the facility at which scpjail will send syslog messages. By default, it is defined as "auth". =head1 VERSION $Revision: 1.9 $ =head1 AUTHOR Sandor W. Sklar Stanford University ITSS ssklar@stanford.edu http://www.stanford.edu/~ssklar/scpjail/ =head1 COPYRIGHT This program is free software; you may redistribute it and/or modify it under the same terms as Perl itself. =cut