# vim: set cindent expandtab ts=4 sw=4:
#
# Copyright (c) 1998-2005 Chi-Keung Ho. All rights reserved.
#
# This programe is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# Extmail - a high-performance webmail to maildir
# $Id$
package Ext::Passwd;
use strict;
use Exporter;
use MIME::Base64;
use vars qw(@ISA @EXPORT @SCHEMES);
@ISA = qw(Exporter);
@SCHEMES = qw(CRYPT CLEARTEXT PLAIN MD5 MD5CRYPT PLAIN-MD5 LDAP-MD5 SHA SHA1);
@EXPORT = qw(@SCHEMES);
sub new {
my $this = shift;
my $self = bless {@_}, ref $this || $this;
$self->init(@_);
}
sub init {
my $self = shift;
my %opt = @_;
my $default = uc $opt{fallback_scheme};
die "$default not suuport!" unless grep {m/^$default$/} @SCHEMES;
$self->{_fallback_scheme} = $default;
$self;
}
sub get_passwd_scheme {
my $self = shift;
my $passwd = shift;
die "Password null or invalid!" unless $passwd;
# cleanup first
delete $self->{_passwd};
delete $self->{_scheme};
if (substr($passwd, 0, 3) eq '$1$') {
$self->{_passwd} = $passwd;
$self->{_scheme} = 'MD5';
return 'MD5';
} elsif (substr($passwd, 0, 1) eq '{') {
my $pos = index($passwd, '}');
my $scheme;
die "Password format invalid!" unless $pos>0;
$scheme = uc substr($passwd, 1, $pos-1);
$passwd = substr($passwd, $pos+1); # strip out {xx}
if (!grep {m/^$scheme$/} @SCHEMES) {
die "$scheme password not support!";
}
if ($scheme eq 'MD5') {
# to distinguish between md5crypt and ldap-md5
$scheme = 'LDAP-MD5';
} elsif ($scheme eq 'CRYPT') {
# to distinguish between crypt and md5crypt
if (substr($passwd, 0, 3) eq '$1$') {
$scheme = 'MD5';
} else {
$scheme = 'CRYPT';
}
}
$self->{_passwd} = $passwd;
$self->{_scheme} = $scheme;
return $scheme;
} else {
# fallback to default password scheme
$self->{_passwd} = $passwd;
$self->{_scheme} = $self->{_fallback_scheme};
return uc $self->{_fallback_scheme};
}
}
# the top api for encrypt a password, api:
# $self->encrypt($type, $password)
sub encrypt {
my $self = shift;
my $type = uc shift;
if ($type eq 'CRYPT') {
return encrypt_crypt($_[0]);
} elsif ($type eq 'CLEARTEXT') {
return encrypt_clear($_[0]);
} elsif ($type eq 'PLAIN') {
return encrypt_clear($_[0]);
} elsif ($type eq 'MD5') {
return encrypt_md5($_[0]);
} elsif ($type eq 'MD5CRYPT') {
return encrypt_md5($_[0]);
} elsif ($type eq 'PLAIN-MD5') {
return encrypt_plain_md5($_[0]);
} elsif ($type eq 'LDAP-MD5') {
return encrypt_ldap_md5($_[0]);
} elsif ($type eq 'SHA') {
return encrypt_sha($_[0]);
} elsif($type eq 'SHA1') {
return encrypt_sha($_[0]);
}
die "unsupport password type: $type";
}
# verify ($pass, $raw_pwd_data)
#
# $pass user input plain password
# $raw_pwd_data encrypted password in database
sub verify {
my $self = shift;
my $pass = shift;
my $raw_pwd_data = shift;
my $type = $self->get_passwd_scheme($raw_pwd_data);
my $passwd = $self->{_passwd}; # maby be same as
# $raw_pwd_data
if ($type eq 'CRYPT') {
return verify_crypt($pass, $passwd);
} elsif ($type eq 'CLEARTEXT') {
return verify_clear($pass, $passwd);
} elsif ($type eq 'PLAIN') {
return verify_clear($pass, $passwd);
} elsif ($type eq 'MD5') {
return verify_md5($pass, $passwd);
} elsif ($type eq 'MD5CRYPT') {
return verify_md5($pass, $passwd);
} elsif ($type eq 'PLAIN-MD5') {
return verify_plain_md5($pass, $passwd);
} elsif ($type eq 'LDAP-MD5') {
return verify_ldap_md5($pass, $passwd);
} elsif ($type eq 'SHA') {
return verify_sha($pass, $passwd);
} elsif ($type eq 'SHA1') {
return verify_sha($pass, $passwd);
}
die "unsupport password type: $type";
}
sub encrypt_crypt {
my $pwd = $_[0];
my $salt = join '', ('.', '/', 0..9, 'A'..'Z', 'a'..'z')[rand 64, rand 64];
return crypt($pwd, $salt);
}
sub verify_crypt {
return (crypt($_[0], $_[1]) eq $_[1] ? 1 : 0);
}
sub encrypt_clear {
return shift;
}
sub verify_clear {
return ($_[0] eq $_[1] ? 1 : 0);
}
sub encrypt_md5 {
eval { require Crypt::PasswdMD5 };
if ($@) {
return 'Crypt::PasswdMD5 not found!';
} else {
Crypt::PasswdMD5->import(qw(unix_md5_crypt));
return unix_md5_crypt(shift);
}
}
sub verify_md5 {
eval { require Crypt::PasswdMD5 };
if ($@) {
die 'Crypt::PasswdMD5 not found!';
} else {
# prepend $1$ if the raw passwd data missing it
if (substr($_[1], 0, 3) ne '$1$') {
$_[1] = '$1$'.$_[1];
}
Crypt::PasswdMD5->import(qw(unix_md5_crypt));
return (unix_md5_crypt($_[0], $_[1]) eq $_[1] ? 1 : 0);
}
}
sub encrypt_plain_md5 {
eval { require Digest::MD5 };
if ($@) {
return 'Digest::MD5 could not found!';
} else {
Digest::MD5->import(qw(md5_hex));
return md5_hex(shift);
}
}
sub verify_plain_md5 {
eval { require Digest::MD5 };
if ($@) {
die 'Digest::MD5 not found!';
} else {
Digest::MD5->import(qw(md5_hex));
return (md5_hex($_[0]) eq $_[1] ? 1 : 0);
}
}
sub encrypt_ldap_md5 {
eval { require Digest::MD5 };
if ($@) {
return 'Digest::MD5 could not found!';
} else {
Digest::MD5->import(qw(md5));
return '{MD5}'.mybase64_encode(md5(shift));
}
}
sub verify_ldap_md5 {
eval { require Digest::MD5 };
if ($@) {
die 'Digest::MD5 not found!';
} else {
Digest::MD5->import(qw(md5));
return (mybase64_encode(md5($_[0])) eq $_[1] ? 1 : 0 );
}
}
sub encrypt_sha {
eval { require Digest::SHA1 };
if ($@) {
return 'Digest::SHA1 could not found!';
} else {
Digest::SHA1->import(qw(sha1_base64));
# bug fix, add redundant '=' to compatible with base64 standard
return '{SHA}'.sha1_base64(shift).'=';
}
}
sub verify_sha {
eval { require Digest::SHA1 };
if ($@) {
die 'Digest::SHA1 not found1';
} else {
Digest::SHA1->import(qw(sha1_base64));
return (sha1_base64($_[0]).'=' eq $_[1] ? 1 : 0);
}
}
sub mybase64_encode {
my $str = shift;
$str = encode_base64($str);
chomp $str;
return $str;
}
sub DESTORY {
}
1;
__END__
Authentication and password scheme defination. reference from Dovecot-auth
, Courier-authlib and LDAP RFC2307.
Ext::Passwd currently support the following password scheme mapping:
CRYPT => crypt
MD5 => md5
PLAIN-MD5 => plain_md5
LDAP-MD5 => ldap_md5
SHA => sha
SHA1 => sha
CLEARTEXT => clear
PLAIN => clear
The way to identify password scheme:
$1$hhhhhh$xxxxxxxxxxx => md5 crypted, hhh is hash, xxxx is raw data
{xxxx}yyyyyyyyyyyyyyy => xxxx is scheme, yyy is data (base64 encoded)
xxxxxxxxxxxxxxxxxxxxx => no scheme, raw data, need to specify type!
syntax highlighted by Code2HTML, v. 0.9.1