From: mark Date: Thu, 12 Nov 2009 21:45:07 +0000 (+0000) Subject: Add default password encoding option X-Git-Tag: root_of_svc_elec_features~685 X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=commitdiff_plain;h=121a0e466d425648801b687a474acb985090d1c6 Add default password encoding option --- diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 582313571..84f06590d 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -1144,6 +1144,23 @@ worry that config_items is freeside-specific and icky. }, { + 'key' => 'default-password-encoding', + 'section' => 'password', + 'description' => 'Default storage format for passwords', + 'type' => 'select', + 'select_hash' => [ + 'plain' => 'Plain text', + 'crypt-des' => 'Unix password (DES encrypted)', + 'crypt-md5' => 'Unix password (MD5 digest)', + 'ldap-plain' => 'LDAP (plain text)', + 'ldap-crypt' => 'LDAP (DES encrypted)', + 'ldap-md5' => 'LDAP (MD5 digest)', + 'ldap-sha1' => 'LDAP (SHA1 digest)', + 'legacy' => 'Legacy mode', + ], + }, + + { 'key' => 'referraldefault', 'section' => 'UI', 'description' => 'Default referral, specified by refnum', diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index 32dba2560..3af41bac6 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -20,6 +20,8 @@ use Carp; use Fcntl qw(:flock); use Date::Format; use Crypt::PasswdMD5 1.2; +use Digest::SHA1 'sha1_base64'; +use Digest::MD5 'md5_base64'; use Data::Dumper; use Text::Template; use Authen::Passphrase; @@ -1179,6 +1181,17 @@ sub check { $self->ut_textn($_); } + # First, if _password is blank, generate one and set default encoding. + if ( ! $recref->{_password} ) { + $self->set_password(''); + } + # But if there's a _password but no encoding, assume it's plaintext and + # set it to default encoding. + elsif ( ! $recref->{_password_encoding} ) { + $self->set_password($recref->{_password}); + } + + # Next, check _password to ensure compliance with the encoding. if ( $recref->{_password_encoding} eq 'ldap' ) { if ( $recref->{_password} =~ /^(\{[\w\-]+\})(!?.{0,64})$/ ) { @@ -1201,11 +1214,8 @@ sub check { } } elsif ( $recref->{_password_encoding} eq 'plain' ) { - - #generate a password if it is blank - $recref->{_password} = join('',map($pw_set[ int(rand $#pw_set) ], (0..7) ) ) - unless length( $recref->{_password} ); - + # Password randomization is now in set_password. + # Strip whitespace characters, check length requirements, etc. if ( $recref->{_password} =~ /^([^\t\n]{$passwordmin,$passwordmax})$/ ) { $recref->{_password} = $1; } else { @@ -1220,51 +1230,148 @@ sub check { if ( $password_noexclamation ) { $recref->{_password} =~ /\!/ and return gettext('illegal_password'); } + } + elsif ( $recref->{_password_encoding} eq 'legacy' ) { + # this happens when set_password fails + return gettext('illegal_password'). " $passwordmin-$passwordmax ". + FS::Msgcat::_gettext('illegal_password_characters'). + ": ". $recref->{_password}; + } + $self->SUPER::check; - } else { +} - #carp "warning: _password_encoding unspecified\n"; - #generate a password if it is blank - unless ( length($recref->{_password}) || ! $passwordmin ) { +sub _password_encryption { + my $self = shift; + my $encoding = lc($self->_password_encoding); + return if !$encoding; + return 'plain' if $encoding eq 'plain'; + if($encoding eq 'crypt') { + my $pass = $self->_password; + $pass =~ s/^\*SUSPENDED\* //; + $pass =~ s/^!!?//; + return 'md5' if $pass =~ /^\$1\$/; + #return 'blowfish' if $self->_password =~ /^\$2\$/; + return 'des' if length($pass) == 13; + return; + } + if($encoding eq 'ldap') { + uc($self->_password) =~ /^\{([\w-]+)\}/; + return 'crypt' if $1 eq 'CRYPT' or $1 eq 'DES'; + return 'plain' if $1 eq 'PLAIN' or $1 eq 'CLEARTEXT'; + return 'md5' if $1 eq 'MD5'; + return 'sha1' if $1 eq 'SHA' or $1 eq 'SHA-1'; + + return; + } + return; +} + +sub get_cleartext_password { + my $self = shift; + if($self->_password_encryption eq 'plain') { + if($self->_password_encoding eq 'ldap') { + $self->_password =~ /\{\w+\}(.*)$/; + return $1; + } + else { + return $self->_password; + } + } + return; +} - $recref->{_password} = - join('',map($pw_set[ int(rand $#pw_set) ], (0..7) ) ); - $recref->{_password_encoding} = 'plain'; + +=item set_password - } else { - - #if ( $recref->{_password} =~ /^((\*SUSPENDED\* )?)([^\t\n]{4,16})$/ ) { - if ( $recref->{_password} =~ /^((\*SUSPENDED\* |!!?)?)([^\t\n]{$passwordmin,$passwordmax})$/ ) { - $recref->{_password} = $1.$3; - $recref->{_password_encoding} = 'plain'; - } elsif ( $recref->{_password} =~ - /^((\*SUSPENDED\* |!!?)?)([\w\.\/\$\;\+]{13,64})$/ - ) { - $recref->{_password} = $1.$3; - $recref->{_password_encoding} = 'crypt'; - } elsif ( $recref->{_password} eq '*' ) { - $recref->{_password} = '*'; - $recref->{_password_encoding} = 'crypt'; - } elsif ( $recref->{_password} eq '!' ) { - $recref->{_password_encoding} = 'crypt'; - $recref->{_password} = '!'; - } elsif ( $recref->{_password} eq '!!' ) { - $recref->{_password} = '!!'; - $recref->{_password_encoding} = 'crypt'; - } else { - #return "Illegal password"; - return gettext('illegal_password'). " $passwordmin-$passwordmax ". - FS::Msgcat::_gettext('illegal_password_characters'). - ": ". $recref->{_password}; - } +Set the cleartext password for the account. If _password_encoding is set, the +new password will be encoded according to the existing method (including +encryption mode, if it can be determined). Otherwise, +config('default-password-encoding') is used. + +If no password is supplied (or a zero-length password when minimum password length +is >0), one will be generated randomly. +=cut + +sub set_password { + my $self = shift; + my $pass = shift; + my ($encoding, $encryption); + + + if($self->_password_encoding) { + $encoding = $self->_password_encoding; + # identify existing encryption method, try to use it. + $encryption = $self->_password_encryption; + if(!$encryption) { + # use the system default + undef $encoding; } + } + if(!$encoding) { + # set encoding to system default + ($encoding, $encryption) = split(/-/, lc($conf->config('default-password-encoding'))); + $encoding ||= 'legacy'; + $self->_password_encoding($encoding); } - $self->SUPER::check; + if($encoding eq 'legacy') { + # The legacy behavior from check(): + # If the password is blank, randomize it and set encoding to 'plain'. + if(!defined($pass) or (length($pass) == 0 and $passwordmin)) { + $pass = join('',map($pw_set[ int(rand $#pw_set) ], (0..7) ) ); + $self->_password_encoding('plain'); + } + else { + # Prefix + valid-length password + if ( $pass =~ /^((\*SUSPENDED\* |!!?)?)([^\t\n]{$passwordmin,$passwordmax})$/ ) { + $pass = $1.$3; + $self->_password_encoding('plain'); + } + # Prefix + crypt string + elsif ( $pass =~ /^((\*SUSPENDED\* |!!?)?)([\w\.\/\$\;\+]{13,64})$/ ) { + $pass = $1.$3; + $self->_password_encoding('crypt'); + } + # Various disabled crypt passwords + elsif ( $pass eq '*' or + $pass eq '!' or + $pass eq '!!' ) { + $self->_password_encoding('crypt'); + } + else { + # do nothing; check() will recognize this as an error + } + } + } + elsif($encoding eq 'crypt') { + if($encryption eq 'md5') { + $pass = unix_md5_crypt($pass); + } + elsif($encryption eq 'des') { + $pass = crypt($pass, $saltset[int(rand(64))].$saltset[int(rand(64))]); + } + } + elsif($encoding eq 'ldap') { + if($encryption eq 'md5') { + $pass = md5_base64($pass); + } + elsif($encryption eq 'sha1') { + $pass = sha1_base64($pass); + } + elsif($encryption eq 'crypt') { + $pass = crypt($pass, $saltset[int(rand(64))].$saltset[int(rand(64))]); + } + # else $encryption eq 'plain', do nothing + $pass = '{'.uc($encryption).'}'.$pass; + } + # else encoding eq 'plain' + $self->_password($pass); + return; } =item _check_system diff --git a/httemplate/edit/process/svc_acct.cgi b/httemplate/edit/process/svc_acct.cgi index 0a89e253c..c19c2a51f 100755 --- a/httemplate/edit/process/svc_acct.cgi +++ b/httemplate/edit/process/svc_acct.cgi @@ -5,7 +5,7 @@ <% $cgi->redirect(popurl(3). "view/svc_acct.cgi?" . $svcnum ) %> %} <%init> - +use CGI::Carp; die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific? @@ -23,12 +23,6 @@ if ( $svcnum ) { #unmunge popnum $cgi->param('popnum', (split(/:/, $cgi->param('popnum') ))[0] ); -#unmunge passwd -if ( $cgi->param('_password') eq '*HIDDEN*' ) { - die "fatal: no previous account to recall hidden password from!" unless $old; - $cgi->param('_password',$old->getfield('_password')); -} - #unmunge usergroup $cgi->param('usergroup', [ $cgi->param('radius_usergroup') ] ); @@ -45,6 +39,15 @@ map { } (fields('svc_acct'), qw ( pkgnum svcpart usergroup )); my $new = new FS::svc_acct ( \%hash ); +$new->_password($old->_password) if $old; +if( $cgi->param('clear_password') eq '*HIDDEN*' + or $cgi->param('clear_password') =~ /^\(.* encrypted\)$/ ) { + die "fatal: no previous account to recall hidden password from!" unless $old; +} +else { + $new->set_password($cgi->param('clear_password')); +} + my $error; if ( $svcnum ) { foreach (grep { $old->$_ != $new->$_ } qw( seconds upbytes downbytes totalbytes )) { diff --git a/httemplate/edit/svc_acct.cgi b/httemplate/edit/svc_acct.cgi index b9a587d2a..9c3e8de03 100755 --- a/httemplate/edit/svc_acct.cgi +++ b/httemplate/edit/svc_acct.cgi @@ -9,6 +9,18 @@
% } +
@@ -35,13 +47,14 @@ Service # <% $svcnum ? "$svcnum" : " (NEW)" %>
Password - MAXLENGTH=<% $pmax %>> - (blank to generate) + MAXLENGTH=<% $pmax %>> + %}else{ - + %} + % %my $sec_phrase = $svc_acct->sec_phrase; %if ( $conf->exists('security_phrase') @@ -428,14 +441,21 @@ my $otaker = getotaker; my $username = $svc_acct->username; my $password; -if ( $svc_acct->_password ) { - if ( $conf->exists('showpasswords') || ! $svcnum ) { - $password = $svc_acct->_password; - } else { - $password = "*HIDDEN*"; +my $password_encryption = $svc_acct->_password_encryption; +my $password_encoding = $svc_acct->_password_encoding; + +if($svcnum) { + if($password = $svc_acct->get_cleartext_password) { + if (! $conf->exists('showpasswords')) { + $password = '*HIDDEN*'; + } + } + elsif($svc_acct->_password and $password_encryption ne 'plain') { + $password = "(".uc($password_encryption)." encrypted)"; + } + else { + $password = ''; } -} else { - $password = ''; } my $ulen = @@ -444,9 +464,13 @@ my $ulen = : dbdef->table('svc_acct')->column('username')->length; my $ulen2 = $ulen+2; -my $pmax = $conf->config('passwordmax') || 8; +my $pmax = max($conf->config('passwordmax') || 13); my $pmax2 = $pmax+2; my $p1 = popurl(1); +sub max { + (sort(@_))[-1] +} + diff --git a/httemplate/view/svc_acct.cgi b/httemplate/view/svc_acct.cgi index 6a47ec767..44a2aa611 100755 --- a/httemplate/view/svc_acct.cgi +++ b/httemplate/view/svc_acct.cgi @@ -160,14 +160,19 @@ Service #<% $svcnum %> Password -% my $password = $svc_acct->_password; +% my $password = $svc_acct->get_cleartext_password; % if ( $password =~ /^\*\w+\* (.*)$/ ) { % $password = $1; % (login disabled) % } -% if ( $conf->exists('showpasswords') ) { +% if ( !$password and +% $svc_acct->_password_encryption ne 'plain' and +% $svc_acct->_password ) { + (<% uc($svc_acct->_password_encryption) %> encrypted) +% } +% elsif ( $conf->exists('showpasswords') ) {
<% encode_entities($password) %>
% } else {