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('USERNAME_MAX_LENGTH', 60);
|
||||||
define('EMAIL_MAX_LENGTH', 64);
|
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.
|
* Invokes hook_user() in every module.
|
||||||
|
@ -162,14 +164,14 @@ function user_load($user_info = array()) {
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$password_check_needed = FALSE;
|
||||||
foreach ($user_info as $key => $value) {
|
foreach ($user_info as $key => $value) {
|
||||||
if ($key == 'uid' || $key == 'status') {
|
if ($key == 'uid' || $key == 'status') {
|
||||||
$query[] = "$key = %d";
|
$query[] = "$key = %d";
|
||||||
$params[] = $value;
|
$params[] = $value;
|
||||||
}
|
}
|
||||||
else if ($key == 'pass') {
|
else if ($key == 'pass') {
|
||||||
$query[] = "pass = '%s'";
|
$password_check_needed = TRUE;
|
||||||
$params[] = md5($value);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$query[]= "LOWER($key) = LOWER('%s')";
|
$query[]= "LOWER($key) = LOWER('%s')";
|
||||||
|
@ -181,6 +183,13 @@ function user_load($user_info = array()) {
|
||||||
if ($user = db_fetch_object($result)) {
|
if ($user = db_fetch_object($result)) {
|
||||||
$user = drupal_unpack($user);
|
$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();
|
$user->roles = array();
|
||||||
if ($user->uid) {
|
if ($user->uid) {
|
||||||
$user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
|
$user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
|
||||||
|
|
Reference in a new issue