]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Fix for bug 77473, bug 74032, and bug 85472: Passwords are no longer stored in plaint...
authorjustdave%syndicomm.com <>
Wed, 11 Jul 2001 12:29:16 +0000 (12:29 +0000)
committerjustdave%syndicomm.com <>
Wed, 11 Jul 2001 12:29:16 +0000 (12:29 +0000)
Patch by Myk Melez <myk@mozilla.org>
r= justdave@syndicomm.com, jake@acutex.net

Bugzilla/Token.pm [new file with mode: 0644]
CGI.pl
Token.pm [new file with mode: 0644]
checksetup.pl
createaccount.cgi
editusers.cgi
globals.pl
sanitycheck.cgi
token.cgi [new file with mode: 0755]
userprefs.cgi

diff --git a/Bugzilla/Token.pm b/Bugzilla/Token.pm
new file mode 100644 (file)
index 0000000..cde97f8
--- /dev/null
@@ -0,0 +1,184 @@
+#!/usr/bonsaitools/bin/perl -w
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s):    Myk Melez <myk@mozilla.org>
+
+################################################################################
+# Module Initialization
+################################################################################
+
+# Make it harder for us to do dangerous things in Perl.
+use diagnostics;
+use strict;
+
+# Bundle the functions in this file together into the "Token" package.
+package Token;
+
+# This module requires that its caller have said "require CGI.pl" to import
+# relevant functions from that script and its companion globals.pl.
+
+################################################################################
+# Functions
+################################################################################
+
+sub IssuePasswordToken {
+    # Generates a random token, adds it to the tokens table, and sends it
+    # to the user with instructions for using it to change their password.
+
+    my ($loginname) = @_;
+
+    # Retrieve the user's ID from the database.
+    my $quotedloginname = &::SqlQuote($loginname);
+    &::SendSQL("SELECT userid FROM profiles WHERE login_name = $quotedloginname");
+    my ($userid) = &::FetchSQLData();
+
+    # Generate a unique token and insert it into the tokens table.
+    # We have to lock the tokens table before generating the token, 
+    # since the database must be queried for token uniqueness.
+    &::SendSQL("LOCK TABLE tokens WRITE");
+    my $token = GenerateUniqueToken();
+    my $quotedtoken = &::SqlQuote($token);
+    my $quotedipaddr = &::SqlQuote($::ENV{'REMOTE_ADDR'});
+    &::SendSQL("INSERT INTO tokens ( userid , issuedate , token , tokentype , eventdata )
+                VALUES      ( $userid , NOW() , $quotedtoken , 'password' , $quotedipaddr )");
+    &::SendSQL("UNLOCK TABLES");
+
+    # Mail the user the token along with instructions for using it.
+    MailPasswordToken($loginname, $token);
+
+}
+
+
+sub GenerateUniqueToken {
+    # Generates a unique random token.  Uses &GenerateRandomPassword 
+    # for the tokens themselves and checks uniqueness by searching for
+    # the token in the "tokens" table.  Gives up if it can't come up
+    # with a token after about one hundred tries.
+
+    my $token;
+    my $duplicate = 1;
+    my $tries = 0;
+    while ($duplicate) {
+
+        ++$tries;
+        if ($tries > 100) {
+            &::DisplayError("Something is seriously wrong with the token generation system.");
+            exit;
+        }
+
+        $token = &::GenerateRandomPassword();
+        &::SendSQL("SELECT userid FROM tokens WHERE token = " . &::SqlQuote($token));
+        $duplicate = &::FetchSQLData();
+    }
+
+    return $token;
+
+}
+
+sub MailPasswordToken {
+    # Emails a password token to a user along with instructions for its use.
+    # Called exclusively from &IssuePasswordToken.
+
+    my ($emailaddress, $token) = @_;
+
+    my $urlbase = &::Param("urlbase");
+    my $emailsuffix = &::Param('emailsuffix');
+
+    open SENDMAIL, "|/usr/lib/sendmail -t";
+
+    print SENDMAIL qq|From: bugzilla-daemon
+To: $emailaddress$emailsuffix
+Subject: Bugzilla Change Password Request
+
+You or someone impersonating you has requested to change your Bugzilla
+password.  To change your password, visit the following link:
+
+${urlbase}token.cgi?a=cfmpw&t=$token
+
+If you are not the person who made this request, or you wish to cancel
+this request, visit the following link:
+
+${urlbase}token.cgi?a=cxlpw&t=$token
+|;
+    close SENDMAIL;
+}
+
+sub Cancel {
+    # Cancels a previously issued token and notifies the system administrator.
+    # This should only happen when the user accidentally makes a token request
+    # or when a malicious hacker makes a token request on behalf of a user.
+    
+    my ($token, $cancelaction) = @_;
+
+    # Quote the token for inclusion in SQL statements.
+    my $quotedtoken = &::SqlQuote($token);
+    
+    # Get information about the token being cancelled.
+    &::SendSQL("SELECT  issuedate , tokentype , eventdata , login_name , realname
+                FROM    tokens, profiles 
+                WHERE   tokens.userid = profiles.userid
+                AND     token = $quotedtoken");
+    my ($issuedate, $tokentype, $eventdata, $loginname, $realname) = &::FetchSQLData();
+
+    # Get the email address of the Bugzilla maintainer.
+    my $maintainer = &::Param('maintainer');
+
+    # Format the user's real name and email address into a single string.
+    my $username = $realname ? $realname . " <" . $loginname . ">" : $loginname;
+
+    # Notify the user via email about the cancellation.
+    open SENDMAIL, "|/usr/lib/sendmail -t";
+    print SENDMAIL qq|From: bugzilla-daemon
+To: $username
+Subject: "$tokentype" token cancelled
+
+A token was cancelled from $::ENV{'REMOTE_ADDR'}.  This is either 
+an honest mistake or the result of a malicious hack attempt.  
+Take a look at the information below and forward this email 
+to $maintainer if you suspect foul play.
+
+            Token: $token
+       Token Type: $tokentype
+             User: $username
+       Issue Date: $issuedate
+       Event Data: $eventdata
+
+Cancelled Because: $cancelaction
+|;
+    close SENDMAIL;
+
+    # Delete the token from the database.
+    &::SendSQL("LOCK TABLE tokens WRITE");
+    &::SendSQL("DELETE FROM tokens WHERE token = $quotedtoken");
+    &::SendSQL("UNLOCK TABLES");
+}
+
+sub HasPasswordToken {
+    # Returns a password token if the user has one.  Otherwise returns 0 (false).
+    
+    my ($userid) = @_;
+    
+    &::SendSQL("SELECT token FROM tokens WHERE userid = $userid LIMIT 1");
+    my ($token) = &::FetchSQLData();
+    
+    return $token;
+}
+
+1;
diff --git a/CGI.pl b/CGI.pl
index d7782f7e845bf15db53d2a2b258b87e46005d892..ede69a9ad9de5886243ab8932c16f3190a3b0703 100644 (file)
--- a/CGI.pl
+++ b/CGI.pl
@@ -713,43 +713,54 @@ sub confirm_login {
     # to a later section.  -Joe Robins, 8/3/00
     my $enteredlogin = "";
     my $realcryptpwd = "";
-    if (defined $::FORM{"Bugzilla_login"} &&
-       defined $::FORM{"Bugzilla_password"}) {
-
-       $enteredlogin = $::FORM{"Bugzilla_login"};
-       my $enteredpwd = $::FORM{"Bugzilla_password"};
-       CheckEmailSyntax($enteredlogin);
-
-       $realcryptpwd  = PasswordForLogin($::FORM{"Bugzilla_login"});
-
-       if (defined $::FORM{"PleaseMailAPassword"}) {
-         my $realpwd;
-         if ($realcryptpwd eq "") {
-           $realpwd = InsertNewUser($enteredlogin, "");
-         } else {
-           SendSQL("select password from profiles where login_name = " .
-                   SqlQuote($enteredlogin));
-           $realpwd = FetchOneColumn();
-         }
-         print "Content-type: text/html\n\n";
-         PutHeader("Password has been emailed");
-         MailPassword($enteredlogin, $realpwd);
-         PutFooter();
-         exit;
-       }
 
-       SendSQL("SELECT encrypt(" . SqlQuote($enteredpwd) . ", " .
-               SqlQuote(substr($realcryptpwd, 0, 2)) . ")");
-       my $enteredcryptpwd = FetchOneColumn();
+    # If the form contains Bugzilla login and password fields, use Bugzilla's 
+    # built-in authentication to authenticate the user (otherwise use LDAP below).
+    if (defined $::FORM{"Bugzilla_login"} && defined $::FORM{"Bugzilla_password"}) {
+        # Make sure the user's login name is a valid email address.
+        $enteredlogin = $::FORM{"Bugzilla_login"};
+        CheckEmailSyntax($enteredlogin);
+
+        # Retrieve the user's ID and crypted password from the database.
+        my $userid;
+        SendSQL("SELECT userid, cryptpassword FROM profiles 
+                 WHERE login_name = " . SqlQuote($enteredlogin));
+        ($userid, $realcryptpwd) = FetchSQLData();
+
+        # If this is a new user, generate a password, insert a record
+        # into the database, and email their password to them.
+        if ( defined $::FORM{"PleaseMailAPassword"} && !$userid ) {
+            my $password = InsertNewUser($enteredlogin, "");
+            print "Content-Type: text/html\n\n";
+            PutHeader("Account Created");
+            MailPassword($enteredlogin, $password);
+            PutFooter();
+            exit;
+        }
+
+        # Otherwise, authenticate the user.
+        else {
+            # Get the salt from the user's crypted password.
+            my $salt = $realcryptpwd;
+
+            # Using the salt, crypt the password the user entered.
+            my $enteredCryptedPassword = crypt( $::FORM{"Bugzilla_password"} , $salt );
+
+            # Make sure the passwords match or throw an error.
+            ($enteredCryptedPassword eq $realcryptpwd)
+              || DisplayError("The username or password you entered is not valid.")
+              && exit;
+
+            # If the user has successfully logged in, delete any password tokens
+            # lying around in the system for them.
+            use Token;
+            my $token = Token::HasPasswordToken($userid);
+            while ( $token ) {
+                Token::Cancel($token, "user logged in");
+                $token = Token::HasPasswordToken($userid);
+            }
+        }
 
-       if ($realcryptpwd eq "" || $enteredcryptpwd ne $realcryptpwd) {
-         print "Content-type: text/html\n\n";
-         PutHeader("Login failed");
-         print "The username or password you entered is not valid.\n";
-         print "Please click <b>Back</b> and try again.\n";
-         PutFooter();
-         exit;
-       }
      } elsif (Param("useLDAP") &&
               defined $::FORM{"LDAP_login"} &&
               defined $::FORM{"LDAP_password"}) {
@@ -952,23 +963,32 @@ Content-type: text/html
 </tr>
 </table>
 ";
-        foreach my $i (keys %::FORM) {
-            if ($i =~ /^Bugzilla_/) {
-                next;
-            }
-            print "<input type=hidden name=$i value=\"@{[value_quote($::FORM{$i})]}\">\n";
+        # Add all the form fields into the form as hidden fields
+        # (except for Bugzilla_login and Bugzilla_password which we
+        # already added as text fields above).
+        foreach my $i ( grep( $_ !~ /^Bugzilla_/ , keys %::FORM ) ) {
+            print qq|<input type="hidden" name="$i" value="@{[value_quote($::FORM{$i})]}">\n|;
         }
-        print "
-<input type=submit value=Login name=GoAheadAndLogIn><hr>
-";
-        # If we're using LDAP, we can't request that a password be mailed...
-        unless(Param("useLDAP")) {
-          print "
-If you don't have a password, or have forgotten it, then please fill in the
-e-mail address above and click
- here:<input type=submit value=\"E-mail me a password\"
-name=PleaseMailAPassword>
-</form>\n";
+
+        print qq|
+          <input type="submit" name="GoAheadAndLogIn" value="Login">
+          </form>
+        |;
+
+        # Allow the user to request a token to change their password (unless
+        # we are using LDAP, in which case the user must use LDAP to change it).
+        unless( Param("useLDAP") ) {
+            print qq|
+              <hr>
+              <form method="get" action="token.cgi">
+                <input type="hidden" name="a" value="reqpw">
+                If you don't have a password or have forgotten it,
+                enter your login name below and submit a request 
+                to change your password.<br>
+                <input size="35" name="loginname">
+                <input type="submit" value="Submit Request">
+              </form>
+            |;
         }
 
         # This seems like as good as time as any to get rid of old
diff --git a/Token.pm b/Token.pm
new file mode 100644 (file)
index 0000000..cde97f8
--- /dev/null
+++ b/Token.pm
@@ -0,0 +1,184 @@
+#!/usr/bonsaitools/bin/perl -w
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s):    Myk Melez <myk@mozilla.org>
+
+################################################################################
+# Module Initialization
+################################################################################
+
+# Make it harder for us to do dangerous things in Perl.
+use diagnostics;
+use strict;
+
+# Bundle the functions in this file together into the "Token" package.
+package Token;
+
+# This module requires that its caller have said "require CGI.pl" to import
+# relevant functions from that script and its companion globals.pl.
+
+################################################################################
+# Functions
+################################################################################
+
+sub IssuePasswordToken {
+    # Generates a random token, adds it to the tokens table, and sends it
+    # to the user with instructions for using it to change their password.
+
+    my ($loginname) = @_;
+
+    # Retrieve the user's ID from the database.
+    my $quotedloginname = &::SqlQuote($loginname);
+    &::SendSQL("SELECT userid FROM profiles WHERE login_name = $quotedloginname");
+    my ($userid) = &::FetchSQLData();
+
+    # Generate a unique token and insert it into the tokens table.
+    # We have to lock the tokens table before generating the token, 
+    # since the database must be queried for token uniqueness.
+    &::SendSQL("LOCK TABLE tokens WRITE");
+    my $token = GenerateUniqueToken();
+    my $quotedtoken = &::SqlQuote($token);
+    my $quotedipaddr = &::SqlQuote($::ENV{'REMOTE_ADDR'});
+    &::SendSQL("INSERT INTO tokens ( userid , issuedate , token , tokentype , eventdata )
+                VALUES      ( $userid , NOW() , $quotedtoken , 'password' , $quotedipaddr )");
+    &::SendSQL("UNLOCK TABLES");
+
+    # Mail the user the token along with instructions for using it.
+    MailPasswordToken($loginname, $token);
+
+}
+
+
+sub GenerateUniqueToken {
+    # Generates a unique random token.  Uses &GenerateRandomPassword 
+    # for the tokens themselves and checks uniqueness by searching for
+    # the token in the "tokens" table.  Gives up if it can't come up
+    # with a token after about one hundred tries.
+
+    my $token;
+    my $duplicate = 1;
+    my $tries = 0;
+    while ($duplicate) {
+
+        ++$tries;
+        if ($tries > 100) {
+            &::DisplayError("Something is seriously wrong with the token generation system.");
+            exit;
+        }
+
+        $token = &::GenerateRandomPassword();
+        &::SendSQL("SELECT userid FROM tokens WHERE token = " . &::SqlQuote($token));
+        $duplicate = &::FetchSQLData();
+    }
+
+    return $token;
+
+}
+
+sub MailPasswordToken {
+    # Emails a password token to a user along with instructions for its use.
+    # Called exclusively from &IssuePasswordToken.
+
+    my ($emailaddress, $token) = @_;
+
+    my $urlbase = &::Param("urlbase");
+    my $emailsuffix = &::Param('emailsuffix');
+
+    open SENDMAIL, "|/usr/lib/sendmail -t";
+
+    print SENDMAIL qq|From: bugzilla-daemon
+To: $emailaddress$emailsuffix
+Subject: Bugzilla Change Password Request
+
+You or someone impersonating you has requested to change your Bugzilla
+password.  To change your password, visit the following link:
+
+${urlbase}token.cgi?a=cfmpw&t=$token
+
+If you are not the person who made this request, or you wish to cancel
+this request, visit the following link:
+
+${urlbase}token.cgi?a=cxlpw&t=$token
+|;
+    close SENDMAIL;
+}
+
+sub Cancel {
+    # Cancels a previously issued token and notifies the system administrator.
+    # This should only happen when the user accidentally makes a token request
+    # or when a malicious hacker makes a token request on behalf of a user.
+    
+    my ($token, $cancelaction) = @_;
+
+    # Quote the token for inclusion in SQL statements.
+    my $quotedtoken = &::SqlQuote($token);
+    
+    # Get information about the token being cancelled.
+    &::SendSQL("SELECT  issuedate , tokentype , eventdata , login_name , realname
+                FROM    tokens, profiles 
+                WHERE   tokens.userid = profiles.userid
+                AND     token = $quotedtoken");
+    my ($issuedate, $tokentype, $eventdata, $loginname, $realname) = &::FetchSQLData();
+
+    # Get the email address of the Bugzilla maintainer.
+    my $maintainer = &::Param('maintainer');
+
+    # Format the user's real name and email address into a single string.
+    my $username = $realname ? $realname . " <" . $loginname . ">" : $loginname;
+
+    # Notify the user via email about the cancellation.
+    open SENDMAIL, "|/usr/lib/sendmail -t";
+    print SENDMAIL qq|From: bugzilla-daemon
+To: $username
+Subject: "$tokentype" token cancelled
+
+A token was cancelled from $::ENV{'REMOTE_ADDR'}.  This is either 
+an honest mistake or the result of a malicious hack attempt.  
+Take a look at the information below and forward this email 
+to $maintainer if you suspect foul play.
+
+            Token: $token
+       Token Type: $tokentype
+             User: $username
+       Issue Date: $issuedate
+       Event Data: $eventdata
+
+Cancelled Because: $cancelaction
+|;
+    close SENDMAIL;
+
+    # Delete the token from the database.
+    &::SendSQL("LOCK TABLE tokens WRITE");
+    &::SendSQL("DELETE FROM tokens WHERE token = $quotedtoken");
+    &::SendSQL("UNLOCK TABLES");
+}
+
+sub HasPasswordToken {
+    # Returns a password token if the user has one.  Otherwise returns 0 (false).
+    
+    my ($userid) = @_;
+    
+    &::SendSQL("SELECT token FROM tokens WHERE userid = $userid LIMIT 1");
+    my ($token) = &::FetchSQLData();
+    
+    return $token;
+}
+
+1;
index c314bad849b341877a6a8e8c0ae0cc3fa9a86cc2..7102df13c7866a08361b65e4a73610351155a8f8 100755 (executable)
@@ -914,7 +914,7 @@ $table{groups} =
 $table{logincookies} =
    'cookie mediumint not null auto_increment primary key,
     userid mediumint not null,
-    cryptpassword varchar(64),
+    cryptpassword varchar(34),
     hostname varchar(128),
     lastused timestamp,
 
@@ -936,8 +936,7 @@ $table{products} =
 $table{profiles} =
    'userid mediumint not null auto_increment primary key,
     login_name varchar(255) not null,
-    password varchar(16),
-    cryptpassword varchar(64),
+    cryptpassword varchar(34),
     realname varchar(255),
     groupset bigint not null,
     disabledtext mediumtext,
@@ -1038,6 +1037,19 @@ $table{duplicates} =
     'dupe_of mediumint(9) not null,
      dupe mediumint(9) not null primary key';
 
+# 2001-06-21, myk@mozilla.org, bug 77473:
+# Stores the tokens users receive when they want to change their password 
+# or email address.  Tokens provide an extra measure of security for these changes.
+$table{tokens} =
+    'userid mediumint not null , 
+     issuedate datetime not null , 
+     token varchar(16) not null primary key ,  
+     tokentype varchar(8) not null , 
+     eventdata tinytext null , 
+
+     index(userid)';
+
+
 
 ###########################################################################
 # Create tables
@@ -1417,12 +1429,15 @@ _End_Of_SQL_
     system("stty","-echo");  # disable input echoing
 
     while( $pass1 ne $pass2 ) {
-      while( $pass1 eq "" ) {
+      while( $pass1 eq "" || $pass1 !~ /^[a-zA-Z0-9-_]{3,16}$/ ) {
         print "Enter a password for the administrator account: ";
         $pass1 = <STDIN>;
         chomp $pass1;
         if(! $pass1 ) {
           print "\n\nIt's just plain stupid to not have a password.  Try again!\n";
+        } elsif ( $pass1 !~ /^[a-zA-Z0-9-_]{3,16}$/ ) {
+          print "The password must be 3-16 characters in length 
+                 and contain only letters, numbers, hyphens (-), and underlines (_).";
         }
       }
       print "\nPlease retype the password to verify: ";
@@ -1435,6 +1450,9 @@ _End_Of_SQL_
       }
     }
 
+    # Crypt the administrator's password
+    my $cryptedpassword = Crypt($pass1);
+
     system("stty","echo"); # re-enable input echoing
     $SIG{HUP}  = 'DEFAULT'; # and remove our interrupt hooks
     $SIG{INT}  = 'DEFAULT';
@@ -1442,12 +1460,12 @@ _End_Of_SQL_
     $SIG{TERM} = 'DEFAULT';
 
     $realname = $dbh->quote($realname);
-    $pass1 = $dbh->quote($pass1);
+    $cryptedpassword = $dbh->quote($cryptedpassword);
 
     $dbh->do(<<_End_Of_SQL_);
       INSERT INTO profiles
-      (login_name, realname, password, cryptpassword, groupset)
-      VALUES ($login, $realname, $pass1, encrypt($pass1), 0x7fffffffffffffff)
+      (login_name, realname, cryptpassword, groupset)
+      VALUES ($login, $realname, $cryptedpassword, 0x7fffffffffffffff)
 _End_Of_SQL_
   } else {
     $dbh->do(<<_End_Of_SQL_);
@@ -1460,6 +1478,41 @@ _End_Of_SQL_
 }
 
 
+sub Crypt {
+    # Crypts a password, generating a random salt to do it.
+    # Random salts are generated because the alternative is usually
+    # to use the first two characters of the password itself, and since
+    # the salt appears in plaintext at the beginning of the crypted
+    # password string this has the effect of revealing the first two
+    # characters of the password to anyone who views the crypted version.
+
+    my ($password) = @_;
+
+    # The list of characters that can appear in a salt.  Salts and hashes
+    # are both encoded as a sequence of characters from a set containing
+    # 64 characters, each one of which represents 6 bits of the salt/hash.
+    # The encoding is similar to BASE64, the difference being that the
+    # BASE64 plus sign (+) is replaced with a forward slash (/).
+    my @saltchars = (0..9, 'A'..'Z', 'a'..'z', '.', '/');
+
+    # Generate the salt.  We use an 8 character (48 bit) salt for maximum
+    # security on systems whose crypt uses MD5.  Systems with older
+    # versions of crypt will just use the first two characters of the salt.
+    my $salt = '';
+    for ( my $i=0 ; $i < 8 ; ++$i ) {
+        $salt .= $saltchars[rand(64)];
+    }
+
+    # Crypt the password.
+    my $cryptedpassword = crypt($password, $salt);
+
+    # Return the crypted password.
+    return $cryptedpassword;
+}
+
+
+
+
 ###########################################################################
 # Create initial test product if there are no products present.
 ###########################################################################
@@ -1808,10 +1861,10 @@ if (GetFieldDef('bugs', 'long_desc')) {
                         # him or something.  Invent a new profile entry,
                         # disabled, just to represent him.
                         $dbh->do("INSERT INTO profiles " .
-                                 "(login_name, password, cryptpassword," .
+                                 "(login_name, cryptpassword," .
                                  " disabledtext) VALUES (" .
                                  $dbh->quote($name) .
-                                 ", 'okthen', encrypt('okthen'), " .
+                                 ", " . $dbh->quote(Crypt('okthen')) . ", " . 
                                  "'Account created only to maintain database integrity')");
                         $s2 = $dbh->prepare("SELECT LAST_INSERT_ID()");
                         $s2->execute();
@@ -2204,6 +2257,42 @@ if (-d 'shadow') {
 DropField("profiles", "emailnotification");
 DropField("profiles", "newemailtech");
 
+# 2001-06-12; myk@mozilla.org; bugs 74032, 77473:
+# Recrypt passwords using Perl &crypt instead of the mysql equivalent
+# and delete plaintext passwords from the database.
+if ( GetFieldDef('profiles', 'password') ) {
+    
+    print <<ENDTEXT;
+Your current installation of Bugzilla stores passwords in plaintext 
+in the database and uses mysql's encrypt function instead of Perl's 
+crypt function to crypt passwords.  Passwords are now going to be 
+re-crypted with the Perl function, and plaintext passwords will be 
+deleted from the database.  This could take a while if your  
+installation has many users. 
+ENDTEXT
+
+    # Re-crypt everyone's password.
+         my $sth = $dbh->prepare("SELECT userid, password FROM profiles");
+         $sth->execute();
+
+    my $i = 1;
+
+    print "Fixing password #1... ";
+         while (my ($userid, $password) = $sth->fetchrow_array()) {
+        my $cryptpassword = $dbh->quote(Crypt($password));
+        $dbh->do("UPDATE profiles SET cryptpassword = $cryptpassword WHERE userid = $userid");
+        ++$i;
+        # Let the user know where we are at every 500 records.
+        print "$i... " if !($i%500);
+         }
+    print "$i... Done.\n";
+
+    # Drop the plaintext password field and resize the cryptpassword field.
+    DropField('profiles', 'password');
+    ChangeFieldType('profiles', 'cryptpassword', 'varchar(34)');
+
+}
+
 #
 # 2001-06-06 justdave@syndicomm.com:
 # There was no index on the 'who' column in the long descriptions table.
index c2358d6fdb58078c9fc9b8105de23dadd9dd75b3..5b9bfb9f6a19bd253d154818b434f5b302aaa0ed 100755 (executable)
@@ -57,13 +57,18 @@ my $realname = $::FORM{'realname'};
 if (defined $login) {
     CheckEmailSyntax($login);
     if (DBname_to_id($login) != 0) {
-       PutHeader("Account exists");
-       print "A bugzilla account for the name <tt>$login</tt> already\n";
-       print "exists.  If you have forgotten the password for it, then\n";
-       print "<a href=query.cgi?GoAheadAndLogIn>click here</a> and use\n";
-       print "the <b>E-mail me a password</b> button.\n";
+        PutHeader("Account Exists");
+        print qq|
+          <form method="get" action="token.cgi">
+            <input type="hidden" name="a" value="reqpw">
+            <input type="hidden" name="loginname" value="$login">
+            A Bugzilla account for <tt>$login</tt> already exists.  If you
+            are the account holder and have forgotten your password, 
+            <input type="submit" value="submit a request to change it">.
+          </form>
+        |;
         PutFooter();
-       exit;
+        exit;
     }
     PutHeader("Account created");
     my $password = InsertNewUser($login, $realname);
index 41240473ec7e71402e34fa34e25e645621e0173a..48dcfa0c1a22c414e74ee2a053ac80feb0f93d7a 100755 (executable)
@@ -97,10 +97,9 @@ sub EmitElement ($$)
 # Displays the form to edit a user parameters
 #
 
-sub EmitFormElements ($$$$$$)
+sub EmitFormElements ($$$$$)
 {
-    my ($user, $password, $realname, $groupset, $blessgroupset,
-        $disabledtext) = @_;
+    my ($user, $realname, $groupset, $blessgroupset, $disabledtext) = @_;
 
     print "  <TH ALIGN=\"right\">Login name:</TH>\n";
     EmitElement("user", $user);
@@ -115,7 +114,11 @@ sub EmitFormElements ($$$$$$)
         if(Param('useLDAP')) {
           print "  <TD><FONT COLOR=RED>This site is using LDAP for authentication!</FONT></TD>\n";
         } else {
-          print "  <TD><INPUT TYPE=\"PASSWORD\" SIZE=16 MAXLENGTH=16 NAME=\"password\" VALUE=\"$password\"></TD>\n";
+          print qq|
+            <TD><INPUT TYPE="PASSWORD" SIZE="16" MAXLENGTH="16" NAME="password" VALUE=""><br>
+                (enter new password to change)
+            </TD>
+          |;
         }
         print "</TR><TR>\n";
 
@@ -386,7 +389,7 @@ if ($action eq 'add') {
     print "<FORM METHOD=POST ACTION=editusers.cgi>\n";
     print "<TABLE BORDER=0 CELLPADDING=4 CELLSPACING=0><TR>\n";
 
-    EmitFormElements('', '', '', 0, 0, '');
+    EmitFormElements('', '', 0, 0, '');
 
     print "</TR></TABLE>\n<HR>\n";
     print "<INPUT TYPE=SUBMIT VALUE=\"Add\">\n";
@@ -423,7 +426,10 @@ if ($action eq 'new') {
 
     # Cleanups and valididy checks
     my $realname = trim($::FORM{realname} || '');
-    my $password = trim($::FORM{password} || '');
+    # We don't trim the password since that could falsely lead the user
+    # to believe a password with a space was accepted even though a space 
+    # is an illegal character in a Bugzilla password.
+    my $password = $::FORM{'password'};
     my $disabledtext = trim($::FORM{disabledtext} || '');
     my $emailregexp = Param("emailregexp");
 
@@ -445,10 +451,9 @@ if ($action eq 'new') {
         PutTrailer($localtrailer);
        exit;
     }
-    if ($password !~ /^[a-zA-Z0-9-_]*$/ || length($password) < 3 || length($password) > 16) {
-        print "The new user must have a password. The password must be between ",
-             "3 and 16 characters long and must contain only numbers, letters, ",
-             "hyphens and underlines. Press <b>Back</b> and try again.\n";
+    my $passworderror = ValidatePassword($password);
+    if ( $passworderror ) {
+        print $passworderror;
         PutTrailer($localtrailer);
         exit;
     }
@@ -473,12 +478,11 @@ if ($action eq 'new') {
 
     # Add the new user
     SendSQL("INSERT INTO profiles ( " .
-            "login_name, password, cryptpassword, realname, groupset, " .
+            "login_name, cryptpassword, realname, groupset, " .
             "disabledtext" .
             " ) VALUES ( " .
             SqlQuote($user) . "," .
-            SqlQuote($password) . "," .
-            "encrypt(" . SqlQuote($password) . ")," .
+            SqlQuote(Crypt($password)) . "," .
             SqlQuote($realname) . "," .
             $bits . "," .
             SqlQuote($disabledtext) . ")" );
@@ -682,24 +686,20 @@ if ($action eq 'edit') {
     CheckUser($user);
 
     # get data of user
-    SendSQL("SELECT password, realname, groupset, blessgroupset, disabledtext
+    SendSQL("SELECT realname, groupset, blessgroupset, disabledtext
             FROM profiles
             WHERE login_name=" . SqlQuote($user));
-    my ($password, $realname, $groupset, $blessgroupset,
+    my ($realname, $groupset, $blessgroupset,
         $disabledtext) = FetchSQLData();
 
     print "<FORM METHOD=POST ACTION=editusers.cgi>\n";
     print "<TABLE BORDER=0 CELLPADDING=4 CELLSPACING=0><TR>\n";
 
-    EmitFormElements($user, $password, $realname, $groupset, $blessgroupset,
-                     $disabledtext);
+    EmitFormElements($user, $realname, $groupset, $blessgroupset, $disabledtext);
     
     print "</TR></TABLE>\n";
 
     print "<INPUT TYPE=HIDDEN NAME=\"userold\" VALUE=\"$user\">\n";
-    if ($editall && !Param('useLDAP')) {
-        print "<INPUT TYPE=HIDDEN NAME=\"passwordold\" VALUE=\"$password\">\n";
-    }
     print "<INPUT TYPE=HIDDEN NAME=\"realnameold\" VALUE=\"$realname\">\n";
     print "<INPUT TYPE=HIDDEN NAME=\"groupsetold\" VALUE=\"$groupset\">\n";
     print "<INPUT TYPE=HIDDEN NAME=\"blessgroupsetold\" VALUE=\"$blessgroupset\">\n";
@@ -726,8 +726,7 @@ if ($action eq 'update') {
     my $userold               = trim($::FORM{userold}              || '');
     my $realname              = trim($::FORM{realname}             || '');
     my $realnameold           = trim($::FORM{realnameold}          || '');
-    my $password              = trim($::FORM{password}             || '');
-    my $passwordold           = trim($::FORM{passwordold}          || '');
+    my $password              = $::FORM{password}                  || '';
     my $disabledtext          = trim($::FORM{disabledtext}         || '');
     my $disabledtextold       = trim($::FORM{disabledtextold}      || '');
     my $groupsetold           = trim($::FORM{groupsetold}          || '0');
@@ -791,14 +790,19 @@ if ($action eq 'update') {
        print "Updated ability to tweak permissions of other users.\n";
     }
 
-    if(!Param('useLDAP')) {
-      if ($editall && $password ne $passwordold) {
-        my $q = SqlQuote($password);
-        SendSQL("UPDATE profiles
-                SET password= $q, cryptpassword = ENCRYPT($q)
-                WHERE login_name=" . SqlQuote($userold));
-       print "Updated password.<BR>\n";
-      }
+    # Update the database with the user's new password if they changed it.
+    if ( !Param('useLDAP') && $editall && $password ) {
+        my $passworderror = ValidatePassword($password);
+        if ( !$passworderror ) {
+            my $cryptpassword = SqlQuote(Crypt($password));
+            my $loginname = SqlQuote($userold);
+            SendSQL("UPDATE  profiles
+                     SET     cryptpassword = $cryptpassword
+                     WHERE   login_name = $loginname");
+            print "Updated password.<BR>\n";
+        } else {
+            print "Did not update password: $passworderror<br>\n";
+        }
     }
     if ($editall && $realname ne $realnameold) {
         SendSQL("UPDATE profiles
index 736cb431ae07ae88f6ad351a3196fab9357d2b4e..302d9b8b711617595da118e31af546e16bb873c6 100644 (file)
@@ -616,19 +616,21 @@ sub GetVersionTable {
 
 sub InsertNewUser {
     my ($username, $realname) = (@_);
-    my $password = "";
-    for (my $i=0 ; $i<8 ; $i++) {
-        $password .= substr("abcdefghijklmnopqrstuvwxyz", int(rand(26)), 1);
-    }
 
+    # Generate a new random password for the user.
+    my $password = GenerateRandomPassword();
+    my $cryptpassword = Crypt($password);
+
+    # Determine what groups the user should be in by default
+    # and add them to those groups.
     PushGlobalSQLState();
     SendSQL("select bit, userregexp from groups where userregexp != ''");
     my $groupset = "0";
     while (MoreSQLData()) {
         my @row = FetchSQLData();
-       # Modified -Joe Robins, 2/17/00
-       # Making this case insensitive, since usernames are email addresses,
-       # and could be any case.
+        # Modified -Joe Robins, 2/17/00
+        # Making this case insensitive, since usernames are email addresses,
+        # and could be any case.
         if ($username =~ m/$row[1]/i) {
             $groupset .= "+ $row[0]"; # Silly hack to let MySQL do the math,
                                       # not Perl, since we're dealing with 64
@@ -636,14 +638,103 @@ sub InsertNewUser {
                                       # does that.
         }
     }
-            
+
+    # Insert the new user record into the database.            
     $username = SqlQuote($username);
     $realname = SqlQuote($realname);
-    SendSQL("insert into profiles (login_name, realname, password, cryptpassword, groupset) values ($username, $realname, '$password', encrypt('$password'), $groupset)");
+    $cryptpassword = SqlQuote($cryptpassword);
+    SendSQL("INSERT INTO profiles (login_name, realname, cryptpassword, groupset) 
+             VALUES ($username, $realname, $cryptpassword, $groupset)");
     PopGlobalSQLState();
+
+    # Return the password to the calling code so it can be included 
+    # in an email sent to the user.
+    return $password;
+}
+
+sub GenerateRandomPassword {
+    my ($size) = @_;
+
+    # Generated passwords are eight characters long by default.
+    $size ||= 8;
+
+    # The list of characters that can appear in a password.
+    # If you change this you must also update &ValidatePassword below.
+    my @pwchars = (0..9, 'A'..'Z', 'a'..'z', '-', '_');
+    #my @pwchars = (0..9, 'A'..'Z', 'a'..'z', '-', '_', '!', '@', '#', '$', '%', '^', '&', '*');
+
+    # The number of characters in the list.
+    my $pwcharslen = scalar(@pwchars);
+
+    # Generate the password.
+    my $password = "";
+    for ( my $i=0 ; $i<$size ; $i++ ) {
+        $password .= $pwchars[rand($pwcharslen)];
+    }
+
+    # Return the password.
     return $password;
 }
 
+
+sub ValidatePassword {
+    # Determines whether or not a password is valid (i.e. meets Bugzilla's
+    # requirements for length and content).  If the password is valid, the
+    # function returns boolean false.  Otherwise it returns an error message
+    # (synonymous with boolean true) that can be displayed to the user.
+    
+    # If a second password is passed in, this function also verifies that
+    # the two passwords match.
+
+    my ($password, $matchpassword) = @_;
+    
+    if ( $password !~ /^[a-zA-Z0-9-_]*$/ ) {
+        return "The password contains an illegal character.  Legal characters are letters, numbers, hyphens (-), and underlines (_).";
+    } elsif ( length($password) < 3 ) {
+        return "The password is less than three characters long.  It must be at least three characters.";
+    } elsif ( length($password) > 16 ) {
+        return "The password is more than 16 characters long.  It must be no more than 16 characters.";
+    } elsif ( $matchpassword && $password ne $matchpassword ) { 
+        return "The two passwords do not match.";
+    }
+
+    return 0;
+}
+
+
+sub Crypt {
+    # Crypts a password, generating a random salt to do it.
+    # Random salts are generated because the alternative is usually
+    # to use the first two characters of the password itself, and since
+    # the salt appears in plaintext at the beginning of the crypted
+    # password string this has the effect of revealing the first two
+    # characters of the password to anyone who views the crypted version.
+
+    my ($password) = @_;
+
+    # The list of characters that can appear in a salt.  Salts and hashes
+    # are both encoded as a sequence of characters from a set containing
+    # 64 characters, each one of which represents 6 bits of the salt/hash.
+    # The encoding is similar to BASE64, the difference being that the
+    # BASE64 plus sign (+) is replaced with a forward slash (/).
+    my @saltchars = (0..9, 'A'..'Z', 'a'..'z', '.', '/');
+
+    # Generate the salt.  We use an 8 character (48 bit) salt for maximum
+    # security on systems whose crypt uses MD5.  Systems with older
+    # versions of crypt will just use the first two characters of the salt.
+    my $salt = '';
+    for ( my $i=0 ; $i < 8 ; ++$i ) {
+        $salt .= $saltchars[rand(64)];
+    }
+
+    # Crypt the password.
+    my $cryptedpassword = crypt($password, $salt);
+
+    # Return the crypted password.
+    return $cryptedpassword;
+}
+
+
 sub DBID_to_real_or_loginname {
     my ($id) = (@_);
     PushGlobalSQLState();
index bc3b823c7a35e9603dc7f124a4311427ca3b924b..c8f628e58885a9bea375b66a19c8d156757d1f9e 100755 (executable)
@@ -165,25 +165,6 @@ CrossCheck("profiles", "userid",
            ["components", "initialowner", "value"],
            ["components", "initialqacontact", "value", ["0"]]);
 
-Status("Checking passwords");
-SendSQL("SELECT COUNT(*) FROM profiles WHERE cryptpassword != ENCRYPT(password, left(cryptpassword, 2))");
-my $count = FetchOneColumn();
-if ($count) {
-    Alert("$count entries have problems in their crypted password.");
-    if ($::FORM{'rebuildpasswords'}) {
-        Status("Rebuilding passwords");
-        SendSQL("UPDATE profiles
-                 SET cryptpassword = ENCRYPT(password,
-                                            left(cryptpassword, 2))
-                 WHERE cryptpassword != ENCRYPT(password,
-                                                left(cryptpassword, 2))");
-        Status("Passwords have been rebuilt.");
-    } else {
-        print qq{<a href="sanitycheck.cgi?rebuildpasswords=1">Click here to rebuild the crypted passwords</a><p>\n};
-    }
-}
-
-
 
 Status("Checking groups");
 SendSQL("select bit from groups where bit != pow(2, round(log(bit) / log(2)))");
diff --git a/token.cgi b/token.cgi
new file mode 100755 (executable)
index 0000000..145f7d6
--- /dev/null
+++ b/token.cgi
@@ -0,0 +1,243 @@
+#!/usr/bonsaitools/bin/perl -w
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Myk Melez <myk@mozilla.org>
+
+############################################################################
+# Script Initialization
+############################################################################
+
+# Make it harder for us to do dangerous things in Perl.
+use diagnostics;
+use strict;
+
+# Include the Bugzilla CGI and general utility library.
+require "CGI.pl";
+
+# Establish a connection to the database backend.
+ConnectToDatabase();
+
+# Use the "Token" module that contains functions for doing various
+# token-related tasks.
+use Token;
+
+################################################################################
+# Data Validation / Security Authorization
+################################################################################
+
+# Throw an error if the form does not contain an "action" field specifying
+# what the user wants to do.
+$::FORM{'a'}
+  || DisplayError("I could not figure out what you wanted to do.")
+  && exit;
+
+# Assign the action to a global variable.
+$::action = $::FORM{'a'};
+
+# If a token was submitted, make sure it is a valid token that exists in the
+# database and is the correct type for the action being taken.
+if ($::FORM{'t'}) {
+  # Assign the token and its SQL quoted equivalent to global variables.
+  $::token = $::FORM{'t'};
+  $::quotedtoken = SqlQuote($::token);
+  
+  # Make sure the token contains only valid characters in the right amount.
+  my $validationerror = ValidatePassword($::token);
+  if ($validationerror) {
+      DisplayError('The token you entered is invalid.');
+      exit;
+  }
+
+  # Make sure the token exists in the database.
+  SendSQL( "SELECT tokentype FROM tokens WHERE token = $::quotedtoken" );
+  (my $tokentype = FetchSQLData())
+    || DisplayError("The token you submitted does not exist.")
+    && exit;
+
+  # Make sure the token is the correct type for the action being taken.
+  if ( grep($::action eq $_ , qw(cfmpw cxlpw chgpw)) && $tokentype ne 'password' ) {
+    DisplayError("That token cannot be used to change your password.");
+    Token::Cancel($::token, "user tried to use token to change password");
+    exit;
+  }
+}
+
+# If the user is requesting a password change, make sure they submitted
+# their login name and it exists in the database.
+if ( $::action eq 'reqpw' ) {
+    defined $::FORM{'loginname'}
+      || DisplayError("You must enter a login name when requesting to change your password.")
+      && exit;
+
+    # Make sure the login name looks like an email address.  This function
+    # displays its own error and stops execution if the login name looks wrong.
+    CheckEmailSyntax($::FORM{'loginname'});
+
+    my $quotedloginname = SqlQuote($::FORM{'loginname'});
+    SendSQL("SELECT userid FROM profiles WHERE login_name = $quotedloginname");
+    FetchSQLData()
+      || DisplayError("There is no Bugzilla account with that login name.")
+      && exit;
+}
+
+# If the user is changing their password, make sure they submitted a new
+# password and that the new password is valid.
+if ( $::action eq 'chgpw' ) {
+    defined $::FORM{'password'}
+      && defined $::FORM{'matchpassword'}
+      || DisplayError("You cannot change your password without submitting a new one.")
+      && exit;
+
+     my $passworderror = ValidatePassword($::FORM{'password'}, $::FORM{'matchpassword'});
+     if ( $passworderror ) {
+         DisplayError($passworderror);
+         exit;
+     }
+}
+
+################################################################################
+# Main Body Execution
+################################################################################
+
+# All calls to this script should contain an "action" variable whose value
+# determines what the user wants to do.  The code below checks the value of
+# that variable and runs the appropriate code.
+
+if ($::action eq 'reqpw') { 
+    requestChangePassword(); 
+} elsif ($::action eq 'cfmpw') { 
+    confirmChangePassword(); 
+} elsif ($::action eq 'cxlpw') { 
+    cancelChangePassword(); 
+} elsif ($::action eq 'chgpw') { 
+    changePassword(); 
+} else { 
+    # If the action that the user wants to take (specified in the "a" form field)
+    # is none of the above listed actions, display an error telling the user 
+    # that we do not understand what they would like to do.
+    DisplayError("I could not figure out what you wanted to do.");
+}
+
+exit;
+
+################################################################################
+# Functions
+################################################################################
+
+sub requestChangePassword {
+
+    Token::IssuePasswordToken($::FORM{'loginname'});
+
+    # Return HTTP response headers.
+    print "Content-Type: text/html\n\n";
+
+    PutHeader("Request to Change Password");
+    print qq|
+        <p>
+        A token for changing your password has been emailed to you.
+        Follow the instructions in that email to change your password.
+        </p>
+    |;
+    PutFooter();
+}
+
+sub confirmChangePassword {
+
+    # Return HTTP response headers.
+    print "Content-Type: text/html\n\n";
+
+    PutHeader("Change Password");
+    print qq|
+      <p>
+      To change your password, enter a new password twice:
+      </p>
+      <form method="post" action="token.cgi">
+        <input type="hidden" name="t" value="$::token">
+        <input type="hidden" name="a" value="chgpw">
+        <table>
+          <tr>
+            <th align="right">New Password:</th>
+            <td><input type="password" name="password" size="16" maxlength="16"></td>
+          </tr>
+          <tr>
+            <th align="right">New Password Again:</th>
+            <td><input type="password" name="matchpassword" size="16" maxlength="16"></td>
+          </tr>
+          <tr>
+            <th align="right">&nbsp;</th>
+            <td><input type="submit" value="Submit"></td>
+          </tr>
+        </table>
+      </form>
+    |;
+    PutFooter();
+}
+
+sub cancelChangePassword {
+    
+    Token::Cancel($::token, "user requested cancellation");
+
+    # Return HTTP response headers.
+    print "Content-Type: text/html\n\n";
+
+    PutHeader("Cancel Request to Change Password");
+    print qq|
+      <p>
+      Your request has been cancelled.
+      </p>
+    |;
+    PutFooter();
+}
+
+sub changePassword {
+
+    # Quote the password and token for inclusion into SQL statements.
+    my $cryptedpassword = Crypt($::FORM{'password'});
+    my $quotedpassword = SqlQuote($cryptedpassword);
+
+    # Get the user's ID from the tokens table.
+    SendSQL("SELECT userid FROM tokens WHERE token = $::quotedtoken");
+    my $userid = FetchSQLData();
+    
+    # Update the user's password in the profiles table and delete the token
+    # from the tokens table.
+    SendSQL("LOCK TABLE profiles WRITE , tokens WRITE");
+    SendSQL("UPDATE   profiles
+             SET      cryptpassword = $quotedpassword
+             WHERE    userid = $userid");
+    SendSQL("DELETE FROM tokens WHERE token = $::quotedtoken");
+    SendSQL("UNLOCK TABLES");
+
+    # Return HTTP response headers.
+    print "Content-Type: text/html\n\n";
+
+    # Let the user know their password has been changed.
+    PutHeader("Password Changed");
+    print qq|
+      <p>
+      Your password has been changed.
+      </p>
+    |;
+    PutFooter();
+}
+
+
+
+
index f880cf8e228a397c7bf171456ed3fb0bd8e942be..0eeda0e71009f929a4915d28c0d417feb6b1cd8f 100755 (executable)
@@ -148,9 +148,12 @@ sub SaveAccount {
         my $old = SqlQuote($::FORM{'Bugzilla_password'});
         my $pwd1 = SqlQuote($::FORM{'pwd1'});
         my $pwd2 = SqlQuote($::FORM{'pwd2'});
-        SendSQL("SELECT cryptpassword = ENCRYPT($old, LEFT(cryptpassword, 2)) " .
-                "FROM profiles WHERE userid = $userid");
-        if (!FetchOneColumn()) {
+        SendSQL("SELECT cryptpassword FROM profiles WHERE userid = $userid");
+        my $oldcryptedpwd = FetchOneColumn();
+        if ( !$oldcryptedpwd ) {
+            Error("I was unable to retrieve your old password from the database.");
+        }
+        if ( crypt($::FORM{'Bugzilla_password'}, $oldcryptedpwd) ne $oldcryptedpwd ) {
             Error("You did not enter your old password correctly.");
         }
         if ($pwd1 ne $pwd2) {
@@ -159,9 +162,13 @@ sub SaveAccount {
         if ($::FORM{'pwd1'} eq '') {
             Error("You must enter a new password.");
         }
-        SendSQL("UPDATE profiles SET password = $pwd1, " .
-                "cryptpassword = ENCRYPT($pwd1) " .
-                "WHERE userid = $userid");
+        my $passworderror = ValidatePassword($::FORM{'pwd1'});
+        Error($passworderror) if $passworderror;
+
+        my $cryptedpassword = SqlQuote(Crypt($::FORM{'pwd1'}));
+        SendSQL("UPDATE  profiles 
+                 SET     cryptpassword = $cryptedpassword 
+                 WHERE   userid = $userid");
     }
     SendSQL("UPDATE profiles SET " .
             "realname = " . SqlQuote($::FORM{'realname'}) .