Last active
February 21, 2022 21:08
-
-
Save phuze/8f66a26767500398d271b42013672536 to your computer and use it in GitHub Desktop.
Perl script for managing users in a Berkeley DB within a Linux environment. Read the full article: Building a Secure FTP Server — https://phuze.dev/building-a-secure-ftp-server
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/perl -w | |
# | |
# USAGE | |
# ===== | |
# Run this script from within linux. | |
# Note: you must include the leading dot. | |
# | |
# ./path/to/users usage | |
# | |
# DOCUMENTATION | |
# ============= | |
# Perl DB_File: https://perldoc.perl.org/DB_File | |
# PAM_userdb: https://www.man7.org/linux/man-pages/man8/pam_userdb.8.html | |
use strict; | |
use DB_File; | |
# | |
# CONFIGURATION | |
# ============= | |
my $DBFILE = '/etc/vsftpd/users.db'; | |
my $DBMODE = 0660; | |
# open or create the database | |
tie my %db, 'DB_File', $DBFILE, O_RDWR|O_CREAT, $DBMODE, $DB_HASH; | |
# quit | |
sub quit () { | |
untie %db; | |
exit; | |
} | |
# usage | |
sub usage () { | |
print "\n"; | |
print "Manage (PAM) userdb with crypted passwords.\n"; | |
print "You may pass a dash (-) for [user] and/or [pass] to prompt for input instead.\n"; | |
print "Adding a new user will create both a virtual FTPS user, as well as a local SFTP user.\n"; | |
print "Users are jailed in both scenarios, and local users have no shell access.\n"; | |
print "\n"; | |
print "Usage: $0 <command> [user] [pass]\n"; | |
print " <command> can be:\n"; | |
print " list Show all users.\n"; | |
print " add Add <user> to the database with <pass>.\n"; | |
print " edit Update <user> with new <pass>.\n"; | |
print " del Remove <user> from the database.\n"; | |
print "\n"; | |
quit(); | |
} | |
# hash a password | |
sub hashPass ($) { | |
my $password = shift; | |
# Generate a salt | |
my @chars = ('.', '/', 0..9, 'A'..'Z', 'a'..'z'); | |
my $salt = (@chars)[rand @chars] . (@chars)[rand @chars]; | |
# Crypt the password | |
return crypt($password, $salt); | |
} | |
# list users | |
sub listusers () { | |
# list users | |
foreach my $user (keys %db) { | |
print "$user\n"; | |
} | |
quit(); | |
} | |
# add user | |
sub adduser ($$) { | |
my $username = shift; | |
my $password = shift; | |
chomp($username = <STDIN>) if $username eq '-'; | |
chomp($password = <STDIN>) if $password eq '-'; | |
print "Adding user [$username]...\n"; | |
# Check if the user already exists | |
if($db{$username}) { | |
print "ERROR: User [$username] already exists\n"; | |
quit(); | |
} | |
# add the user to the database | |
# use `hashPass($password)` instead, if you wanted to generate | |
# crypted passwords. passwords would be limited to 8 characters. | |
$db{$username} = $password; | |
# create user's FTPS directory, set permissions and ownership at the same time | |
my $virtual = system("install -d -m 0775 /home/vftp/$username -o ftp -g ftp"); | |
if($virtual == 0) { | |
print "... successfully created VFTP directory.\n"; | |
} else { | |
print "ERROR: failed to create VFTP directory. Try again.\n"; | |
deluser($username); | |
quit(); | |
} | |
print "... successfully created virtual FTPS user.\n"; | |
# quietly add local user without password and no shell access | |
my $adduser = system("adduser --quiet --disabled-password --shell /bin/false --no-create-home --gecos \"User\" $username"); | |
if($adduser == 0) { | |
print "... successfully created local SFTP user.\n"; | |
} else { | |
print "ERROR: failed to create local SFTP user. Try again.\n"; | |
deluser($username); | |
quit(); | |
} | |
# set local user's password | |
my $chpasswd = system("echo \"$username:$password\" | chpasswd"); | |
if($chpasswd == 0) { | |
print "... successfully set password.\n"; | |
} else { | |
print "ERROR: failed to set password. Try again.\n"; | |
deluser($username); | |
quit(); | |
} | |
# add local user to SFTPONLY group | |
my $group = system("usermod -a -G sftponly $username"); | |
if($group == 0) { | |
print "... successfully set group permissions.\n"; | |
} else { | |
print "ERROR: failed to set group permissions. Try again.\n"; | |
deluser($username); | |
quit(); | |
} | |
# create user's SFTP directory, set permissions and ownership | |
my $home = system("install -d -m 0755 /home/sftp/$username -o $username -g sftponly"); | |
if($home == 0) { | |
print "... successfully created SFTP directory.\n"; | |
} else { | |
print "ERROR: failed to create SFTP directory. Try again.\n"; | |
deluser($username); | |
quit(); | |
} | |
print "SUCCESS: Added user [$username]\n"; | |
quit(); | |
} | |
# edit user | |
sub edituser ($$) { | |
my $username = shift; | |
my $password = shift; | |
chomp($username = <STDIN>) if $username eq '-'; | |
chomp($password = <STDIN>) if $password eq '-'; | |
# Check if the user exists | |
if (! $db{$username}) { | |
print "ERROR: User [$username] does not exist\n"; | |
quit(); | |
} | |
# update users password in the database | |
# use `hashPass($password)` instead, if you wanted to generate | |
# crypted passwords. passwords would be limited to 8 characters. | |
$db{$username} = $password; | |
# update local user's password | |
system("echo \"$username:$password\" | chpasswd"); | |
print "SUCCESS: Updated password for user [$username]\n"; | |
quit(); | |
} | |
# delete user | |
sub deluser ($) { | |
my $username = shift; | |
chomp($username = <STDIN>) if $username eq '-'; | |
# Check if the user exists | |
if (! $db{$username}) { | |
print "ERROR: User [$username] does not exist\n"; | |
quit(); | |
} | |
# Remove the user | |
delete $db{$username}; | |
# delete the local user | |
system("userdel $username"); | |
# delete the local virtual home directory | |
system("rm -R /home/vftp/$username"); | |
# delete the local user's home directory | |
system("rm -R /home/sftp/$username"); | |
print "SUCCESS: Removed user [$username]\n"; | |
quit(); | |
} | |
# main | |
if (@ARGV == 0) { usage(); } | |
elsif ($ARGV[0] eq 'list') { listusers(); } | |
elsif ($ARGV[0] eq 'add' && @ARGV == 3) { adduser($ARGV[1], $ARGV[2]); } | |
elsif ($ARGV[0] eq 'edit' && @ARGV == 3) { edituser($ARGV[1], $ARGV[2]); } | |
elsif ($ARGV[0] eq 'del' && @ARGV == 2) { deluser($ARGV[1]); } | |
else { usage(); } | |
quit(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment