#!/usr/bin/perl -w # # pam_userdb_admin.pl - pam_userdb(8) authentication database management tool # # Copyright (c) 2011 EPIPE Communications # # 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 = ) if $username eq '-'; chomp($password = ) 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