New module 'Secure Password Hashes' with core patch applied
This commit is contained in:
parent
3251330c4c
commit
6a3eb66d19
10 changed files with 1171 additions and 2 deletions
28
modules/phpass/INSTALL.txt
Normal file
28
modules/phpass/INSTALL.txt
Normal file
|
@ -0,0 +1,28 @@
|
|||
|
||||
All necessary code is included with the module. The hashing functionality is
|
||||
backported as the include file from Drupal 7 with minor changes.
|
||||
|
||||
This module requires PHP 5.2.4 as a minimum version (the same as Drupal 7).
|
||||
|
||||
During installation all existing user passwords will be converted to the
|
||||
more secure form. This is a lengthy process. Using drush expect a
|
||||
conversion time on the order of 3-5 minutes per 10,000 users. Using
|
||||
the modules page, a batch process will run and will take at least as long.
|
||||
|
||||
Once you have installed this module, all password hashes will be converted.
|
||||
You will not be able to disable or uninstall it except via upgrading to
|
||||
Drupal 7. Test this on your developent copy of your site first. You have
|
||||
been warned.
|
||||
|
||||
Note the enable hook tries to convert all existing user passwords. If you have more
|
||||
than a thousand users a batch job will start when the module is enabled if enabled
|
||||
via the modules page. If enabled via drush or with <= 1000 users, no batching
|
||||
wiil be performed. You may need to increase the allowed PHP memory if enabling
|
||||
via drush for very large numbers of users.
|
||||
|
||||
Please test everything on your development copy of the site first!
|
||||
|
||||
A core patch is also included with the module as user_load.D6.patch
|
||||
This patch is not needed for typical sites, but may be required for
|
||||
certain contrib modules that perform user registration with an external
|
||||
service or manipulate the user registration flow.
|
339
modules/phpass/LICENSE.txt
Normal file
339
modules/phpass/LICENSE.txt
Normal file
|
@ -0,0 +1,339 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
22
modules/phpass/README.txt
Normal file
22
modules/phpass/README.txt
Normal file
|
@ -0,0 +1,22 @@
|
|||
|
||||
This module implements secure password hashes using backported code from
|
||||
Drupal 7, which is the outcome of http://drupal.org/node/29706.
|
||||
|
||||
This module requires PHP 5.2.4 as a minimum version (the same as Drupal 7).
|
||||
|
||||
IMPORTANT - this module cannot be disabled except via SQL or upgrading to
|
||||
Drupal 7. Disabling will mean all users will have to login via password-reset
|
||||
links.
|
||||
|
||||
Upgrading to Drupal 7 should be seamless as long as the core version is
|
||||
>= 7.9. That is the first release that includes the fix at
|
||||
http://drupal.org/node/1205138. The hashes stored in the {users} table
|
||||
by this module are fully compatible with the Drupal 7 password system.
|
||||
|
||||
The default Drupal 6 and before password hashes are insecure. MD5 has
|
||||
long been known to be crackable, and we should not be storing passwords
|
||||
using this.
|
||||
|
||||
There has been a long discussion about this. See http://drupal.org/node/29706
|
||||
and http://drupal.org/node/1201444 for in-depth discussions. Do not post
|
||||
issues about anything already discussed there.
|
63
modules/phpass/password-hash.sh
Normal file
63
modules/phpass/password-hash.sh
Normal file
|
@ -0,0 +1,63 @@
|
|||
#!/usr/bin/php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Drupal hash script - to generate a hash from a plaintext password
|
||||
*
|
||||
* Check for your PHP interpreter - on Windows you'll probably have to
|
||||
* replace line 1 with
|
||||
* #!c:/program files/php/php.exe
|
||||
*
|
||||
* @param password1 [password2 [password3 ...]]
|
||||
* Plain-text passwords in quotes (or with spaces backslash escaped).
|
||||
*/
|
||||
|
||||
if (version_compare(PHP_VERSION, "5.2.0", "<")) {
|
||||
$version = PHP_VERSION;
|
||||
echo <<<EOF
|
||||
|
||||
ERROR: This script requires at least PHP version 5.2.0. You invoked it with
|
||||
PHP version {$version}.
|
||||
\n
|
||||
EOF;
|
||||
exit;
|
||||
}
|
||||
|
||||
$script = basename(array_shift($_SERVER['argv']));
|
||||
|
||||
if (in_array('--help', $_SERVER['argv']) || empty($_SERVER['argv'])) {
|
||||
echo <<<EOF
|
||||
|
||||
Generate Drupal 7 password hashes from the shell.
|
||||
|
||||
Usage: {$script} "<plan-text password>"
|
||||
Example: {$script} "mynewpassword"
|
||||
|
||||
All arguments are long options.
|
||||
|
||||
--help Print this page.
|
||||
|
||||
"<password1>" ["<password2>" ["<password3>" ...]]
|
||||
|
||||
One or more plan-text passwords enclosed by double quotes. The
|
||||
output hash may be manually entered into the {users}.pass field to
|
||||
change a password via SQL to a known value.
|
||||
|
||||
EOF;
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fake variable_get() since we are not bootstrapping Drupal.
|
||||
*/
|
||||
function variable_get($name, $default = NULL) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
require_once dirname(__FILE__) . '/password.inc';
|
||||
|
||||
foreach ($_SERVER['argv'] as $password) {
|
||||
print("\npassword: $password \t\thash: ". user_hash_password($password) ."\n");
|
||||
}
|
||||
print("\n");
|
||||
|
350
modules/phpass/password.inc
Normal file
350
modules/phpass/password.inc
Normal file
|
@ -0,0 +1,350 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Secure password hashing functions for user authentication.
|
||||
*
|
||||
* Based on the Portable PHP password hashing framework.
|
||||
* @see http://www.openwall.com/phpass/
|
||||
*
|
||||
* An alternative or custom version of this password hashing API may be
|
||||
* used by setting the variable password_inc to the name of the PHP file
|
||||
* containing replacement user_hash_password(), user_check_password(), and
|
||||
* user_needs_new_hash() functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The standard log2 number of iterations for password stretching. This should
|
||||
* increase by 1 every Drupal version in order to counteract increases in the
|
||||
* speed and power of computers available to crack the hashes.
|
||||
*/
|
||||
define('DRUPAL_HASH_COUNT', 15);
|
||||
|
||||
/**
|
||||
* The minimum allowed log2 number of iterations for password stretching.
|
||||
*/
|
||||
define('DRUPAL_MIN_HASH_COUNT', 7);
|
||||
|
||||
/**
|
||||
* The maximum allowed log2 number of iterations for password stretching.
|
||||
*/
|
||||
define('DRUPAL_MAX_HASH_COUNT', 30);
|
||||
|
||||
/**
|
||||
* The expected (and maximum) number of characters in a hashed password.
|
||||
*/
|
||||
define('DRUPAL_HASH_LENGTH', 55);
|
||||
|
||||
/**
|
||||
* Returns a string for mapping an int to the corresponding base 64 character.
|
||||
*/
|
||||
function _password_itoa64() {
|
||||
return './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode bytes into printable base 64 using the *nix standard from crypt().
|
||||
*
|
||||
* @param $input
|
||||
* The string containing bytes to encode.
|
||||
* @param $count
|
||||
* The number of characters (bytes) to encode.
|
||||
*
|
||||
* @return
|
||||
* Encoded string
|
||||
*/
|
||||
function _password_base64_encode($input, $count) {
|
||||
$output = '';
|
||||
$i = 0;
|
||||
$itoa64 = _password_itoa64();
|
||||
do {
|
||||
$value = ord($input[$i++]);
|
||||
$output .= $itoa64[$value & 0x3f];
|
||||
if ($i < $count) {
|
||||
$value |= ord($input[$i]) << 8;
|
||||
}
|
||||
$output .= $itoa64[($value >> 6) & 0x3f];
|
||||
if ($i++ >= $count) {
|
||||
break;
|
||||
}
|
||||
if ($i < $count) {
|
||||
$value |= ord($input[$i]) << 16;
|
||||
}
|
||||
$output .= $itoa64[($value >> 12) & 0x3f];
|
||||
if ($i++ >= $count) {
|
||||
break;
|
||||
}
|
||||
$output .= $itoa64[($value >> 18) & 0x3f];
|
||||
} while ($i < $count);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random base 64-encoded salt prefixed with settings for the hash.
|
||||
*
|
||||
* Proper use of salts may defeat a number of attacks, including:
|
||||
* - The ability to try candidate passwords against multiple hashes at once.
|
||||
* - The ability to use pre-hashed lists of candidate passwords.
|
||||
* - The ability to determine whether two users have the same (or different)
|
||||
* password without actually having to guess one of the passwords.
|
||||
*
|
||||
* @param $count_log2
|
||||
* Integer that determines the number of iterations used in the hashing
|
||||
* process. A larger value is more secure, but takes more time to complete.
|
||||
*
|
||||
* @return
|
||||
* A 12 character string containing the iteration count and a random salt.
|
||||
*/
|
||||
function _password_generate_salt($count_log2) {
|
||||
$output = '$S$';
|
||||
// Ensure that $count_log2 is within set bounds.
|
||||
$count_log2 = _password_enforce_log2_boundaries($count_log2);
|
||||
// We encode the final log2 iteration count in base 64.
|
||||
$itoa64 = _password_itoa64();
|
||||
$output .= $itoa64[$count_log2];
|
||||
// 6 bytes is the standard salt for a portable phpass hash.
|
||||
$output .= _password_base64_encode(password_random_bytes(6), 6);
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that $count_log2 is within set bounds.
|
||||
*
|
||||
* @param $count_log2
|
||||
* Integer that determines the number of iterations used in the hashing
|
||||
* process. A larger value is more secure, but takes more time to complete.
|
||||
*
|
||||
* @return
|
||||
* Integer within set bounds that is closest to $count_log2.
|
||||
*/
|
||||
function _password_enforce_log2_boundaries($count_log2) {
|
||||
if ($count_log2 < DRUPAL_MIN_HASH_COUNT) {
|
||||
return DRUPAL_MIN_HASH_COUNT;
|
||||
}
|
||||
elseif ($count_log2 > DRUPAL_MAX_HASH_COUNT) {
|
||||
return DRUPAL_MAX_HASH_COUNT;
|
||||
}
|
||||
|
||||
return (int) $count_log2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash a password using a secure stretched hash.
|
||||
*
|
||||
* By using a salt and repeated hashing the password is "stretched". Its
|
||||
* security is increased because it becomes much more computationally costly
|
||||
* for an attacker to try to break the hash by brute-force computation of the
|
||||
* hashes of a large number of plain-text words or strings to find a match.
|
||||
*
|
||||
* @param $algo
|
||||
* The string name of a hashing algorithm usable by hash(), like 'sha256'.
|
||||
* @param $password
|
||||
* Plain-text password up to 512 bytes (128 to 512 UTF-8 characters) to hash.
|
||||
* @param $setting
|
||||
* An existing hash or the output of _password_generate_salt(). Must be
|
||||
* at least 12 characters (the settings and salt).
|
||||
*
|
||||
* @return
|
||||
* A string containing the hashed password (and salt) or FALSE on failure.
|
||||
* The return string will be truncated at DRUPAL_HASH_LENGTH characters max.
|
||||
*/
|
||||
function _password_crypt($algo, $password, $setting) {
|
||||
// Prevent DoS attacks by refusing to hash large passwords.
|
||||
if (strlen($password) > 512) {
|
||||
return FALSE;
|
||||
}
|
||||
// The first 12 characters of an existing hash are its setting string.
|
||||
$setting = substr($setting, 0, 12);
|
||||
|
||||
if ($setting[0] != '$' || $setting[2] != '$') {
|
||||
return FALSE;
|
||||
}
|
||||
$count_log2 = _password_get_count_log2($setting);
|
||||
// Hashes may be imported from elsewhere, so we allow != DRUPAL_HASH_COUNT
|
||||
if ($count_log2 < DRUPAL_MIN_HASH_COUNT || $count_log2 > DRUPAL_MAX_HASH_COUNT) {
|
||||
return FALSE;
|
||||
}
|
||||
$salt = substr($setting, 4, 8);
|
||||
// Hashes must have an 8 character salt.
|
||||
if (strlen($salt) != 8) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Convert the base 2 logarithm into an integer.
|
||||
$count = 1 << $count_log2;
|
||||
|
||||
// We rely on the hash() function being available in PHP 5.2+.
|
||||
$hash = hash($algo, $salt . $password, TRUE);
|
||||
do {
|
||||
$hash = hash($algo, $hash . $password, TRUE);
|
||||
} while (--$count);
|
||||
|
||||
$len = strlen($hash);
|
||||
$output = $setting . _password_base64_encode($hash, $len);
|
||||
// _password_base64_encode() of a 16 byte MD5 will always be 22 characters.
|
||||
// _password_base64_encode() of a 64 byte sha512 will always be 86 characters.
|
||||
$expected = 12 + ceil((8 * $len) / 6);
|
||||
return (strlen($output) == $expected) ? substr($output, 0, DRUPAL_HASH_LENGTH) : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the log2 iteration count from a stored hash or setting string.
|
||||
*/
|
||||
function _password_get_count_log2($setting) {
|
||||
$itoa64 = _password_itoa64();
|
||||
return strpos($itoa64, $setting[3]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash a password using a secure hash.
|
||||
*
|
||||
* @param $password
|
||||
* A plain-text password.
|
||||
* @param $count_log2
|
||||
* Optional integer to specify the iteration count. Generally used only during
|
||||
* mass operations where a value less than the default is needed for speed.
|
||||
*
|
||||
* @return
|
||||
* A string containing the hashed password (and a salt), or FALSE on failure.
|
||||
*/
|
||||
function user_hash_password($password, $count_log2 = 0) {
|
||||
if (empty($count_log2)) {
|
||||
// Use the standard iteration count.
|
||||
$count_log2 = variable_get('password_count_log2', DRUPAL_HASH_COUNT);
|
||||
}
|
||||
return _password_crypt('sha512', $password, _password_generate_salt($count_log2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a plain text password matches a stored hashed password.
|
||||
*
|
||||
* Alternative implementations of this function may use other data in the
|
||||
* $account object, for example the uid to look up the hash in a custom table
|
||||
* or remote database.
|
||||
*
|
||||
* @param $password
|
||||
* A plain-text password
|
||||
* @param $account
|
||||
* A user object with at least the fields from the {users} table.
|
||||
*
|
||||
* @return
|
||||
* TRUE or FALSE.
|
||||
*/
|
||||
function user_check_password($password, $account) {
|
||||
if (substr($account->pass, 0, 2) == 'U$') {
|
||||
// This may be an updated password from user_update_7000(). Such hashes
|
||||
// have 'U' added as the first character and need an extra md5().
|
||||
$stored_hash = substr($account->pass, 1);
|
||||
$password = md5($password);
|
||||
}
|
||||
else {
|
||||
$stored_hash = $account->pass;
|
||||
}
|
||||
|
||||
$type = substr($stored_hash, 0, 3);
|
||||
switch ($type) {
|
||||
case '$S$':
|
||||
// A normal Drupal 7 password using sha512.
|
||||
$hash = _password_crypt('sha512', $password, $stored_hash);
|
||||
break;
|
||||
case '$H$':
|
||||
// phpBB3 uses "$H$" for the same thing as "$P$".
|
||||
case '$P$':
|
||||
// A phpass password generated using md5. This is an
|
||||
// imported password or from an earlier Drupal version.
|
||||
$hash = _password_crypt('md5', $password, $stored_hash);
|
||||
break;
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
return ($hash && $stored_hash == $hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a user's hashed password needs to be replaced with a new hash.
|
||||
*
|
||||
* This is typically called during the login process when the plain text
|
||||
* password is available. A new hash is needed when the desired iteration count
|
||||
* has changed through a change in the variable password_count_log2 or
|
||||
* DRUPAL_HASH_COUNT or if the user's password hash was generated in an update
|
||||
* like user_update_7000().
|
||||
*
|
||||
* Alternative implementations of this function might use other criteria based
|
||||
* on the fields in $account.
|
||||
*
|
||||
* @param $account
|
||||
* A user object with at least the fields from the {users} table.
|
||||
*
|
||||
* @return
|
||||
* TRUE or FALSE.
|
||||
*/
|
||||
function user_needs_new_hash($account) {
|
||||
// Check whether this was an updated password.
|
||||
if ((substr($account->pass, 0, 3) != '$S$') || (strlen($account->pass) != DRUPAL_HASH_LENGTH)) {
|
||||
return TRUE;
|
||||
}
|
||||
// Ensure that $count_log2 is within set bounds.
|
||||
$count_log2 = _password_enforce_log2_boundaries(variable_get('password_count_log2', DRUPAL_HASH_COUNT));
|
||||
// Check whether the iteration count used differs from the standard number.
|
||||
return (_password_get_count_log2($account->pass) !== $count_log2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Code above from password.inc in Drupal 7
|
||||
*
|
||||
* Code below copied from function drupal_random_bytes() in bootstrap.inc in Drupal 7
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns a string of highly randomized bytes (over the full 8-bit range).
|
||||
*
|
||||
* This function is better than simply calling mt_rand() or any other built-in
|
||||
* PHP function because it can return a long string of bytes (compared to < 4
|
||||
* bytes normally from mt_rand()) and uses the best available pseudo-random source.
|
||||
*
|
||||
* @param $count
|
||||
* The number of characters (bytes) to return in the string.
|
||||
*/
|
||||
function password_random_bytes($count) {
|
||||
// $random_state does not use drupal_static as it stores random bytes.
|
||||
static $random_state, $bytes;
|
||||
// Initialize on the first call. The contents of $_SERVER includes a mix of
|
||||
// user-specific and system information that varies a little with each page.
|
||||
if (!isset($random_state)) {
|
||||
$random_state = print_r($_SERVER, TRUE);
|
||||
if (function_exists('getmypid')) {
|
||||
// Further initialize with the somewhat random PHP process ID.
|
||||
$random_state .= getmypid();
|
||||
}
|
||||
$bytes = '';
|
||||
}
|
||||
if (strlen($bytes) < $count) {
|
||||
// /dev/urandom is available on many *nix systems and is considered the
|
||||
// best commonly available pseudo-random source.
|
||||
if ($fh = @fopen('/dev/urandom', 'rb')) {
|
||||
// PHP only performs buffered reads, so in reality it will always read
|
||||
// at least 4096 bytes. Thus, it costs nothing extra to read and store
|
||||
// that much so as to speed any additional invocations.
|
||||
$bytes .= fread($fh, max(4096, $count));
|
||||
fclose($fh);
|
||||
}
|
||||
// If /dev/urandom is not available or returns no bytes, this loop will
|
||||
// generate a good set of pseudo-random bytes on any system.
|
||||
// Note that it may be important that our $random_state is passed
|
||||
// through hash() prior to being rolled into $output, that the two hash()
|
||||
// invocations are different, and that the extra input into the first one -
|
||||
// the microtime() - is prepended rather than appended. This is to avoid
|
||||
// directly leaking $random_state via the $output stream, which could
|
||||
// allow for trivial prediction of further "random" numbers.
|
||||
while (strlen($bytes) < $count) {
|
||||
$random_state = hash('sha256', microtime() . mt_rand() . $random_state);
|
||||
$bytes .= hash('sha256', mt_rand() . $random_state, TRUE);
|
||||
}
|
||||
}
|
||||
$output = substr($bytes, 0, $count);
|
||||
$bytes = substr($bytes, $count);
|
||||
return $output;
|
||||
}
|
||||
|
12
modules/phpass/phpass.info
Normal file
12
modules/phpass/phpass.info
Normal file
|
@ -0,0 +1,12 @@
|
|||
name = Secure Password Hashes
|
||||
description = "Store password hashes securely (using the Drupal 7 hash algorithm)"
|
||||
package = Security
|
||||
core = 6.x
|
||||
php = 5.2.4
|
||||
|
||||
; Information added by Drupal.org packaging script on 2014-11-19
|
||||
version = "6.x-2.1"
|
||||
core = "6.x"
|
||||
project = "phpass"
|
||||
datestamp = "1416425332"
|
||||
|
157
modules/phpass/phpass.install
Normal file
157
modules/phpass/phpass.install
Normal file
|
@ -0,0 +1,157 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Implements hook_schema_alter().
|
||||
*/
|
||||
function phpass_schema_alter(&$schema) {
|
||||
$schema['users']['fields']['pass']['length'] = 128;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_install().
|
||||
*/
|
||||
function phpass_install() {
|
||||
// Change the length of {users}.pass from 32 characters to 128.
|
||||
$ret = array();
|
||||
db_change_field($ret, 'users', 'pass', 'pass', array(
|
||||
'type' => 'varchar',
|
||||
'length' => 128,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
));
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_uninstall().
|
||||
*/
|
||||
function phpass_uninstall() {
|
||||
// This will almost never be called, but is useful for development.
|
||||
variable_del('phpass_user_update_hash_count');
|
||||
variable_del('phpass_user_update_cli_orderby');
|
||||
variable_del('phpass_user_update_cli_count');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_enable().
|
||||
*/
|
||||
function phpass_enable() {
|
||||
$cli = (!isset($_SERVER['SERVER_SOFTWARE']) && (php_sapi_name() == 'cli' || (is_numeric($_SERVER['argc']) && $_SERVER['argc'] > 0)));
|
||||
$user_count = db_result(db_query("SELECT COUNT(uid) FROM {users} WHERE uid > 0"));
|
||||
if ($cli || ($user_count <= 1000)) {
|
||||
// Don't bother with batched operations.
|
||||
require_once dirname(__FILE__) . '/password.inc';
|
||||
// Hash again all current hashed passwords.
|
||||
// Variables defined here should be overriden in $conf in settings.php if needed.
|
||||
// The count is in log 2, so 11 means 2048 iterations. This is much lower than
|
||||
// the standard 14 (by a fator of 8) for speed during mass conversion.
|
||||
$hash_count_log2 = variable_get('phpass_user_update_hash_count', 11);
|
||||
// An alternative ordering may be desired, such as 'u.login DESC'.
|
||||
$order_by = variable_get('phpass_user_update_cli_orderby', 'u.uid ASC');
|
||||
// Number of users to conver per iteration.
|
||||
$count = variable_get('phpass_user_update_cli_count', 10000);
|
||||
$from = 0;
|
||||
do {
|
||||
$has_rows = FALSE;
|
||||
$result = db_query_range("SELECT u.uid, u.pass FROM {users} u WHERE u.uid > 0 ORDER BY " . $order_by, $from, $count);
|
||||
$from += $count;
|
||||
while ($account = db_fetch_object($result)) {
|
||||
$has_rows = TRUE;
|
||||
// If the $account->pass value is not a MD5 hash (a 32 character
|
||||
// hexadecimal string) then skip it.
|
||||
if (!preg_match('/^[0-9a-f]{32}$/', $account->pass)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$new_hash = user_hash_password($account->pass, $hash_count_log2);
|
||||
if ($new_hash) {
|
||||
// Indicate an updated password.
|
||||
$new_hash = 'U' . $new_hash;
|
||||
db_query("UPDATE {users} set pass = '%s' WHERE uid = %d", $new_hash, $account->uid);
|
||||
}
|
||||
}
|
||||
} while ($has_rows);
|
||||
}
|
||||
else {
|
||||
$t = get_t();
|
||||
$batch = array(
|
||||
'operations' => array(
|
||||
array('phpass_user_update_hashes',array()),
|
||||
),
|
||||
'title' => $t('Converting passwords'),
|
||||
'init_message' => $t('Starting password conversion'),
|
||||
'error_message' => $t('Error converting passwords'),
|
||||
'file' => __FILE__,
|
||||
);
|
||||
batch_set($batch);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-hashes all current passwords to improve security. This may be a
|
||||
* lengthy process, and is performed batch-wise.
|
||||
*
|
||||
* Adapted from Drupal 7 user_update_7000()
|
||||
*/
|
||||
function phpass_user_update_hashes(&$context) {
|
||||
$context['finished'] = 0;
|
||||
// Lower than DRUPAL_HASH_COUNT to make the update run at a reasonable speed.
|
||||
$hash_count_log2 = variable_get('phpass_user_update_hash_count', 11);
|
||||
// Multi-part update.
|
||||
if (!isset($context['sandbox']['user_from'])) {
|
||||
$context['sandbox']['user_from'] = 0;
|
||||
$context['sandbox']['user_count'] = db_result(db_query("SELECT COUNT(uid) FROM {users} WHERE uid > 0"));
|
||||
}
|
||||
require_once dirname(__FILE__) . '/password.inc';
|
||||
// Hash again all current hashed passwords.
|
||||
$has_rows = FALSE;
|
||||
// Update this many per page load.
|
||||
$count = 1000;
|
||||
$result = db_query_range("SELECT uid, pass FROM {users} WHERE uid > 0 ORDER BY uid", $context['sandbox']['user_from'], $count);
|
||||
while ($account = db_fetch_object($result)) {
|
||||
$has_rows = TRUE;
|
||||
// If the $account->pass value is not a MD5 hash (a 32 character
|
||||
// hexadecimal string) then skip it.
|
||||
if (!preg_match('/^[0-9a-f]{32}$/', $account->pass)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$new_hash = user_hash_password($account->pass, $hash_count_log2);
|
||||
if ($new_hash) {
|
||||
// Indicate an updated password.
|
||||
$new_hash = 'U' . $new_hash;
|
||||
db_query("UPDATE {users} set pass = '%s' WHERE uid = %d", $new_hash, $account->uid);
|
||||
}
|
||||
}
|
||||
$context['finished'] = $context['sandbox']['user_from']/$context['sandbox']['user_count'];
|
||||
$context['sandbox']['user_from'] += $count;
|
||||
if (!$has_rows) {
|
||||
$context['finished'] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update from 1.x branch.
|
||||
*/
|
||||
function phpass_update_6200(&$sess) {
|
||||
$ret = array();
|
||||
if (!isset($sess['context'])) {
|
||||
$ret = array_merge($ret, phpass_install());
|
||||
$sess['context'] = array();
|
||||
}
|
||||
if (db_table_exists('user_phpass')) {
|
||||
// Copy any portable hashes to the {users} table from {phpass}. The subquery
|
||||
// for should work for both postgres and mysql (I think).
|
||||
if (variable_get('user_hash_method', 'phpass') == 'phpass' && variable_get('user_hash_portable', TRUE)) {
|
||||
$ret[] = update_sql("UPDATE {users} u SET pass = (SELECT up.hash FROM {user_phpass} up WHERE up.uid = u.uid) WHERE EXISTS (SELECT up.hash FROM {user_phpass} up WHERE up.uid = u.uid)");
|
||||
}
|
||||
db_drop_table($ret, 'user_phpass');
|
||||
variable_del('user_hash_method');
|
||||
variable_del('user_hash_strength');
|
||||
variable_del('user_hash_portable');
|
||||
}
|
||||
phpass_user_update_hashes($sess['context']);
|
||||
$ret['#finished'] = $sess['context']['finished'];
|
||||
return $ret;
|
||||
}
|
||||
|
145
modules/phpass/phpass.module
Normal file
145
modules/phpass/phpass.module
Normal file
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Implements hook_user.
|
||||
*/
|
||||
function phpass_user($op, &$edit, &$account, $category = NULL) {
|
||||
switch ($op) {
|
||||
case 'submit':
|
||||
case 'update':
|
||||
case 'insert':
|
||||
if (isset($edit['pass'])) {
|
||||
// Function user_authenticate() applies a trim().
|
||||
$edit['pass'] = trim($edit['pass']);
|
||||
}
|
||||
// Catch password changes and update the password hash.
|
||||
if (!empty($edit['pass'])) {
|
||||
phpass_user_rehash_password($account, $edit['pass']);
|
||||
}
|
||||
// Prevent the md5 from being saved on update.
|
||||
$edit['pass'] = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_alter().
|
||||
*/
|
||||
function phpass_form_alter(&$form, $form_state, $form_id) {
|
||||
// Perform replacement of the core validation functions.
|
||||
if (!empty($form['#validate'])) {
|
||||
$key = array_search('user_login_authenticate_validate', $form['#validate']);
|
||||
if ($key !== FALSE) {
|
||||
$form['#validate'][$key] = 'phpass_user_login_authenticate_validate';
|
||||
}
|
||||
}
|
||||
// Add to the core submit function if the core patch is not applied.
|
||||
if (!defined('USER_LOAD_PHPASS_PATCHED') && !empty($form['#submit']) && in_array('user_register_submit', $form['#submit'])) {
|
||||
$form['#submit'][] = 'phpass_user_register_submit';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_system_info_alter().
|
||||
*/
|
||||
function phpass_system_info_alter(&$info, $file) {
|
||||
if ($file->name == 'user' && $file->type == 'module') {
|
||||
// Force user module to depend on this module so it cannot be
|
||||
// disabled, since user logins would fail.
|
||||
$info['dependencies'][] = 'phpass';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replacement for user_login_authenticate_validate().
|
||||
*/
|
||||
function phpass_user_login_authenticate_validate($form, &$form_state) {
|
||||
$form_state['values']['pass'] = trim($form_state['values']['pass']);
|
||||
phpass_user_authenticate($form_state['values']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra submit to follow user_register_submit().
|
||||
*/
|
||||
function phpass_user_register_submit($form, &$form_state) {
|
||||
global $user;
|
||||
|
||||
$account = isset($form_state['user']) ? $form_state['user'] : NULL;
|
||||
if (!$account || user_access('administer users') || variable_get('user_email_verification', TRUE) || !$account->status) {
|
||||
return;
|
||||
}
|
||||
// Test if the authentication failed (core patch not applied). This
|
||||
// only happens when email verification is not required for registration.
|
||||
if ($user->uid == 0 && !form_get_errors()) {
|
||||
$user = $account;
|
||||
user_authenticate_finalize($form_state['values']);
|
||||
drupal_set_message(t('Registration successful. You are now logged in.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replacement for user_authenticate().
|
||||
*/
|
||||
function phpass_user_authenticate($form_values = array()) {
|
||||
global $user;
|
||||
|
||||
require_once dirname(__FILE__) . '/password.inc';
|
||||
$account = _phpass_load_user($form_values['name'], $form_values['pass']);
|
||||
if ($account && drupal_is_denied('mail', $account->mail)) {
|
||||
form_set_error('name', t('The name %name is registered using a reserved e-mail address and therefore could not be logged in.', array('%name' => $account->name)));
|
||||
}
|
||||
|
||||
// Name and pass keys are required.
|
||||
// The user is about to be logged in, so make sure no error was previously
|
||||
// encountered in the validation process.
|
||||
if (!form_get_errors() && !empty($form_values['name']) && !empty($form_values['pass']) && $account) {
|
||||
$user = $account;
|
||||
user_authenticate_finalize($form_values);
|
||||
// Update user to new password hash if needed.
|
||||
if (user_needs_new_hash($account)) {
|
||||
phpass_user_rehash_password($account, $form_values['pass']);
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
else {
|
||||
watchdog('user', 'Login attempt failed for %user.', array('%user' => $form_values['name']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a user account by name and password.
|
||||
*
|
||||
* @param $name
|
||||
* The user name.
|
||||
* @param $password
|
||||
* The user's plaintext password.
|
||||
*/
|
||||
function _phpass_load_user($name, $password) {
|
||||
$uid = FALSE;
|
||||
if (!empty($name) && !empty($password)) {
|
||||
$account = user_load(array('name' => $name, 'status' => 1));
|
||||
if ($account && user_check_password($password, $account)) {
|
||||
// Successful authentication.
|
||||
$uid = $account->uid;
|
||||
}
|
||||
}
|
||||
return $uid ? $account : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a user's password hash.
|
||||
*
|
||||
* @param $account
|
||||
* A user account object.
|
||||
* @param $password
|
||||
* The user's current password.
|
||||
*/
|
||||
function phpass_user_rehash_password($account, $password, $hash_count_log2 = 0) {
|
||||
require_once dirname(__FILE__) . '/password.inc';
|
||||
$new_hash = user_hash_password($password, $hash_count_log2);
|
||||
if ($new_hash) {
|
||||
db_query("UPDATE {users} SET pass = '%s' WHERE uid = %d", $new_hash, $account->uid);
|
||||
$account->pass = $new_hash;
|
||||
}
|
||||
}
|
||||
|
44
modules/phpass/user_load.D6.patch
Normal file
44
modules/phpass/user_load.D6.patch
Normal file
|
@ -0,0 +1,44 @@
|
|||
diff --git a/modules/user/user.module b/modules/user/user.module
|
||||
index 625a00c..88ffbdb 100644
|
||||
--- a/modules/user/user.module
|
||||
+++ b/modules/user/user.module
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
define('USERNAME_MAX_LENGTH', 60);
|
||||
define('EMAIL_MAX_LENGTH', 64);
|
||||
+// Extra define to indicate user_load() is patched.
|
||||
+define('USER_LOAD_PHPASS_PATCHED', 1);
|
||||
|
||||
/**
|
||||
* Invokes hook_user() in every module.
|
||||
@@ -162,14 +164,14 @@ function user_load($user_info = array()) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
+ $password_check_needed = FALSE;
|
||||
foreach ($user_info as $key => $value) {
|
||||
if ($key == 'uid' || $key == 'status') {
|
||||
$query[] = "$key = %d";
|
||||
$params[] = $value;
|
||||
}
|
||||
else if ($key == 'pass') {
|
||||
- $query[] = "pass = '%s'";
|
||||
- $params[] = md5($value);
|
||||
+ $password_check_needed = TRUE;
|
||||
}
|
||||
else {
|
||||
$query[]= "LOWER($key) = LOWER('%s')";
|
||||
@@ -181,6 +183,13 @@ function user_load($user_info = array()) {
|
||||
if ($user = db_fetch_object($result)) {
|
||||
$user = drupal_unpack($user);
|
||||
|
||||
+ if ($password_check_needed) {
|
||||
+ module_load_include('inc', 'phpass', 'password');
|
||||
+ if (!user_check_password($user_info['pass'], $user)) {
|
||||
+ return FALSE;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
$user->roles = array();
|
||||
if ($user->uid) {
|
||||
$user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
define('USERNAME_MAX_LENGTH', 60);
|
||||
define('EMAIL_MAX_LENGTH', 64);
|
||||
// Extra define to indicate user_load() is patched.
|
||||
define('USER_LOAD_PHPASS_PATCHED', 1);
|
||||
|
||||
/**
|
||||
* Invokes hook_user() in every module.
|
||||
|
@ -162,14 +164,14 @@ function user_load($user_info = array()) {
|
|||
return FALSE;
|
||||
}
|
||||
|
||||
$password_check_needed = FALSE;
|
||||
foreach ($user_info as $key => $value) {
|
||||
if ($key == 'uid' || $key == 'status') {
|
||||
$query[] = "$key = %d";
|
||||
$params[] = $value;
|
||||
}
|
||||
else if ($key == 'pass') {
|
||||
$query[] = "pass = '%s'";
|
||||
$params[] = md5($value);
|
||||
$password_check_needed = TRUE;
|
||||
}
|
||||
else {
|
||||
$query[]= "LOWER($key) = LOWER('%s')";
|
||||
|
@ -181,6 +183,13 @@ function user_load($user_info = array()) {
|
|||
if ($user = db_fetch_object($result)) {
|
||||
$user = drupal_unpack($user);
|
||||
|
||||
if ($password_check_needed) {
|
||||
module_load_include('inc', 'phpass', 'password');
|
||||
if (!user_check_password($user_info['pass'], $user)) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
$user->roles = array();
|
||||
if ($user->uid) {
|
||||
$user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
|
||||
|
|
Reference in a new issue