Updating session handle to remove explicit phpBB3 dependancy.

This commit is contained in:
Chris 2011-07-27 16:09:54 +01:00
parent 901f879bba
commit a79795d96b
2 changed files with 108 additions and 108 deletions

View File

@ -20,12 +20,12 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
## @class ## @class
# The SessionHandler class provides cookie-based session facilities for # The SessionHandler class provides cookie-based session facilities for
# maintaining user state over http transactions. This code depends on # maintaining user state over http transactions. This code depends on
# integration with a phpBB3 database: a number of custom tables are needed # integration with a phpBB3 database: a number of custom tables are needed
# (see config docs), but user handling is tied to phpBB3 user tables, and # (see config docs), but user handling is tied to phpBB3 user tables, and
# a number of joins between custom tables and phpBB3 ones require the two # a number of joins between custom tables and phpBB3 ones require the two
# to share database space. This code provides session verification, and # to share database space. This code provides session verification, and
# takes some steps towards ensuring security against cookie hijacking, but # takes some steps towards ensuring security against cookie hijacking, but
# as with any cookie based auth system there is the potential for security # as with any cookie based auth system there is the potential for security
# issues. # issues.
@ -47,7 +47,6 @@ use MIME::Base64;
use Data::Dumper; use Data::Dumper;
# Custom module imports # Custom module imports
use phpBB3;
use Logging qw(die_log); use Logging qw(die_log);
# Globals... # Globals...
@ -72,7 +71,7 @@ sub new {
my $self = { my $self = {
cgi => undef, cgi => undef,
dbh => undef, dbh => undef,
phpbb => undef, auth => undef,
template => undef, template => undef,
settings => undef, settings => undef,
@_, @_,
@ -81,7 +80,7 @@ sub new {
# Ensure that we have objects that we need # Ensure that we have objects that we need
return set_error("cgi object not set") unless($self -> {"cgi"}); return set_error("cgi object not set") unless($self -> {"cgi"});
return set_error("dbh object not set") unless($self -> {"dbh"}); return set_error("dbh object not set") unless($self -> {"dbh"});
return set_error("phpbb object not set") unless($self -> {"phpbb"}); return set_error("auth object not set") unless($self -> {"auth"});
return set_error("template object not set") unless($self -> {"template"}); return set_error("template object not set") unless($self -> {"template"});
return set_error("settings object not set") unless($self -> {"settings"}); return set_error("settings object not set") unless($self -> {"settings"});
@ -98,12 +97,12 @@ sub new {
# Now try to obtain a session id - start by looking at the cookies # Now try to obtain a session id - start by looking at the cookies
$self -> {"sessid"} = $self -> {"cgi"} -> cookie($cookiebase."_sid"); # The session id cookie itself $self -> {"sessid"} = $self -> {"cgi"} -> cookie($cookiebase."_sid"); # The session id cookie itself
$self -> {"sessuser"} = $self -> {"cgi"} -> cookie($cookiebase."_u"); # Which user does this session claim to be for? $self -> {"sessuser"} = $self -> {"cgi"} -> cookie($cookiebase."_u"); # Which user does this session claim to be for?
$self -> {"autokey"} = $self -> {"cgi"} -> cookie($cookiebase."_k"); # Do we have an autologin key for the user? $self -> {"autokey"} = $self -> {"cgi"} -> cookie($cookiebase."_k"); # Do we have an autologin key for the user?
# If we don't have a session id now, try to pull it from the query string # If we don't have a session id now, try to pull it from the query string
$self -> {"sessid"} = $self -> {"cgi"} -> param("sid") if(!$self -> {"sessid"}); $self -> {"sessid"} = $self -> {"cgi"} -> param("sid") if(!$self -> {"sessid"});
# If we have a session id, we need to check it # If we have a session id, we need to check it
if($self -> {"sessid"}) { if($self -> {"sessid"}) {
# Try to get the session... # Try to get the session...
@ -119,9 +118,9 @@ sub new {
if(!$self -> session_expired($session)) { if(!$self -> session_expired($session)) {
# The session is valid, and can be touched. # The session is valid, and can be touched.
$self -> touch_session($session); $self -> touch_session($session);
return $self; return $self;
} # if(!$self -> session_expired($session)) { } # if(!$self -> session_expired($session)) {
} # if($self -> ip_check($ENV{"REMOTE_ADDR"}, $session -> {"session_ip"})) { } # if($self -> ip_check($ENV{"REMOTE_ADDR"}, $session -> {"session_ip"})) {
} # if($session) { } # if($session) {
} # if($sessid) { } # if($sessid) {
@ -133,7 +132,7 @@ sub new {
## @method $ create_session($user, $persist) ## @method $ create_session($user, $persist)
# Create a new session. If the user is not specified, this creates an anonymous session, # Create a new session. If the user is not specified, this creates an anonymous session,
# otherwise the session is attached to the user. # otherwise the session is attached to the user.
# #
# @param user Optional user ID to associate with the session. # @param user Optional user ID to associate with the session.
# @param persist If true, and autologins are permitted, an autologin key is generated for # @param persist If true, and autologins are permitted, an autologin key is generated for
@ -147,27 +146,27 @@ sub create_session {
# nuke the cookies, it's the only way to be sure # nuke the cookies, it's the only way to be sure
delete($self -> {"cookies"}) if($self -> {"cookies"}); delete($self -> {"cookies"}) if($self -> {"cookies"});
# get the current time... # get the current time...
my $now = time(); my $now = time();
# If persistent logins are not permitted, disable them # If persistent logins are not permitted, disable them
$self -> {"autokey"} = $persist = '' if(!$self -> {"phpbb"} -> get_config("allow_autologin")); $self -> {"autokey"} = $persist = '' if(!$self -> {"auth"} -> get_config("allow_autologin"));
# Set a default last visit, might be updated later # Set a default last visit, might be updated later
$self -> {"last_visit"} = $now; $self -> {"last_visit"} = $now;
# If we have a key, and a user in the cookies, try to get it # If we have a key, and a user in the cookies, try to get it
if($self -> {"autokey"} && $self -> {"sessuser"} && $self -> {"sessuser"} != $phpBB3::ANONYMOUS) { if($self -> {"autokey"} && $self -> {"sessuser"} && $self -> {"sessuser"} != $self -> {"auth"} -> {"ANONYMOUS"}) {
my $autocheck = $self -> {"dbh"} -> prepare("SELECT u.* FROM ". my $autocheck = $self -> {"dbh"} -> prepare("SELECT u.* FROM ".
$self -> {"phpbb"} -> {"prefix"}."users AS u, ". $self -> {"auth"} -> {"prefix"}."users AS u, ".
$self -> {"settings"} -> {"database"} -> {"keys"}." AS k $self -> {"settings"} -> {"database"} -> {"keys"}." AS k
WHERE u.user_id = ? WHERE u.user_id = ?
AND u.user_type IN (0, 3) AND u.user_type IN (0, 3)
AND k.user_id = u.user_id AND k.user_id = u.user_id
AND k.key_id = ?"); AND k.key_id = ?");
$autocheck -> execute($self -> {"sessuser"}, md5_hex($self -> {"autokey"})) $autocheck -> execute($self -> {"sessuser"}, md5_hex($self -> {"autokey"}))
or return set_error("Unable to peform user lookup query\nError was: ".$self -> {"dbh"} -> errstr); or return set_error("Unable to peform user lookup query\nError was: ".$self -> {"dbh"} -> errstr);
$userdata = $autocheck -> fetchrow_hashref; $userdata = $autocheck -> fetchrow_hashref;
@ -176,11 +175,11 @@ sub create_session {
$self -> {"autokey"} = ''; $self -> {"autokey"} = '';
$self -> {"sessuser"} = $user; $self -> {"sessuser"} = $user;
my $userh = $self -> {"dbh"} -> prepare("SELECT * FROM ".$self -> {"phpbb"} -> {"prefix"}."users my $userh = $self -> {"dbh"} -> prepare("SELECT * FROM ".$self -> {"auth"} -> {"prefix"}."users
WHERE user_id = ? WHERE user_id = ?
AND user_type IN (0, 3)"); AND user_type IN (0, 3)");
$userh -> execute($self -> {"sessuser"}) $userh -> execute($self -> {"sessuser"})
or return set_error("Unable to peform user lookup query\nError was: ".$self -> {"dbh"} -> errstr); or return set_error("Unable to peform user lookup query\nError was: ".$self -> {"dbh"} -> errstr);
$userdata = $userh -> fetchrow_hashref; $userdata = $userh -> fetchrow_hashref;
} }
@ -189,12 +188,12 @@ sub create_session {
# the user doesn't exist, is inactive, or is a bot. Just get the anonymous user # the user doesn't exist, is inactive, or is a bot. Just get the anonymous user
if(!$userdata) { if(!$userdata) {
$self -> {"autokey"} = ''; $self -> {"autokey"} = '';
$self -> {"sessuser"} = $phpBB3::ANONYMOUS; $self -> {"sessuser"} = $self -> {"auth"} -> {"ANONYMOUS"};
my $userh = $self -> {"dbh"} -> prepare("SELECT * FROM ".$self -> {"phpbb"} -> {"prefix"}."users my $userh = $self -> {"dbh"} -> prepare("SELECT * FROM ".$self -> {"auth"} -> {"prefix"}."users
WHERE user_id = ?"); WHERE user_id = ?");
$userh -> execute($self -> {"sessuser"}) $userh -> execute($self -> {"sessuser"})
or return set_error("Unable to peform user lookup query\nError was: ".$self -> {"dbh"} -> errstr); or return set_error("Unable to peform user lookup query\nError was: ".$self -> {"dbh"} -> errstr);
$userdata = $userh -> fetchrow_hashref; $userdata = $userh -> fetchrow_hashref;
@ -209,22 +208,22 @@ sub create_session {
# Fall back on now if we have no last visit time # Fall back on now if we have no last visit time
$self -> {"last_visit"} = $visitr -> [0] if($visitr); $self -> {"last_visit"} = $visitr -> [0] if($visitr);
} }
# Determine whether the session can be made persistent (requires the user to be registered, and normal) # Determine whether the session can be made persistent (requires the user to be registered, and normal)
my $is_registered = ($userdata -> {"user_id"} && $userdata -> {"user_id"} != $phpBB3::ANONYMOUS && ($userdata -> {"user_type"} == 0 || $userdata -> {"user_type"} == 3)); my $is_registered = ($userdata -> {"user_id"} && $userdata -> {"user_id"} != $self -> {"auth"} -> {"ANONYMOUS"} && ($userdata -> {"user_type"} == 0 || $userdata -> {"user_type"} == 3));
$persist = (($self -> {"autokey"} || $persist) && $is_registered) ? 1 : 0; $persist = (($self -> {"autokey"} || $persist) && $is_registered) ? 1 : 0;
# Do we already have a session id? If we do, and it's an anonymous session, we want to nuke it # Do we already have a session id? If we do, and it's an anonymous session, we want to nuke it
if($self -> {"sessid"}) { if($self -> {"sessid"}) {
my $killsess = $self -> {"dbh"} -> prepare("DELETE FROM ".$self -> {"settings"} -> {"database"} -> {"sessions"}. my $killsess = $self -> {"dbh"} -> prepare("DELETE FROM ".$self -> {"settings"} -> {"database"} -> {"sessions"}.
" WHERE session_id = ? AND session_user_id = ?"); " WHERE session_id = ? AND session_user_id = ?");
$killsess -> execute($self -> {"sessid"}, $phpBB3::ANONYMOUS) $killsess -> execute($self -> {"sessid"}, $self -> {"auth"} -> {"ANONYMOUS"})
or return set_error("Unable to remove anonymous session\nError was: ".$self -> {"dbh"} -> errstr); or return set_error("Unable to remove anonymous session\nError was: ".$self -> {"dbh"} -> errstr);
} }
# generate a new session id. The md5 of a unique ID should be unique enough... # generate a new session id. The md5 of a unique ID should be unique enough...
$self -> {"sessid"} = md5_hex($self -> {"phpbb"} -> unique_id()); $self -> {"sessid"} = md5_hex($self -> {"auth"} -> unique_id());
# store the time # store the time
$self -> {"session_time"} = $now; $self -> {"session_time"} = $now;
@ -239,7 +238,7 @@ sub create_session {
$ENV{"REMOTE_ADDR"}, $ENV{"REMOTE_ADDR"},
$persist) $persist)
or return set_error("Unable to peform session creation\nError was: ".$self -> {"dbh"} -> errstr); or return set_error("Unable to peform session creation\nError was: ".$self -> {"dbh"} -> errstr);
$self -> set_login_key($self -> {"sessuser"}, $ENV{"REMOTE_ADDR"}) if($persist); $self -> set_login_key($self -> {"sessuser"}, $ENV{"REMOTE_ADDR"}) if($persist);
return $self; return $self;
@ -261,8 +260,8 @@ sub delete_session {
# If we're not dealing with anonymous, we need to store the visit time, # If we're not dealing with anonymous, we need to store the visit time,
# and nuke any autologin key for the now defunct session # and nuke any autologin key for the now defunct session
if($self -> {"sessuser"} != $phpBB3::ANONYMOUS) { if($self -> {"sessuser"} != $self -> {"auth"} -> {"ANONYMOUS"}) {
# If we don't have a session time for some reason, make it now # If we don't have a session time for some reason, make it now
$self -> {"session_time"} = time() if(!$self -> {"session_time"}); $self -> {"session_time"} = time() if(!$self -> {"session_time"});
@ -281,7 +280,7 @@ sub delete_session {
" WHERE key_id = ? AND user_id = ?"); " WHERE key_id = ? AND user_id = ?");
$nukekeys -> execute(md5_hex($self -> {"autokey"}), $self -> {"sessuser"}) $nukekeys -> execute(md5_hex($self -> {"autokey"}), $self -> {"sessuser"})
or return set_error("Unable to remove session key\nError was: ".$self -> {"dbh"} -> errstr); or return set_error("Unable to remove session key\nError was: ".$self -> {"dbh"} -> errstr);
} }
} }
# clear all the session settings internally for safety # clear all the session settings internally for safety
@ -318,7 +317,7 @@ sub encode_querystring {
sub decode_querystring { sub decode_querystring {
my $self = shift; my $self = shift;
my $query = shift; my $query = shift;
# Bomb if we don't have a query, or it is not valid base64 # Bomb if we don't have a query, or it is not valid base64
return "" if(!$query || $query =~ m{[^A-Za-z0-9+/=]}); return "" if(!$query || $query =~ m{[^A-Za-z0-9+/=]});
@ -337,11 +336,11 @@ sub session_cookies {
# removed before any changes are made... but this shouldn't really be called before # removed before any changes are made... but this shouldn't really be called before
# create_session in reality anyway. # create_session in reality anyway.
if(!$self -> {"cookies"}) { if(!$self -> {"cookies"}) {
my $expires = "+".($self -> {"phpbb"} -> get_config("max_autologin_time") || 365)."d"; my $expires = "+".($self -> {"auth"} -> get_config("max_autologin_time") || 365)."d";
my $sesscookie = $self -> create_cookie($self -> {"settings"} -> {"config"} -> {"cookie_name"}.'_sid', $self -> {"sessid"}, $expires); my $sesscookie = $self -> create_cookie($self -> {"settings"} -> {"config"} -> {"cookie_name"}.'_sid', $self -> {"sessid"}, $expires);
my $sessuser = $self -> create_cookie($self -> {"settings"} -> {"config"} -> {"cookie_name"}.'_u', $self -> {"sessuser"}, $expires); my $sessuser = $self -> create_cookie($self -> {"settings"} -> {"config"} -> {"cookie_name"}.'_u', $self -> {"sessuser"}, $expires);
my $sesskey; my $sesskey;
if($self -> {"sessuser"} != $phpBB3::ANONYMOUS) { if($self -> {"sessuser"} != $self -> {"auth"} -> {"ANONYMOUS"}) {
if($self -> {"autokey"}) { if($self -> {"autokey"}) {
$sesskey = $self -> create_cookie($self -> {"settings"} -> {"config"} -> {"cookie_name"}.'_k', $self -> {"autokey"}, $expires); $sesskey = $self -> create_cookie($self -> {"settings"} -> {"config"} -> {"cookie_name"}.'_k', $self -> {"autokey"}, $expires);
} }
@ -363,7 +362,7 @@ sub session_cookies {
## @method ip_check($userip, $sessip) ## @method ip_check($userip, $sessip)
# Checks whether the specified IPs match. The degree of match required depends # Checks whether the specified IPs match. The degree of match required depends
# on the ip_check setting in the SessionHandler object this is called on: 0 means # on the ip_check setting in the SessionHandler object this is called on: 0 means
# that no checking is done, number between 1 and 4 indicate sections of the # that no checking is done, number between 1 and 4 indicate sections of the
# dotted decimal IPs are checked (1 = 127., 2 = 127.0, 3 = 127.0.0., etc) # dotted decimal IPs are checked (1 = 127., 2 = 127.0, 3 = 127.0.0., etc)
# #
# @param userip The IP the user is connecting from. # @param userip The IP the user is connecting from.
@ -375,7 +374,7 @@ sub ip_check {
my $sessip = shift; my $sessip = shift;
# How may IP address segments should be compared? # How may IP address segments should be compared?
my $iplen = $self -> {"phpbb"} -> get_config('ip_check'); my $iplen = $self -> {"auth"} -> get_config('ip_check');
# bomb immediately if we aren't checking IPs # bomb immediately if we aren't checking IPs
return 1 if($iplen == 0); return 1 if($iplen == 0);
@ -399,10 +398,10 @@ sub session_cleanup {
my $self = shift; my $self = shift;
my $now = time(); my $now = time();
my $timelimit = $now - $self -> {"phpbb"} -> get_config("session_length"); my $timelimit = $now - $self -> {"auth"} -> get_config("session_length");
# We only want to run the garbage collect occasionally # We only want to run the garbage collect occasionally
if($self -> {"settings"} -> {"config"} -> {"lastgc"} < $now - $self -> {"phpbb"} -> get_config("session_gc")) { if($self -> {"settings"} -> {"config"} -> {"lastgc"} < $now - $self -> {"auth"} -> get_config("session_gc")) {
# Okay, we're due a garbage collect, update the config to reflect that we're doing it # Okay, we're due a garbage collect, update the config to reflect that we're doing it
$self -> {"settings"} -> set_db_config($self -> {"dbh"}, $self -> {"settings"} -> {"database"} -> {"settings"}, "lastgc", $now); $self -> {"settings"} -> set_db_config($self -> {"dbh"}, $self -> {"settings"} -> {"database"} -> {"settings"}, "lastgc", $now);
@ -410,9 +409,9 @@ sub session_cleanup {
my $nukesess = $self -> {"dbh"} -> prepare("DELETE FROM ".$self -> {"settings"} -> {"database"} -> {"sessions"}. my $nukesess = $self -> {"dbh"} -> prepare("DELETE FROM ".$self -> {"settings"} -> {"database"} -> {"sessions"}.
" WHERE session_user_id = ? " WHERE session_user_id = ?
AND session_time < ?"); AND session_time < ?");
$nukesess -> execute($phpBB3::ANONYMOUS, $timelimit) $nukesess -> execute($self -> {"auth"} -> {"ANONYMOUS"}, $timelimit)
or return set_error("Unable to remove expired guest sessions\nError was: ".$self -> {"dbh"} -> errstr); or return set_error("Unable to remove expired guest sessions\nError was: ".$self -> {"dbh"} -> errstr);
# now get the most recent expired sessions for each user # now get the most recent expired sessions for each user
my $lastsess = $self -> {"dbh"} -> prepare("SELECT session_user_id,MAX(session_time) FROM ".$self -> {"settings"} -> {"database"} -> {"sessions"}. my $lastsess = $self -> {"dbh"} -> prepare("SELECT session_user_id,MAX(session_time) FROM ".$self -> {"settings"} -> {"database"} -> {"sessions"}.
" WHERE session_time < ? " WHERE session_time < ?
@ -424,7 +423,7 @@ sub session_cleanup {
my $updatelast; my $updatelast;
if($self -> {"settings"} -> {"database"} -> {"lastvisit"}) { if($self -> {"settings"} -> {"database"} -> {"lastvisit"}) {
$updatelast = $self -> {"dbh"} -> prepare("UPDATE ".$self -> {"settings"} -> {"database"} -> {"lastvisit"}. $updatelast = $self -> {"dbh"} -> prepare("UPDATE ".$self -> {"settings"} -> {"database"} -> {"lastvisit"}.
" SET last_visit = ? " SET last_visit = ?
WHERE user_id = ?"); WHERE user_id = ?");
} }
@ -458,13 +457,13 @@ sub session_expired {
# If the session is not an autologin session, and the last update was before the session length, it is expired # If the session is not an autologin session, and the last update was before the session length, it is expired
if(!$sessdata -> {"session_autologin"}) { if(!$sessdata -> {"session_autologin"}) {
return 1 if($sessdata -> {"session_time"} < time() - ($self -> {"phpbb"} -> get_config("session_length") + 60)); return 1 if($sessdata -> {"session_time"} < time() - ($self -> {"auth"} -> get_config("session_length") + 60));
} else { } else {
my $max_autologin = $self -> {"phpbb"} -> get_config("max_autologin_time"); my $max_autologin = $self -> {"auth"} -> get_config("max_autologin_time");
# If the session is autologin, and it is older than the max autologin time, or autologin is not enabled, it's expired # If the session is autologin, and it is older than the max autologin time, or autologin is not enabled, it's expired
return 1 if(!$self -> {"phpbb"} -> get_config("allow_autologin") || return 1 if(!$self -> {"auth"} -> get_config("allow_autologin") ||
($max_autologin && $sessdata -> {"session_time"} < time() - ((86400 * $max_autologin) + 60))); ($max_autologin && $sessdata -> {"session_time"} < time() - ((86400 * $max_autologin) + 60)));
} }
@ -474,7 +473,7 @@ sub session_expired {
## @method $ get_session($sessid) ## @method $ get_session($sessid)
# Obtain the data for the session with the specified session ID. If there is no # Obtain the data for the session with the specified session ID. If there is no
# session with the specified id in the database, this returns undef, otherwise it # session with the specified id in the database, this returns undef, otherwise it
# returns a reference to a hash containing the session data. # returns a reference to a hash containing the session data.
# #
@ -522,7 +521,7 @@ sub set_login_key {
my $self = shift; my $self = shift;
my $key = $self -> {"autokey"}; my $key = $self -> {"autokey"};
my $key_id = $self -> {"phpbb"} -> unique_id(substr($self -> {"sessid"}, 0, 8)); my $key_id = $self -> {"auth"} -> unique_id(substr($self -> {"sessid"}, 0, 8));
# If we don't have a key, we want to create a new key in the table # If we don't have a key, we want to create a new key in the table
if(!$key) { if(!$key) {

121
phpBB3.pm
View File

@ -25,12 +25,12 @@
# a perl script low-level access to the data stored in a phpBB3 database, # a perl script low-level access to the data stored in a phpBB3 database,
# and they should be used with caution. Unlike phpBB3, no security checks # and they should be used with caution. Unlike phpBB3, no security checks
# are done on, for example, whether the user is supposed to be able to see # are done on, for example, whether the user is supposed to be able to see
# a topic in a forum: while it would be technically possible to achieve # a topic in a forum: while it would be technically possible to achieve
# this, it would add a dramatic overhead to the listing and fetching of # this, it would add a dramatic overhead to the listing and fetching of
# posts and would involve session shenanigans to ensure users are logged # posts and would involve session shenanigans to ensure users are logged
# into a phpBB3 account. # into a phpBB3 account.
# #
# #
package phpBB3; package phpBB3;
use strict; use strict;
@ -84,7 +84,7 @@ BEGIN {
"e" => "%Z", "e" => "%Z",
"I" => "", # UNSUPPORTED: (capital i) Whether or not the date is in daylight saving time 1 if Daylight Saving Time, 0 otherwise. "I" => "", # UNSUPPORTED: (capital i) Whether or not the date is in daylight saving time 1 if Daylight Saving Time, 0 otherwise.
"O" => "%z", "O" => "%z",
"P" => "", # UNSUPPORTED: Difference to Greenwich time (GMT) with colon between hours and minutes (added in PHP 5.1.3) "P" => "", # UNSUPPORTED: Difference to Greenwich time (GMT) with colon between hours and minutes (added in PHP 5.1.3)
"T" => "%Z", "T" => "%Z",
"Z" => "", # UNSUPPORTED: Timezone offset in seconds. The offset for timezones west of UTC is always negative, and for those east of UTC is always positive. "Z" => "", # UNSUPPORTED: Timezone offset in seconds. The offset for timezones west of UTC is always negative, and for those east of UTC is always positive.
"c" => "%FT%T%z", "c" => "%FT%T%z",
@ -114,7 +114,7 @@ BEGIN {
# you do not need to provide the username, password, or data_src. # you do not need to provide the username, password, or data_src.
# #
# @param args A hash of key, value pairs to initialise the object with. # @param args A hash of key, value pairs to initialise the object with.
# @return A new phpBB3 object, or undef if no database connection has been provided or # @return A new phpBB3 object, or undef if no database connection has been provided or
# established. # established.
sub new { sub new {
my $invocant = shift; my $invocant = shift;
@ -130,6 +130,7 @@ sub new {
allowanon => 0, allowanon => 0,
dbopts => { RaiseError => 0, AutoCommit => 1 }, dbopts => { RaiseError => 0, AutoCommit => 1 },
url => "/", url => "/",
ANONYMOUS => $ANONYMOUS,
@_, @_,
}; };
@ -139,7 +140,7 @@ sub new {
# try to open the database connection with those credentials. # try to open the database connection with those credentials.
if(!$obj -> {"dbh"} && $self -> {"username"} & $obj -> {"password"} && $obj -> {"data_src"}) { if(!$obj -> {"dbh"} && $self -> {"username"} & $obj -> {"password"} && $obj -> {"data_src"}) {
$obj -> {"dbh"} = DBI -> connect($obj -> {"data_src"}, $obj -> {"dbh"} = DBI -> connect($obj -> {"data_src"},
$obj -> {"username"}, $obj -> {"username"},
$obj -> {"password"}, $obj -> {"password"},
$obj -> {"dbopts"}) $obj -> {"dbopts"})
or return set_error("Unable to open database connection - ".$DBI::errstr); or return set_error("Unable to open database connection - ".$DBI::errstr);
@ -221,10 +222,10 @@ sub register_user {
my $content = $www -> content(); my $content = $www -> content();
return "Unexpected content in response to registration first step accept." return "Unexpected content in response to registration first step accept."
unless($content =~ /PLEASE DO NOT ATTEMPT TO REGISTER USING THIS FORM/); unless($content =~ /PLEASE DO NOT ATTEMPT TO REGISTER USING THIS FORM/);
$www -> form_id('register') $www -> form_id('register')
or return "Unable to locate the second step registration form."; or return "Unable to locate the second step registration form.";
# Now we can fill in fields to submit # Now we can fill in fields to submit
$www -> field ('username' , $args -> {"username"}); $www -> field ('username' , $args -> {"username"});
$www -> field ('email' , $args -> {"email"}); $www -> field ('email' , $args -> {"email"});
@ -235,7 +236,7 @@ sub register_user {
$www -> field ('question2' , 'chris page'); $www -> field ('question2' , 'chris page');
$www -> select('lang' , 'en'); $www -> select('lang' , 'en');
$www -> select('tz' , '0'); $www -> select('tz' , '0');
# And submit that # And submit that
$www -> click('submit'); $www -> click('submit');
return "Failed when posting registration second step. Response was: ".$www -> res -> message if(!$www -> success()); return "Failed when posting registration second step. Response was: ".$www -> res -> message if(!$www -> success());
@ -334,7 +335,7 @@ sub get_group {
# group_id - the id of the group to check in. # group_id - the id of the group to check in.
# If user_id is specified, username is ignored if it is also provided. If user_id # If user_id is specified, username is ignored if it is also provided. If user_id
# is not provided, username must be (ie: you must specify at least one of username # is not provided, username must be (ie: you must specify at least one of username
# or user_id, and user_id takes precedence) Similarly, you must specify at least # or user_id, and user_id takes precedence) Similarly, you must specify at least
# one of group or group_id, and group_id takes precedence over group. # one of group or group_id, and group_id takes precedence over group.
# #
# @param args A hash of arguments. # @param args A hash of arguments.
@ -356,38 +357,38 @@ sub user_in_group {
# If we don't have a user_id, we need to look it up # If we don't have a user_id, we need to look it up
if(!$args{"user_id"}) { if(!$args{"user_id"}) {
my $user = $self -> get_user($args{'username'}) my $user = $self -> get_user($args{'username'})
or return set_error("Unable to find user $args{'username'}"); or return set_error("Unable to find user $args{'username'}");
$args{"user_id"} = $user -> {"user_id"}; $args{"user_id"} = $user -> {"user_id"};
} }
# If we don't have a group_id, we need to look it up # If we don't have a group_id, we need to look it up
if(!$args{"group_id"}) { if(!$args{"group_id"}) {
my $group = $self -> get_group($args{'group'}) my $group = $self -> get_group($args{'group'})
or return set_error("Unable to find user $args{'group'}"); or return set_error("Unable to find user $args{'group'}");
$args{"group_id"} = $group -> {"group_id"}; $args{"group_id"} = $group -> {"group_id"};
} }
# Now we should have a user id and group id, so we can go look in the user_group table # Now we should have a user id and group id, so we can go look in the user_group table
my $ugh = $self -> {"dbh"} -> prepare("SELECT * FROM ".$self -> {"prefix"}."user_group my $ugh = $self -> {"dbh"} -> prepare("SELECT * FROM ".$self -> {"prefix"}."user_group
WHERE group_id = ? AND user_id = ?"); WHERE group_id = ? AND user_id = ?");
$ugh -> execute($args{"group_id"}, $args{"user_id"}) $ugh -> execute($args{"group_id"}, $args{"user_id"})
or die "phpBB3::user_in_group(): Unable to execute user_group lookup query.\nError was: ".$self -> {"dbh"} -> errstr."\n"; or die "phpBB3::user_in_group(): Unable to execute user_group lookup query.\nError was: ".$self -> {"dbh"} -> errstr."\n";
# Do we have one or more rows? # Do we have one or more rows?
my $ugr = $ugh -> fetchrow_arrayref(); my $ugr = $ugh -> fetchrow_arrayref();
return defined($ugr); return defined($ugr);
} }
## @method $ valid_user($username, $password) ## @method $ valid_user($username, $password)
# Attempt to confirm whether the provided user credentials are valid. This will check # Attempt to confirm whether the provided user credentials are valid. This will check
# whether the specified username corresponds to a valid user, and if it does it will # whether the specified username corresponds to a valid user, and if it does it will
# check that the hash of the provided password matches. If the password matches, this # check that the hash of the provided password matches. If the password matches, this
# returns a reference to a hash containing the user's entry in the users table. # returns a reference to a hash containing the user's entry in the users table.
# #
# @param username The username of the user to check. # @param username The username of the user to check.
# @param password The password to check against this user. # @param password The password to check against this user.
# @return A reference to a hash containing the user's data, or undef if an error # @return A reference to a hash containing the user's data, or undef if an error
@ -422,8 +423,8 @@ sub get_profile_url {
## @method $ email_in_use($email) ## @method $ email_in_use($email)
# Determine whether the specified email address is already in use within the # Determine whether the specified email address is already in use within the
# system. # system.
# #
# @param email The email address to check. # @param email The email address to check.
# @return true if the email address already exists within the database, false # @return true if the email address already exists within the database, false
@ -432,7 +433,7 @@ sub email_in_use {
my $self = shift; my $self = shift;
my $email = shift; my $email = shift;
my $emailh = $self -> {'dbh'} -> prepare("SELECT user_id FROM ".$self -> {"prefix"}."users my $emailh = $self -> {'dbh'} -> prepare("SELECT user_id FROM ".$self -> {"prefix"}."users
WHERE user_email LIKE ?"); WHERE user_email LIKE ?");
$emailh -> execute($email) $emailh -> execute($email)
or die "phpBB3::email_in_use(): Unable to execute email lookup query.\nError was: ".$self -> {"dbh"} -> errstr."\n"; or die "phpBB3::email_in_use(): Unable to execute email lookup query.\nError was: ".$self -> {"dbh"} -> errstr."\n";
@ -485,16 +486,16 @@ sub is_valid_password {
# Session handling # Session handling
## @method @ get_session(void) ## @method @ get_session(void)
# Attempt to obtain the userid and username of the current session user. This # Attempt to obtain the userid and username of the current session user. This
# attempts to determine whether the session in the user's cookies is a valid # attempts to determine whether the session in the user's cookies is a valid
# phpBB3 session, and if it is it returns a reference to a hash containing user # phpBB3 session, and if it is it returns a reference to a hash containing user
# and session data. This will update the timestamp on the session, if needed. # and session data. This will update the timestamp on the session, if needed.
# #
# @return A reference to a hash containing user and session data if the session # @return A reference to a hash containing user and session data if the session
# is valid, undef otherwise. # is valid, undef otherwise.
# #
# @todo This does not currently support forwarded_for checks, referer checks, # @todo This does not currently support forwarded_for checks, referer checks,
# or load limiting. It also does not support 'alternative' auth methods: # or load limiting. It also does not support 'alternative' auth methods:
# only database auth is supported. # only database auth is supported.
sub get_session { sub get_session {
my $self = shift; my $self = shift;
@ -505,7 +506,7 @@ sub get_session {
# First, try to obtain a session id - start by looking at the cookies # First, try to obtain a session id - start by looking at the cookies
my $sessid = $self -> {"cgi"} -> cookie($cookiebase."_sid"); my $sessid = $self -> {"cgi"} -> cookie($cookiebase."_sid");
my $sessuser = $self -> {"cgi"} -> cookie($cookiebase."_u"); # Which users does this session claim to be? my $sessuser = $self -> {"cgi"} -> cookie($cookiebase."_u"); # Which users does this session claim to be?
my $autokey = $self -> {"cgi"} -> cookie($cookiebase."_k"); # Do we have an autologin key for the user? my $autokey = $self -> {"cgi"} -> cookie($cookiebase."_k"); # Do we have an autologin key for the user?
# If we don't have a session id now, try to pull it from the query string # If we don't have a session id now, try to pull it from the query string
@ -515,13 +516,13 @@ sub get_session {
return set_error("Unable to obtain a session id for user.") if(!$sessid); return set_error("Unable to obtain a session id for user.") if(!$sessid);
# Obtain the session and user record from the database # Obtain the session and user record from the database
my $sessh = $self -> {"dbh"} -> prepare("SELECT u.*,s.* my $sessh = $self -> {"dbh"} -> prepare("SELECT u.*,s.*
FROM ".$self -> {"prefix"}."users AS u, ".$self -> {"prefix"}."sessions AS s FROM ".$self -> {"prefix"}."users AS u, ".$self -> {"prefix"}."sessions AS s
WHERE s.session_id = ? AND u.user_id = s.session_user_id"); WHERE s.session_id = ? AND u.user_id = s.session_user_id");
$sessh -> execute($sessid) $sessh -> execute($sessid)
or die "phpBB3::get_session(): Unable to obtain session and user data from database.\nError was: ".$self -> {"dbh"} -> errstr."\n"; or die "phpBB3::get_session(): Unable to obtain session and user data from database.\nError was: ".$self -> {"dbh"} -> errstr."\n";
my $sessdata = $sessh -> fetchrow_hashref(); my $sessdata = $sessh -> fetchrow_hashref();
# if we have a session, we need to validate it # if we have a session, we need to validate it
if($sessdata) { if($sessdata) {
# If we have anonymous disabled, at this is the anon user, exit immediately # If we have anonymous disabled, at this is the anon user, exit immediately
@ -546,7 +547,7 @@ sub get_session {
if($self -> get_config("browser_check")) { if($self -> get_config("browser_check")) {
$valid_ua = substr(lc($sessdata -> {"session_browser"}), 0, 150) eq $valid_ua = substr(lc($sessdata -> {"session_browser"}), 0, 150) eq
substr(lc($self -> {"cgi"} -> user_agent()), 0, 150); substr(lc($self -> {"cgi"} -> user_agent()), 0, 150);
} }
# If the ip and browser checks are okay, continue with the validation # If the ip and browser checks are okay, continue with the validation
# TODO: add referer and forwarded_for checks here? # TODO: add referer and forwarded_for checks here?
@ -560,17 +561,17 @@ sub get_session {
# If the session claims to be autologin, but the server doesn't support it, expire the session # If the session claims to be autologin, but the server doesn't support it, expire the session
} elsif(!$self -> get_config("allow_autologin")) { } elsif(!$self -> get_config("allow_autologin")) {
$expired = 1; $expired = 1;
# Otherwise, if check whether a maximum autologin time limit has been set, and that the session is within it # Otherwise, if check whether a maximum autologin time limit has been set, and that the session is within it
} else { } else {
my $max_autologin = $self -> get_config("max_autologin_time"); my $max_autologin = $self -> get_config("max_autologin_time");
$expired = ($max_autologin && $sessdata -> {"session_time"} < (time() - ($max_autologin + 60))); $expired = ($max_autologin && $sessdata -> {"session_time"} < (time() - ($max_autologin + 60)));
} }
# If the session has not expired, we want to touch it # If the session has not expired, we want to touch it
if(!$expired) { if(!$expired) {
if(time() - $sessdata -> {"session_time"} > 60) { if(time() - $sessdata -> {"session_time"} > 60) {
my $touch = $self -> {"dbh"} -> prepare("UPDATE ".$self -> {"prefix"}."sessions my $touch = $self -> {"dbh"} -> prepare("UPDATE ".$self -> {"prefix"}."sessions
SET session_time = ? SET session_time = ?
WHERE session_id = ?"); WHERE session_id = ?");
$touch -> execute(time(), $sessdata -> {"session_id"}) $touch -> execute(time(), $sessdata -> {"session_id"})
@ -602,9 +603,9 @@ sub get_session {
# #
# @note <b>This function does no permissions checking whatsoever.</b> It is up # @note <b>This function does no permissions checking whatsoever.</b> It is up
# to the caller to determine whether or not the forum should be visible. # to the caller to determine whether or not the forum should be visible.
# If you expose private forums with this function, you have nobody to # If you expose private forums with this function, you have nobody to
# blame but yourself. You have been warned. # blame but yourself. You have been warned.
# #
# @param forumid The id of the forum to obtain data on. # @param forumid The id of the forum to obtain data on.
# @return A reference to a hash containing the forum data, or undef if the # @return A reference to a hash containing the forum data, or undef if the
# forum does not exist in the database. # forum does not exist in the database.
@ -629,21 +630,21 @@ sub get_forum {
# Given a forum id, a topic count, and an offset, obtain a list of topic IDs # Given a forum id, a topic count, and an offset, obtain a list of topic IDs
# for topics in the forum. This follows the same treatement of posts as the forum # for topics in the forum. This follows the same treatement of posts as the forum
# view in phpBB3: announcements always appear at the start of the list, regardles # view in phpBB3: announcements always appear at the start of the list, regardles
# of the offset. The remaining posts are sorted so that sticky topics will # of the offset. The remaining posts are sorted so that sticky topics will
# appear before normal topics, but are otherwise treated normally. # appear before normal topics, but are otherwise treated normally.
# #
# @note <b>This function does no permissions checking whatsoever.</b> It is up # @note <b>This function does no permissions checking whatsoever.</b> It is up
# to the caller to determine whether or not the forum should be visible. # to the caller to determine whether or not the forum should be visible.
# If you expose private forums with this function, you have nobody to # If you expose private forums with this function, you have nobody to
# blame but yourself. You have been warned. # blame but yourself. You have been warned.
# #
# @param forum The ID of the forum to obtain a topic list for. # @param forum The ID of the forum to obtain a topic list for.
# @param count The number of topics ids to return, if not specified defaults to 10. # @param count The number of topics ids to return, if not specified defaults to 10.
# if set to 0, all post ids are returned. # if set to 0, all post ids are returned.
# @param offset The number of posts to skip, if not specified defaults to 0. This is # @param offset The number of posts to skip, if not specified defaults to 0. This is
# ignored if count is set to 0. # ignored if count is set to 0.
# @param sort_by_last If true, posts are sorted by the last reply time rather # @param sort_by_last If true, posts are sorted by the last reply time rather
# than the default creation time order (note that this must # than the default creation time order (note that this must
# be true to generate the same listing phpBB3 shows) # be true to generate the same listing phpBB3 shows)
# @return A reference to an array of topic ids, or undef if no topics are available # @return A reference to an array of topic ids, or undef if no topics are available
# or an error ocurred. # or an error ocurred.
@ -677,7 +678,7 @@ sub get_topic_ids {
# always having announcements at the front. # always having announcements at the front.
my $announceh = $self -> {"dbh"} -> prepare("SELECT topic_id FROM ".$self -> {"prefix"}."topics my $announceh = $self -> {"dbh"} -> prepare("SELECT topic_id FROM ".$self -> {"prefix"}."topics
WHERE forum_id = ? AND topic_type = 2 WHERE forum_id = ? AND topic_type = 2
$order $order
".($fetchall ? "" : "LIMIT $count")); ".($fetchall ? "" : "LIMIT $count"));
$announceh -> execute($forum) $announceh -> execute($forum)
or die "phpBB3::get_topic_ids(): Unable to obtain announcement topic list.\nError was: ".$self -> {"dbh"} -> errstr."\n"; or die "phpBB3::get_topic_ids(): Unable to obtain announcement topic list.\nError was: ".$self -> {"dbh"} -> errstr."\n";
@ -707,7 +708,7 @@ sub get_topic_ids {
} }
} # if($count) { } # if($count) {
# And we're done. Return a reference to the topics array if it has any contents, # And we're done. Return a reference to the topics array if it has any contents,
# undef if it does not. # undef if it does not.
return scalar(@topics) ? \@topics : set_error(""); return scalar(@topics) ? \@topics : set_error("");
@ -717,7 +718,7 @@ sub get_topic_ids {
## @method $ get_topic($topicid) ## @method $ get_topic($topicid)
# Obtain the data for the topic identified by the specified topicid. This will attempt # Obtain the data for the topic identified by the specified topicid. This will attempt
# to locate a topic entry with the specified topicid and return a reference to a hash # to locate a topic entry with the specified topicid and return a reference to a hash
# containing the topic information. # containing the topic information.
# #
# @param topicid The id of the topic to look up. # @param topicid The id of the topic to look up.
# @return A reference to a hash containing the topic data, undef if the topic could # @return A reference to a hash containing the topic data, undef if the topic could
@ -754,7 +755,7 @@ sub get_topic_firstpost {
or return set_error("Unable to locate topic $topicid in the database"); or return set_error("Unable to locate topic $topicid in the database");
# Now we can obtain the first post and user details # Now we can obtain the first post and user details
my $posth = $self -> {"dbh"} -> prepare("SELECT p.*, u.* my $posth = $self -> {"dbh"} -> prepare("SELECT p.*, u.*
FROM ".$self -> {"prefix"}."posts AS p, ".$self -> {"prefix"}."users AS u FROM ".$self -> {"prefix"}."posts AS p, ".$self -> {"prefix"}."users AS u
WHERE p.post_id = ? AND u.user_id = p.poster_id"); WHERE p.post_id = ? AND u.user_id = p.poster_id");
$posth -> execute($topic -> {"topic_first_post_id"}) $posth -> execute($topic -> {"topic_first_post_id"})
@ -774,15 +775,15 @@ sub get_topic_firstpost {
"post_uid" => $post -> {"bbcode_uid"}, "post_uid" => $post -> {"bbcode_uid"},
"poster_username" => $post -> {"username"}, "poster_username" => $post -> {"username"},
"poster_userid" => $post -> {"user_id"}}; "poster_userid" => $post -> {"user_id"}};
# If the user has an avatar, we want to record it. Note that this expects phpBB3 # If the user has an avatar, we want to record it. Note that this expects phpBB3
# to have enforced any restrictions on avatar types. # to have enforced any restrictions on avatar types.
if($post -> {"user_avatar_type"}) { if($post -> {"user_avatar_type"}) {
# width and height should be there regardless of type # width and height should be there regardless of type
$pdata -> {"avatar_width"} = $post -> {"user_avatar_width"}; $pdata -> {"avatar_width"} = $post -> {"user_avatar_width"};
$pdata -> {"avatar_height"} = $post -> {"user_avatar_height"}; $pdata -> {"avatar_height"} = $post -> {"user_avatar_height"};
# type 1 is uploaded # type 1 is uploaded
if($post -> {"user_avatar_type"} == 1) { if($post -> {"user_avatar_type"} == 1) {
$pdata -> {"avatar_url"} = $self -> get_config("server_protocol"). $pdata -> {"avatar_url"} = $self -> get_config("server_protocol").
$self -> get_config("server_name"). $self -> get_config("server_name").
@ -792,7 +793,7 @@ sub get_topic_firstpost {
# type 2 avatars are remote linked, so the url should be usable as-is # type 2 avatars are remote linked, so the url should be usable as-is
} elsif($post -> {"user_avatar_type"} == 2) { } elsif($post -> {"user_avatar_type"} == 2) {
$pdata -> {"avatar_url"} = $post -> {"user_avatar"}; $pdata -> {"avatar_url"} = $post -> {"user_avatar"};
# type 3 avatars are gallery avatars # type 3 avatars are gallery avatars
} elsif($post -> {"user_avatar_type"} == 3) { } elsif($post -> {"user_avatar_type"} == 3) {
$pdata -> {"avatar_url"} = $self -> get_config("server_protocol"). $pdata -> {"avatar_url"} = $self -> get_config("server_protocol").
@ -863,14 +864,14 @@ sub get_smilie_url {
## @method $ get_config($name, $default) ## @method $ get_config($name, $default)
# Obtain the value for the specified phpBB3 configuration variable. This will # Obtain the value for the specified phpBB3 configuration variable. This will
# return the value for the specified configuration variable if it is found. If # return the value for the specified configuration variable if it is found. If
# it is not found, but default is specified, the default is returned, otherwise # it is not found, but default is specified, the default is returned, otherwise
# this returns undef. # this returns undef.
# #
# @param name The name of the variable to obtain the value for # @param name The name of the variable to obtain the value for
# @param default An optional default value to return if the named variable can not be found # @param default An optional default value to return if the named variable can not be found
# @return The value for the named variable, or the default or undef if the # @return The value for the named variable, or the default or undef if the
# variable is not present. # variable is not present.
sub get_config { sub get_config {
my $self = shift; my $self = shift;
@ -890,7 +891,7 @@ sub get_config {
# Otherwise, return the default or undef # Otherwise, return the default or undef
return $default; return $default;
} }
## @method $ unique_id($extra) ## @method $ unique_id($extra)
# Generate a unique ID that can be used with phpBB3 tables. # Generate a unique ID that can be used with phpBB3 tables.
@ -915,8 +916,8 @@ sub unique_id {
# by the php date() function into something that can be passed to strftime to get the same # by the php date() function into something that can be passed to strftime to get the same
# result. Note that the following php date() format options are not supported and will be # result. Note that the following php date() format options are not supported and will be
# replaced with the empty string: S, t, L, B, u, I, O, and T. The following options are # replaced with the empty string: S, t, L, B, u, I, O, and T. The following options are
# partially supported but the resulting strings are not identical: n (will generate the # partially supported but the resulting strings are not identical: n (will generate the
# same output as m), g (hour has a leading space instead of zero), and G (hour has a # same output as m), g (hour has a leading space instead of zero), and G (hour has a
# leading space instead of zero) # leading space instead of zero)
# #
# @param format The php date() format string to convert. # @param format The php date() format string to convert.
@ -960,13 +961,13 @@ sub set_error {
# Seriously internal stuff # Seriously internal stuff
# The following functions have been ported wholesale from the phpBB3 'includes/functions.php' # The following functions have been ported wholesale from the phpBB3 'includes/functions.php'
# The port has been done with minimal regard for perlification, and it almost certainly # The port has been done with minimal regard for perlification, and it almost certainly
# could be implemented in a far more efficient and perl-friendly fashion. # could be implemented in a far more efficient and perl-friendly fashion.
# #
# Beware, voodoo programming follows. # Beware, voodoo programming follows.
## @fn $ _hash_encode64($input, $count, $itoa64) ## @fn $ _hash_encode64($input, $count, $itoa64)
# Convert a number into an encoded string form. # Convert a number into an encoded string form.
# #
# @param input The number to encode. # @param input The number to encode.
# @param count The number of characters in the number to convert. # @param count The number of characters in the number to convert.
@ -977,13 +978,13 @@ sub _hash_encode64 {
my $output = ''; my $output = '';
my $i = 0; my $i = 0;
while($i < $count) { while($i < $count) {
my $value = ord(substr($input, $i++, 1)); my $value = ord(substr($input, $i++, 1));
$output .= substr($itoa64, $value & 0x3F, 1); $output .= substr($itoa64, $value & 0x3F, 1);
$value |= ord(substr($input, $i, 1)) << 8 if($i < $count); $value |= ord(substr($input, $i, 1)) << 8 if($i < $count);
$output .= substr($itoa64, ($value >> 6) & 0x3F, 1); $output .= substr($itoa64, ($value >> 6) & 0x3F, 1);
last if($i++ >= $count); last if($i++ >= $count);
@ -991,7 +992,7 @@ sub _hash_encode64 {
$output .= substr($itoa64, ($value >> 12) & 0x3F, 1); $output .= substr($itoa64, ($value >> 12) & 0x3F, 1);
last if($i++ >= $count); last if($i++ >= $count);
$output .= substr($itoa64, ($value >> 18) & 0x3F, 1); $output .= substr($itoa64, ($value >> 18) & 0x3F, 1);
} }
@ -1011,7 +1012,7 @@ sub _hash_crypt_private {
my ($password, $setting, $itoa64) = @_; my ($password, $setting, $itoa64) = @_;
my $output = '*'; my $output = '*';
return $output if(substr($setting, 0, 3) ne '$H$'); return $output if(substr($setting, 0, 3) ne '$H$');
my $count_log2 = index($itoa64, substr($setting, 3, 1)); my $count_log2 = index($itoa64, substr($setting, 3, 1));
@ -1036,9 +1037,9 @@ sub _hash_crypt_private {
## @fn $ _check_hash($password, $hash) ## @fn $ _check_hash($password, $hash)
# Determine whether the specified password hashes to the same string as the provided hash. # Determine whether the specified password hashes to the same string as the provided hash.
# This checks whether the plain-text password, and the previously generated hash, are # This checks whether the plain-text password, and the previously generated hash, are
# actually representing the same string by hashing the plain-text password and comparing # actually representing the same string by hashing the plain-text password and comparing
# it to the specified hash. This function can handle phpBB3 (salted md5 hash) and phpBB2 # it to the specified hash. This function can handle phpBB3 (salted md5 hash) and phpBB2
# (straight md5 hash) hashes and chooses the appropriate algorithm based on the length of # (straight md5 hash) hashes and chooses the appropriate algorithm based on the length of
# the hash string: if it is 34 characters, it is assumed to be a phpBB3 hash, otherwise it # the hash string: if it is 34 characters, it is assumed to be a phpBB3 hash, otherwise it
# is assumed to be a hex encoded 32 character string. # is assumed to be a hex encoded 32 character string.