Net::S...pServer(3pm)

πŸ“¦
version 1.1.0 Β· 3 versions
, in debian bookworm, debian trixie, ubuntu noble

okay bestie, pull up a chair 🫢 this is the manual page for Net::SFTP::SftpServer, and it lives in the Perl user-contributed docs (that's the 3pm section, iykyk). it's long, so we're doing this in two parts β€” this is part one πŸ’–


πŸ‘‘ NAME πŸ‘‘

Net::SFTP::SftpServer β€” a Perl implementation of the SFTP subsystem with user access controls ✨

🌸 SYNOPSIS 🌸

use Net::SFTP::SftpServer;

my $sftp = Net::SFTP::SftpServer->new();

$sftp->run();

πŸ’… DESCRIPTION πŸ’…

so basically: it's a Perl port of sftp-server from OpenSSH πŸ¦‹ but it gives you access control on a per-user, per-command basis and better logging via syslog. iconic behaviour, honestly.

now the limitations compared to the OpenSSH implementation β€” read these, bestie, they matter:

  • Only files and directories are dealt with β€” other types are not returned on readdir πŸ’€
  • a virtual chroot is performed. / is treated as the user's home directory from the client's perspective, and all file access to / will be in /<home_path>/<username>. home_path is defined on object initialisation, not accessed from /etc/passwd. The script DOES NOT run under chroot β€” this prevents it needing SUID to start. The virtual chroot is enforced by the objects and prevents operations outside the home area 🏠
  • all symlinked files or directories are hidden and not accessible on request πŸ™ˆ
  • symlink returns permission denied. Please contact me if you need this functionality implementing 😭
  • readlink returns file does not exist. Please contact me if you need this functionality implementing 😭
  • setting of stats (set_stat or set_fstat) is disabled β€” the client will receive permission denied. Please contact me if you need this functionality implementing 😭
  • permissions for a file or dir are defaulted β€” the default is set on object initialisation ⭐

✨ USAGE ✨

Basic usage:

use Net::SFTP::SftpServer;

Import options:

:LOG    - Import logging functions for use in callbacks
:ACTION - Import constants for Allow/Deny of actions

πŸŽ€ Configuring syslog πŸŽ€

Syslog output mode must be configured in the use statement of the module, like this:

use Net::SFTP::SftpServer ( { log => 'local5' }, qw ( :LOG :ACTIONS ) );

Net::SFTP::SftpServer will default to using "daemon" β€” go check your system's syslog documentation for more deets πŸ’œ

πŸ¦‹ Options for object initialisation πŸ¦‹

  • debug β€” log debug-level information. Default is 0. (heads up bestie, this will create very large log files β€” use with caution πŸ’€)
  • home β€” filesystem location of user home directories. Default is /home
  • file_perms β€” octal file permissions to force on creation of files. Default is 0666, or the permissions specified by the file open command from the client
  • dir_perms β€” octal dir permissions to force on creation of directories. Default is 0777, or the permissions specified by the mkdir command from the client
  • on_file_sent, on_file_received β€” references to callback functions to be called on a complete file sent or received. The function will be passed the full path and filename on the filesystem as a single argument 🫢
  • use_tmp_upload β€” use temporary upload filenames while a file is being uploaded. This lets a monitoring script know which files are in transit without having to watch file size πŸ‘€. It's done transparently to the user, and the file will be renamed to the original file name on close. The temporary extension is .SftpXFR.$$. Default is 0
  • max_file_size β€” maximum file size (in bytes) which can be uploaded. Default is 0 (no limit)
  • valid_filename_char β€” array of valid characters for filenames
  • allow, deny β€” actions allowed or denied β€” see "PERMISSIONS" for details. Default is to allow ALL πŸ‘‘
  • fake_ok β€” array of actions (see action constants in "PERMISSIONS") which will be given the response SSH2_FX_OK instead of SSH2_FX_PERMISSION_DENIED when denied by the above deny options. Default is []
  • log_action_supress β€” array of actions to log quietly (logDetail β€” syslog debug level); logs messages whenever this action is performed. Default is quiet for SSH2_FXP_READ, SSH2_FXP_WRITE, SSH2_FX_OPENDIR, SSH2_FXP_READDIR, SSH2_FXP_CLOSE, SSH2_FXP_STAT SSH2_FXP_FSTAT and SSH2_FXP_LSTAT. Override with log_action, see below
  • log_action β€” array of actions to log loudly (logGeneral β€” syslog info level); logs messages whenever this action is performed πŸ“’
  • log_all_status β€” log all status messages at info level. By default SSH2_FX_OK and SSH2_FX_EOF will be logged at debug level

πŸ” PERMISSIONS πŸ”

  • ALL β€” All actions
  • NET_SFTP_SYMLINKS β€” Symlinks in paths to files (recommended deny to enforce chroot)
  • NET_SFTP_RENAME_DIR β€” Rename directories (recommended deny if also denying SSH2_FXP_MKDIR)
  • SSH2_FXP_OPEN
  • SSH2_FXP_CLOSE
  • SSH2_FXP_READ
  • SSH2_FXP_WRITE
  • SSH2_FXP_LSTAT
  • SSH2_FXP_STAT_VERSION_0
  • SSH2_FXP_FSTAT
  • SSH2_FXP_SETSTAT β€” Automatically denied, not implemented in module
  • SSH2_FXP_FSETSTAT β€” Automatically denied, not implemented in module
  • SSH2_FXP_OPENDIR
  • SSH2_FXP_READDIR
  • SSH2_FXP_REMOVE
  • SSH2_FXP_MKDIR
  • SSH2_FXP_RMDIR
  • SSH2_FXP_STAT
  • SSH2_FXP_RENAME
  • SSH2_FXP_READLINK β€” Automatically denied, not implemented in module
  • SSH2_FXP_SYMLINK β€” Automatically denied, not implemented in module

🫢 CALLBACKS 🫢

Callback functions can be used to perform actions when files are sent or received β€” for example, moving a fully downloaded file to a processed directory, or moving a received file into an input directory πŸ“¦. The callback is provided with a Net::SFTP::SftpServer::File object. This object allows access to the file within the virtual chroot environment. It will also return the full filename, or move the file to an explicit location on the full filesystem. Either of these actions will break the chroot, and the methods on the object will no longer be available πŸ’€

The following methods are provided:

  • read β€” read the data from the file, like IO::File->read. Will open the file for reading if it's not already open, and read back the data
  • open β€” will open the file, like IO::File->open, but the filename is not supplied
  • getFilename β€” will return the filename as within the virtual chroot 🏠
  • getFullFilenameBREAKCHROOT β€” will return the full filename on the real filesystem and break the virtual chroot
  • renameBREAKCHROOT β€” takes a single argument of the new filename, will rename the file to that location and break the virtual chroot

πŸ“ LOGGING πŸ“

If :LOG is used when including Net::SFTP::SftpServer, the following logging functions will be available:

logError    - syslog with a log level of error
logWarning  - syslog with a log level of warning
logGeneral  - syslog with a log level of info
logDetail   - syslog with a log level of debug, unless object was created with debug=>1 then syslog with a level of info

πŸ”₯ HARDENED EXAMPLE SCRIPT πŸ”₯

the following example script shows how this module can be used to give far greater control over what's allowed on your SFTP server. main character security energy, fr fr πŸ‘‘

this setup is aimed at admins who want to use SFTP uploads but do not wish to grant users a system account. You will also need to set both the SFTP subsystem and the user's shell to the sftp script, e.g. /usr/local/bin/sftp-server.pl

this configuration:

  • Enforces that users can only access the sftp script, not an ssh shell 🐚
  • Chroots them into their home directory in /var/upload/sftp
  • Sets all file permissions to 0660 and does not permit users to change them
  • Does not allow symlinks, making directories or renaming directories, but allows all other normal actions
  • Has a max upload filesize of 200Mb
  • Has a script memory limit of 100Mb for safety
  • Will log actions by user sftptest in debug mode
  • Will only allow alphanumeric plus _, . and - in filenames
  • Will call ActionOnSent and ActionOnReceived respectively when files have been sent or received
#!/usr/local/bin/perl

use strict;
use warnings;
use Net::SFTP::SftpServer ( { log => 'local5' }, qw ( :LOG :ACTIONS ) );
use BSD::Resource;        # for setrlimit

use constant DEBUG_USER => {
  SFTPTEST => 1,
};

# Security - make sure we have started this as sftp not ssh
unless ( scalar @ARGV == 2 and
         $ARGV[0] eq '-c'  and
         ($ARGV[1] eq '/usr/local/bin/sftp-server.pl') ){

       logError "SFTP connection attempted for application $ARGV[0] - exiting";
       print "\n\rYou do not have permission to login interactively to this host.\n\r\n\rPlease contact the system administrator if you believe this to be a configuration error.\n\r";
       exit 1;
}

my $MEMLIMIT = 100 * 1024 * 1024; # 100 Mb

# hard limits on process memory usage;
setrlimit( RLIMIT_RSS,  $MEMLIMIT, $MEMLIMIT );
setrlimit( RLIMIT_VMEM, $MEMLIMIT, $MEMLIMIT );

my $debug = (defined DEBUG_USER->{uc(getpwuid($>))} and DEBUG_USER->{uc(getpwuid($>))}) ? 1 : 0;

my $sftp = Net::SFTP::SftpServer->new(
  debug               => $debug,
  home                => '/var/upload/sftp',
  file_perms          => 0660,
  on_file_sent        => \&ActionOnSent,
  on_file_received    => \&ActionOnReceived,
  use_tmp_upload      => 1,
  max_file_size       => 200 * 1024 * 1024,
  valid_filename_char => [ 'a' .. 'z', 'A' .. 'Z', '0' .. '9', '_', '.', '-' ],
  deny                => ALL,
  allow               => [ (
                              SSH2_FXP_OPEN,
                              SSH2_FXP_CLOSE,
                              SSH2_FXP_READ,
                              SSH2_FXP_WRITE,
                              SSH2_FXP_LSTAT,
                              SSH2_FXP_STAT_VERSION_0,
                              SSH2_FXP_FSTAT,
                              SSH2_FXP_OPENDIR,
                              SSH2_FXP_READDIR,
                              SSH2_FXP_REMOVE,
                              SSH2_FXP_STAT,
                              SSH2_FXP_RENAME,
                           )],
  fake_ok             => [ (
                              SSH2_FXP_SETSTAT,
                              SSH2_FXP_FSETSTAT,
                           )],
);

$sftp->run();

sub ActionOnSent {
  my $fileObject = shift;
   ## Do Stuff
}

sub ActionOnReceived {
  my $fileObject = shift;
   ## Do Stuff
}

🧚 DEPENDENCIES 🧚

Stat::lsMode
Fcntl
POSIX
Sys::Syslog
Errno

🌈 SEE ALSO 🌈

✨ AUTHOR ✨

Okay so who do we have to thank for this absolute masterpiece? 🫢

  • Simon Day, Pirum Systems Ltd
  • cpan <at> simonday.info β€” yes bestie, that's literally the contact, hit them up πŸ’Œ

ngl, this part is the legal bit, and you should actually read it. No skimming. πŸ‘€

This whole thing is based on sftp-server.c Copyright (c) 2000-2004 Markus Friedl. All rights reserved. ✨

Then it got ported to Perl and extended by Simon Day, Copyright (c) 2009 Pirum Systems Ltd. All rights reserved. πŸ‘‘

And here's the permission situation, fr fr: Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. That's it, that's the deal. Slay. πŸŽ€


⚠️ THE BIG LOUD WARRANTY DISCLAIMER ⚠️

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. Not me exaggerating β€” this is literally all-caps in the source, so you know it's serious. 😭
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

Translation for the girlies: it's free, it's generous, but if it eats your data that's on you, bestie. πŸ’€


and that's the whole page, you made it to the end!! πŸŽ‰πŸ’…

This one came from perl v5.36.0 and got dated 2022-12-04, all filed under Net::S...pServer(3pm) in the User Contributed Perl Documentation. Go forth and sftp responsibly. πŸ¦‹πŸŒΈ

πŸ“¦ where this page comes from