summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Bugzilla/Token.pm184
-rw-r--r--CGI.pl122
-rw-r--r--Token.pm184
-rwxr-xr-xchecksetup.pl107
-rwxr-xr-xcreateaccount.cgi17
-rwxr-xr-xeditusers.cgi64
-rw-r--r--globals.pl109
-rwxr-xr-xsanitycheck.cgi19
-rwxr-xr-xtoken.cgi243
-rwxr-xr-xuserprefs.cgi19
10 files changed, 938 insertions, 130 deletions
diff --git a/Bugzilla/Token.pm b/Bugzilla/Token.pm
new file mode 100644
index 000000000..cde97f87e
--- /dev/null
+++ b/Bugzilla/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;
diff --git a/CGI.pl b/CGI.pl
index d7782f7e8..ede69a9ad 100644
--- 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
index 000000000..cde97f87e
--- /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;
diff --git a/checksetup.pl b/checksetup.pl
index c314bad84..7102df13c 100755
--- a/checksetup.pl
+++ b/checksetup.pl
@@ -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.
diff --git a/createaccount.cgi b/createaccount.cgi
index c2358d6fd..5b9bfb9f6 100755
--- a/createaccount.cgi
+++ b/createaccount.cgi
@@ -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);
diff --git a/editusers.cgi b/editusers.cgi
index 41240473e..48dcfa0c1 100755
--- a/editusers.cgi
+++ b/editusers.cgi
@@ -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
diff --git a/globals.pl b/globals.pl
index 736cb431a..302d9b8b7 100644
--- a/globals.pl
+++ b/globals.pl
@@ -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();
diff --git a/sanitycheck.cgi b/sanitycheck.cgi
index bc3b823c7..c8f628e58 100755
--- a/sanitycheck.cgi
+++ b/sanitycheck.cgi
@@ -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
index 000000000..145f7d6f8
--- /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();
+}
+
+
+
+
diff --git a/userprefs.cgi b/userprefs.cgi
index f880cf8e2..0eeda0e71 100755
--- a/userprefs.cgi
+++ b/userprefs.cgi
@@ -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'}) .