Skip to content

Instantly share code, notes, and snippets.

@phuze
Last active February 21, 2022 21:08
Show Gist options
  • Save phuze/8f66a26767500398d271b42013672536 to your computer and use it in GitHub Desktop.
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
#!/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