package Passfile; # Based on # Crypt::PasswdMD5: Module to provide an interoperable crypt() # function for modern Unix O/S. This is based on the code for # # /usr/src/libcrypt/crypt.c # # on a FreeBSD 2.2.5-RELEASE system, which included the following # notice. # # ---------------------------------------------------------------------------- # "THE BEER-WARE LICENSE" (Revision 42): # wrote this file. As long as you retain this notice you # can do whatever you want with this stuff. If we meet some day, and you think # this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp # ---------------------------------------------------------------------------- # # Passfile Structure (Storable => Rabbit) # USER => {PASS => 'crypted', LEVEL => 'numeric or group'} # LEVEL also means GROUP # Rolf Rost, 26.4.2011 ########################################################################### use strict; use warnings; use Ostore; use Carp; use Digest::MD5; my $Magic = q/$1$/; # Magic string my $itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; sub new{ my $class = shift; my $file = shift or croak q(No File); tie(my %rabbit, 'Ostore::Manager', $file, auto => 0, lock => 0) or return; my $self = \%rabbit; bless $self, $class; return $self; } ##################### Crypt Methods ####################################### sub to64 { my ($v, $n) = @_; my $ret = ''; while (--$n >= 0) { $ret .= substr($itoa64, $v & 0x3f, 1); $v >>= 6; } $ret; } sub apache_md5_crypt { my $self = shift; # change the Magic string to match the one used by Apache $Magic = q/$apr1$/; $self->unix_md5_crypt(@_); } sub unix_md5_crypt { my $self = shift; my($pw, $salt) = @_; croak "No Password given" if not defined $pw; my $passwd = ''; if ( defined $salt ) { $salt =~ s/^\Q$Magic//; # Take care of the magic string if # if present. $salt =~ s/^(.*)\$.*$/$1/; # Salt can have up to 8 chars... $salt = substr($salt, 0, 8); } else { $salt = ''; # in case no salt was proffered $salt .= substr($itoa64,int(rand(64)+1),1) while length($salt) < 8; } my $ctx = new Digest::MD5; # Here we start the calculation $ctx->add($pw); # Original password... $ctx->add($Magic); # ...our magic string... $ctx->add($salt); # ...the salt... my ($final) = new Digest::MD5; $final->add($pw); $final->add($salt); $final->add($pw); $final = $final->digest; for (my $pl = length($pw); $pl > 0; $pl -= 16) { $ctx->add(substr($final, 0, $pl > 16 ? 16 : $pl)); } # Now the 'weird' xform for (my $i = length($pw); $i; $i >>= 1) { if ($i & 1) { $ctx->add(pack("C", 0)); } # This comes from the original version, # where a memset() is done to $final # before this loop. else { $ctx->add(substr($pw, 0, 1)); } } $final = $ctx->digest; # The following is supposed to make # things run slower. In perl, perhaps # it'll be *really* slow! for (my $i = 0; $i < 1000; $i++) { my $ctx1 = new Digest::MD5; if ($i & 1) { $ctx1->add($pw); } else { $ctx1->add(substr($final, 0, 16)); } if ($i % 3) { $ctx1->add($salt); } if ($i % 7) { $ctx1->add($pw); } if ($i & 1) { $ctx1->add(substr($final, 0, 16)); } else { $ctx1->add($pw); } $final = $ctx1->digest; } # Final xform $passwd = ''; $passwd .= to64(int(unpack("C", (substr($final, 0, 1))) << 16) | int(unpack("C", (substr($final, 6, 1))) << 8) | int(unpack("C", (substr($final, 12, 1)))), 4); $passwd .= to64(int(unpack("C", (substr($final, 1, 1))) << 16) | int(unpack("C", (substr($final, 7, 1))) << 8) | int(unpack("C", (substr($final, 13, 1)))), 4); $passwd .= to64(int(unpack("C", (substr($final, 2, 1))) << 16) | int(unpack("C", (substr($final, 8, 1))) << 8) | int(unpack("C", (substr($final, 14, 1)))), 4); $passwd .= to64(int(unpack("C", (substr($final, 3, 1))) << 16) | int(unpack("C", (substr($final, 9, 1))) << 8) | int(unpack("C", (substr($final, 15, 1)))), 4); $passwd .= to64(int(unpack("C", (substr($final, 4, 1))) << 16) | int(unpack("C", (substr($final, 10, 1))) << 8) | int(unpack("C", (substr($final, 5, 1)))), 4); $passwd .= to64(int(unpack("C", substr($final, 11, 1))), 2); $final = ''; return $Magic.$salt.q/$/.$passwd; } # get SALT from crypted password sub getsalt{ my $pass = shift; $pass =~ /^\$.*\$(.*)\$/; return $1; } ########################################################################### ################## Public Methods ######################################### sub useradd{ my $self = shift; my $user = shift or croak("No Username"); my $pass = shift or croak("No Password"); my $level = shift; croak("No Level") if not defined $level; if(exists $self->{$user}){ return "User already exists"; } else{ my $crypt = $self->apache_md5_crypt($pass); $self->{$user}->{PASS} = $crypt; $self->{$user}->{LEVEL} = $level; my $rabbit = tied %{$self}; $rabbit->write; return; } } sub userdel{ my $self = shift; my $user = shift or croak "No User"; my $ret = delete $self->{$user}; #my $rabbit = tied %{$self}; #$rabbit->store; return $ret ? $ret : ''; } # Update Password, Level for User sub userupdate{ my $self = shift; my $user = shift or croak("No Username"); my $pass = shift or croak("No Password"); my $level = shift; croak("No Level") if not defined $level; if(not exists $self->{$user}){ return "User not found"; } else{ my $crypt = $self->apache_md5_crypt($pass); $self->{$user}->{PASS} = $crypt; $self->{$user}->{LEVEL} = $level; my $rabbit = tied %{$self}; $rabbit->store; return; } } # Check a given user/pass # Return values: (error, user, level) sub usercheck{ my $self = shift; my $user = shift; my $pass = shift; if(not exists $self->{$user}){ return("User not found", $user, ''); } else{ my $salt = getsalt($self->{$user}->{PASS}); my $cmppass = $self->apache_md5_crypt($pass, $salt); if($cmppass eq $self->{$user}->{PASS}){ return('', $user, $self->{$user}->{LEVEL}); } else{ return('Wrong password', $user, ''); } } } sub showusers{ my $self = shift; my %hash = (); foreach my $k(keys %{$self}){ $hash{$k} = $self->{$k}->{LEVEL}; } return \%hash; } 1; ######################################################################## __END__ Methods ================================ error = useradd(username, password, level); error = userupdate(username, password, level); username = userdel(username); (error, username, level) = usercheck(username, password); hashref = showusers;