chrooting/jailing transfer-only accounts

Sandor W. Sklar ssklar at
Thu May 23 05:49:49 EST 2002

At 9:20a -0500 5/22/02, Ben Lindstrom wrote:
>I'm sorry but I know I don't read binhex.

sorry bout that; I've pasted the script below (and attached it with 

>But assuming you did what has been discussed here before  which is wrote
>some from of program that detects the -c argument passed to it and accept
>or deny the commands.  This can work for sftp-server also.  Because we
>do ${SHELL} -c sftp-server just like one would expect.

Right, but when using (openssh) scp, the $SSH_ORIGINAL_COMMAND 
contains "scp", and one of several arguments, and the name (or names) 
of the file(s) being transferred.  Thus, it is easy to break up that 
command and modify it on the server-side.  I see no equivalent way of 
doing so when the server is spawning the sftp-server.


# =====================================================================
# 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.8 2002/05/22 19:05:03 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 ...


     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]";


	} 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]";


	} 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 
		# that the user is retrieving ...

		$file   = join(" ", @command);


	} 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 ...
# ---------------------------------------------------------------------


=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 

   # 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 

   # cat /home/luser/.ssh/authorized_keys
   command="/usr/local/sbin/scpjail" ssh-dss AAAAB3NMAAACBAIIPIu9j2
   ... blah blah blah ...
   LQwxMc7k6xoIE1qFBWWXjMZeQ== luser at 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!


=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 

    May  9 21:48:09 whippet scpjail[31482]: environment variable
    SSH_ORIGINAL_COMMAND undefined, dying now.


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 

I<$logfac> contains the facility at which scpjail will send syslog 
messages.  By default, it is defined as "auth".

=head1 VERSION

   $Revision: 1.8 $

=head1 AUTHOR

   Sandor W. Sklar
   Stanford University ITSS

   ssklar at


This program is free software; you may redistribute it and/or modify 
it under the same terms as Perl itself.


>- Ben
>On Tue, 21 May 2002, Sandor W. Sklar wrote:
>>  Folks,
>>  I've been tasked to find a solution that will create
>>  file-transfer-only accounts that are jailed or chrooted to a specific
>>  directory.  (Not an uncommon task, I think.)
>>  Using the OpenSSH server and the OpenSSH scp client program, I can
>>  achieve the goal of having a file transfer only account jailed to a
>>  specified directory, by using the "scpjail" script (attached) as a
>>  forced command.
>>  However, if the client is using the SSH.COM's scp2 client program,
>>  the above technique does not work, since the commercial version uses
>>  sftp as the underlying method.
>>  So, the only solution I can see is to use one of the several
>>  chrooting patches that are floating around to the OpenSSH source, and
>>  set the user's shell to sftp-server.  If I do this, I make it
>>  impossible to use the OpenSSH scp client ; all connections must be
>>  done using sftp clients.  I am also tied to selecting and using one
>>  of these patches, which I admit, I do not have the technical ability
>>  to judge on their merits and potential weaknesses.  I am phobic about
>>  using patches that are not part of the baseline code (especially for
>>  security-related software), as it creates one more thing to worry
>>  about.
>>  My question is, does anyone see a solution that I am missing here?
>>  Complaining to SSH.COM is not a solution, as it does not solve my
>>  problem.  It is not in my power to force the user community to use
>>  only the OpenSSH implementation.
>>  I've seen many mails on this list lately talking about the pros and
>>  cons of including chroot-ability; the people who seem to feel that it
>>  is unnecessary have said that it is easy enough to implement outside
>>  of OpenSSH.  I don't have the ability to do so; among the community
>>  of OpenSSH users, I doubt I'm alone in this.
>>  (As an aside, I'd appreciate it if people would look at the attached
>>  script, and let me know if they can see any obvious holes in it.
>>  I've tried unsuccessfully to break out if it is set up properly, but
>>  others may have more success.)
>>  Thanks, -S-
>>  --
>>     Sandor W. Sklar  -  Unix Systems Administrator  -  Stanford 
>>University ITSS
>>     Non impediti ratione cogitationis. 
>openssh-unix-dev at mailing list

   Sandor W. Sklar  -  Unix Systems Administrator  -  Stanford University ITSS
   Non impediti ratione cogitationis.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: %scpjail
Type: application/applefile
Size: 117 bytes
Desc: not available
Url : 
-------------- next part --------------
A non-text attachment was scrubbed...
Name: scpjail
Type: application/octet-stream
Size: 8677 bytes
Desc: not available
Url : 

More information about the openssh-unix-dev mailing list