Skip to content

Instantly share code, notes, and snippets.

@skarllot
Created March 15, 2012 18:54
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save skarllot/2046012 to your computer and use it in GitHub Desktop.
Save skarllot/2046012 to your computer and use it in GitHub Desktop.
Script to manage database file for PAM authentication (vsftpd)
#!/usr/bin/perl -w
#
# pam_userdb_admin.pl - pam_userdb(8) authentication database management tool
#
# Copyright (c) 2011 EPIPE Communications <http://epipe.com/>
#
# Permission to use, copy, modify, and/or 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.
#
# 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. 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.
#
#
# DESCRIPTION
# ===========
#
# This script may be used to maintain a user database file for PAM module
# mod_userdb(8). It can be convenient for example for maintaining vsftpd
# virtual users database.
#
# The $usrtxtfn configuration variable below defines a text format file
# which is used as a master copy of the database. It has the following
# format:
#
# USERNAME:PASSWORD
#
# The PASSWORD can be in plain text or hashed with crypt(3) depending on
# your mod_userdb(8) arguments in your PAM configuration file. Please set
# the $hashpwd configuration variable below accordingly.
#
# The $usrdbfn variable below defines the authentication database which is
# actually used by pam_userdb(8). This script always completely overwrites
# the database file based on the text file contents.
#
#
# USAGE
# =====
#
# Basic usage is as follows:
#
# Create the database from text file:
#
# pam_userdb_admin update
#
# Add user "foo" with password "kissa" to the end of the text file and
# update the database:
#
# pam_userdb_admin adduser foo kissa
#
# Add user "test", read password from standard input:
#
# pam_userdb_admin adduser test -
#
# Add user, read username and password from standard input (on separate lines):
#
# pam_userdb_admin adduser - -
#
# Dump the current database contents:
#
# pam_userdb_admin dump
#
# Delete user:
# vi /etc/vsftpd_login
# pam_userdb_admin update
#
# Usage help:
#
# pam_userdb_admin help
#
use strict;
#
# CONFIGURATION SETTINGS
# ======================
#
# The name of the text format user file which contains the entries which are
# converted to the machine readable database file:
my $usrtxtfn = '/etc/vsftpd_login';
#
# The name of the user database file which is used by pam_userdb(8) for
# authentication:
my $usrdbfn = '/etc/vsftpd_login.db';
#
# The permissions (in octal) of the user database file (umask is also applied):
my $usrdbmode = 0660;
#
# Are hashed or plaintext passwords in use? (1 = hashed, 0 = plaintext)
my $hashpwd = 1;
#
# End of configuration settings.
#
use DB_File;
# The following function is used to create or update the database file from
# the text file:
sub updatedb () {
# open the input file
open(my $txtfh, '<', $usrtxtfn)
or die "$usrtxtfn: $!";
# create a temporary user database file with the following name
my $usrdbtmpfn = $usrdbfn . ".$$.tmp";
my %usrdb = ();
# create the temporary database
tie %usrdb, 'DB_File', $usrdbtmpfn, O_RDWR|O_CREAT, $usrdbmode, $DB_HASH
or die "$usrdbtmpfn: $1";
while (<$txtfh>) {
chomp;
next if /^#/;
next if /^\s*$/;
my ($user, $pass) = split /:/;
if (!defined($user) || $user eq '' || !defined($pass)) {
print STDERR "$usrtxtfn:$. invalid line ignored\n";
next;
}
if (defined($usrdb{$user})) {
print STDERR "$usrtxtfn:$. duplicate user $user"
. " ignored\n";
next;
}
$usrdb{$user} = $pass;
}
untie %usrdb;
undef $txtfh;
# move the new user database in place, hopefully atomically
rename($usrdbtmpfn, $usrdbfn)
or die "rename($usrdbtmpfn, $usrdbfn): $!";
}
# generate salt
sub gensalt ($) {
my $count = shift;
my @saltchars = ( '.', '/', 0 .. 9, 'A' .. 'Z', 'a' .. 'z' );
my $salt;
for (1..$count) {
$salt .= (@saltchars)[rand @saltchars];
}
return $salt;
}
# add user
sub adduser ($$) {
my $username = shift;
my $password = shift;
chomp($username = <STDIN>) if $username eq '-';
chomp($password = <STDIN>) if $password eq '-';
if ($hashpwd) {
# The following uses MD5 hashed password with 8 character salt
# (which is the default on most Linux distros and FreeBSD:
#my $salt = '$1$' . gensalt(8) . '$';
# Unfortunately that does not work with mod_userdb, so we use
# traditional format
my $salt = gensalt(2);
$password = crypt($password, $salt);
}
open(my $fh, '>>', $usrtxtfn)
or die "$usrtxtfn: $!";
print $fh "$username:$password\n";
undef $fh;
}
# dump the db to STDOUT
sub dumpdb () {
my %usrdb = ();
# attach to the database
tie %usrdb, 'DB_File', $usrdbfn, O_RDONLY
or die "$usrdbfn: $!";
while (my ($k, $v) = each %usrdb) {
print "$k:$v\n";
}
untie %usrdb;
}
# usage help
sub usage () {
print "usage: $0 command [opts]\n";
print "\n";
print "command is one of:\n";
print "\tupdate\n\t\tre-creates the database from text file\n";
print "\tdump\n\t\tdump the current database to standard output\n";
print "\tadduser USERNAME PASSWORD\n";
print "\t\tadd user, crypt the password if needed and update the db\n";
print "\t\tif USERNAME and/or PASSWORD is \"-\" it is read from STDIN\n";
print "\n";
exit(2);
}
# main()
if (@ARGV == 0) {
usage();
}
if ($ARGV[0] eq 'update' && @ARGV == 1) {
updatedb();
} elsif ($ARGV[0] eq 'adduser' && @ARGV == 3) {
adduser($ARGV[1], $ARGV[2]);
updatedb();
} elsif ($ARGV[0] eq 'dump' && @ARGV == 1) {
dumpdb();
} else {
usage();
}
exit(0);
# eof
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment