Files
Autoindex-2.2.4/classes/Htaccess.php

508 lines
12 KiB
PHP
Raw Normal View History

2019-09-28 04:55:48 +02:00
<?php
/**
* @package AutoIndex
*
* @copyright Copyright (C) 2002-2007 Justin Hagstrom
* @license http://www.gnu.org/licenses/gpl.html GNU General Public License (GPL)
*
* @link http://autoindex.sourceforge.net
*/
/*
AutoIndex PHP Script 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.
AutoIndex PHP Script 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
if (!defined('IN_AUTOINDEX') || !IN_AUTOINDEX)
{
die();
}
/**
* Parses .htaccess files and imports their settings to AutoIndex.
*
* These Apache directives are supported:
* - <Directory>
* - <Limit>
* - <IfDefine>
* - AddDescription
* - IndexIgnore
* - Include
* - Order
* - Deny from
* - Allow from
* - AuthUserFile
* - AuthName
* - Require user
*
* These password formats are supported for .htpasswd file:
* - MD5
* - SHA-1
* - Crypt
* - Apache's Custom MD5 Crypt
*
* @author Justin Hagstrom <JustinHagstrom@yahoo.com>
* @version 1.0.1 (January 6, 2007)
* @package AutoIndex
*/
class Htaccess
{
/**
* @var string "AuthName" setting
*/
private $auth_name;
/**
* @var string "AuthUserFile" setting
*/
private $auth_user_file;
/**
* @var array "Require user" setting
*/
private $auth_required_users;
/**
* @var string "Order" setting
*/
private $order;
/**
* @var array "Allow from" setting
*/
private $allow_list;
/**
* @var array "Deny from" setting
*/
private $deny_list;
/**
* Converts hexadecimal to binary.
*
* @param string $hex
* @return string
*/
private static function hex2bin($hex)
{
$bin = '';
$ln = strlen($hex);
for($i = 0; $i < $ln; $i += 2)
{
$bin .= chr(hexdec($hex{$i} . $hex{$i+1}));
}
return $bin;
}
/**
* Return the number of count from the value using base conversion.
*
* @param int $value
* @param int $count
* @return int
*/
private static function to64($value, $count)
{
static $root = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
$result = '';
while(--$count)
{
$result .= $root[$value & 0x3f];
$value >>= 6;
}
return $result;
}
/**
* Implementation of Apache's Custom MD5 Crypt.
*
* @param string $plain The plaintext password
* @param string $salt The salt
* @return string The hashed password
*/
private static function md5_crypt($plain, $salt)
{
$length = strlen($plain);
$context = $plain . '$apr1$' . $salt;
$binary = self::hex2bin(md5($plain . $salt . $plain));
for ($i = $length; $i > 0; $i -= 16)
{
$context .= substr($binary, 0, min(16, $i));
}
for ( $i = $length; $i > 0; $i >>= 1)
{
$context .= ($i & 1) ? chr(0) : $plain[0];
}
$binary = self::hex2bin(md5($context));
for ($i = 0; $i < 1000; $i++)
{
$new = ($i & 1) ? $plain : substr($binary, 0, 16);
if ($i % 3)
{
$new .= $salt;
}
if ($i % 7)
{
$new .= $plain;
}
$new .= (($i & 1) ? substr($binary, 0, 16) : $plain);
$binary = self::hex2bin(md5($new));
}
$p = array();
for ($i = 0; $i < 5; $i++)
{
$k = $i + 6;
$j = $i + 12;
if ($j == 16)
{
$j = 5;
}
$p[] = self::to64(
(ord($binary[$i]) << 16) |
(ord($binary[$k]) << 8) |
(ord($binary[$j])), 5
);
}
return '$apr1$' . $salt . '$' . implode($p) . self::to64(ord($binary[11]), 3);
}
/**
* Tests if $test matches $target.
*
* @param string $test
* @param string $target
* @return bool True if $test matches $target
*/
private static function matches($test, $target)
{
static $replace = array(
'\*' => '.*',
'\+' => '.+',
'\?' => '.?');
return (bool)preg_match('/^' . strtr(preg_quote($test, '/'), $replace) . '$/i', $target);
}
/**
* Checks if AuthName and AuthUserFile are set, and then prompts for a
* username and password.
*/
private function check_auth()
{
if ($this -> auth_user_file == '')
{
return;
}
if ($this -> auth_name == '')
{
$this -> auth_name = '"Directory access restricted by AutoIndex"';
}
$validated = false;
if (isset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']))
{
$file = @file($this -> auth_user_file);
if ($file === false)
{
$_GET['dir'] = '';
throw new ExceptionDisplay('Cannot open .htpasswd file.
<br /><em>' . htmlentities($this -> auth_user_file) . '</em>');
}
if ($this -> auth_required_users === array() || DirectoryList::match_in_array($_SERVER['PHP_AUTH_USER'], $this -> auth_required_users))
{
foreach ($file as $account)
{
$parts = explode(':', trim($account));
if (count($parts) < 2 || $_SERVER['PHP_AUTH_USER'] != $parts[0])
{
continue;
}
if (isset($parts[2]))
//MD5 hash format with realm
{
$parts[1] = $parts[2];
}
switch (strlen($parts[1]))
{
case 13:
//Crypt hash format
{
$validated = (crypt($_SERVER['PHP_AUTH_PW'], substr($parts[1], 0, 2)) == $parts[1]);
break 2;
}
case 32:
//MD5 hash format
{
$validated = (md5($_SERVER['PHP_AUTH_PW']) == $parts[1]);
break 2;
}
case 37:
//Apache's MD5 Crypt hash format
{
$salt = explode('$', $parts[1]);
$validated = (self::md5_crypt($_SERVER['PHP_AUTH_PW'], $salt[2]) == $parts[1]);
break 2;
}
case 40:
//SHA-1 hash format
{
$validated = (sha1($_SERVER['PHP_AUTH_PW']) == $parts[1]);
break 2;
}
}
}
}
sleep(1);
}
if (!$validated)
{
header('WWW-Authenticate: Basic realm=' . $this -> auth_name);
header('HTTP/1.0 401 Authorization Required');
$_GET['dir'] = '';
throw new ExceptionDisplay('A username and password are required to access this directory.');
}
}
/**
* Checks if the user's IP or hostname is either allowed or denied.
*/
private function check_deny()
{
global $ip, $host, $words;
if ($this -> order === 'allow,deny')
{
if (!DirectoryList::match_in_array($host, $this -> allow_list)
&& !DirectoryList::match_in_array($ip, $this -> allow_list))
{
$_GET['dir'] = '';
throw new ExceptionDisplay($words -> __get('the administrator has blocked your ip address or hostname') . '.');
}
}
else if (DirectoryList::match_in_array($ip, $this -> deny_list)
|| DirectoryList::match_in_array($host, $this -> deny_list))
{
$_GET['dir'] = '';
throw new ExceptionDisplay($words -> __get('the administrator has blocked your ip address or hostname') . '.');
}
}
/**
* @param string $file The .htaccess file (name and path) to parse
*/
private function parse($file)
{
$data = @file($file);
if ($data === false)
{
return;
}
$conditional_directory = '';
$other_conditional = false;
foreach ($data as $line)
{
$line = trim($line);
if ($line == '')
{
continue;
}
if ($line{0} == '<')
{
if (preg_match('#^</\s*directory.*?>#i', $line))
{
$conditional_directory = '';
}
else if (preg_match('#^<\s*directory\s+\"?(.+?)\"?\s*>#i', $line, $matches))
{
$conditional_directory = $matches[1];
}
else if (preg_match('#^</?\s*limit.*?>#i', $line))
{
//ignore <Limit> tags
continue;
}
else if (preg_match('#^</\s*ifdefine.*?>#i', $line))
{
$conditional_defined = '';
}
else if (preg_match('#^<\s*ifdefine\s+(.+?)\s*>#i', $line, $matches))
{
$conditional_defined = $matches[1];
}
else if (isset($line{1}))
{
$other_conditional = ($line{1} != '/');
}
continue;
}
global $dir;
if ($other_conditional || $conditional_directory != '' && !self::matches($conditional_directory, $dir))
//deal with <Directory> or an unknown < > tag
{
continue;
}
if ($conditional_defined != '')
//deal with <IfDefine>
{
$conditional_defined = strtoupper($conditional_defined);
if ($conditional_defined{0} === '!')
{
$conditional_defined = substr($conditional_defined, 1);
if (defined($conditional_defined) && constant($conditional_defined))
{
continue;
}
}
else if (!defined($conditional_defined) || !constant($conditional_defined))
{
continue;
}
}
$parts = preg_split('#\s#', $line, -1, PREG_SPLIT_NO_EMPTY);
switch (strtolower($parts[0]))
{
case 'indexignore':
{
global $hidden_files;
for ($i = 1; $i < count($parts); $i++)
{
$hidden_files[] = $parts[$i];
}
break;
}
case 'include':
{
if (isset($parts[1]) && @is_file($parts[1]) && @is_readable($parts[1]))
{
self::parse($parts[1]);
}
break;
}
case 'allow':
{
if (isset($parts[1]) && strtolower($parts[1]) === 'from')
{
for ($i = 2; $i < count($parts); $i++)
{
foreach (explode(',', $parts[$i]) as $ip)
{
if (strtolower($ip) === 'all')
{
$this -> allow_list = array('*');
}
else
{
$this -> allow_list[] = $ip;
}
}
}
}
break;
}
case 'deny':
{
if (isset($parts[1]) && strtolower($parts[1]) === 'from')
{
for ($i = 2; $i < count($parts); $i++)
{
foreach (explode(',', $parts[$i]) as $ip)
{
if (strtolower($ip) === 'all')
{
$this -> deny_list = array('*');
}
else
{
$this -> deny_list[] = $ip;
}
}
}
}
break;
}
case 'adddescription':
{
global $descriptions;
if (!isset($descriptions))
{
$descriptions = new ConfigData(false);
}
for ($i = 1; isset($parts[$i], $parts[$i+1]); $i += 2)
{
$descriptions -> set($parts[$i], $parts[$i+1]);
}
break;
}
case 'authuserfile':
{
if (isset($parts[1]))
{
$this -> auth_user_file = str_replace('"', '', implode(' ', array_slice($parts, 1)));
}
break;
}
case 'authname':
{
if (isset($parts[1]))
{
$this -> auth_name = implode(' ', array_slice($parts, 1));
}
break;
}
case 'order':
{
if (isset($parts[1]) && (strtolower($parts[1]) === 'allow,deny' || strtolower($parts[1]) === 'mutual-failure'))
{
$this -> order = 'allow,deny';
}
}
case 'require':
{
if (isset($parts[1]) && strtolower($parts[1]) === 'user')
{
for ($i = 2; $i < count($parts); $i++)
{
$this -> auth_required_users[] = $parts[$i];
}
}
break;
}
}
}
}
/**
* @param string $dir The deepest folder to parse for .htaccess files
* @param string $filename The name of the files to look for
*/
public function __construct($dir, $filename = '.htaccess')
{
$this -> auth_name = $this -> auth_user_file = '';
$this -> auth_required_users = $this -> allow_list = $this -> deny_list = array();
$this -> order = 'deny,allow';
if (DirItem::get_parent_dir($dir) != '')
//recurse into parent directories
{
new Htaccess(DirItem::get_parent_dir($dir));
}
$dir = Item::make_sure_slash($dir);
$file = $dir . $filename;
if (@is_file($file) && @is_readable($file))
{
$this -> parse($dir . $filename);
$this -> check_deny();
$this -> check_auth();
}
}
}
?>