<?php 
/*
    Copyright (C) 2008 Aymeric AUGUSTIN

    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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/


/*
    HTPASSWD GENERATION
    
    This script generates htpasswd entries with the best means available
        - if possible, it calls directly htpasswd
        - otherwise, it uses ad-hoc PHP routines
    
    It requires PHP >= 5.0.0
*/


////// CONFIGURATION

/*
    In the default confguration, the script will try to find the htpasswd 
    program and fallback to its own PHP routines if necessary. Testing this at 
    every execution is quite useless as long as the local configuration does 
    not change, and it consumes resources.
    
    The configuration section enables you disable this behavior and choose 
    how script will proceed.
    
    This may break the script if your local configuration changes.
    Use with caution!
*/

// set this to false to disable auto-configuration and checks
$auto_configuration true;

// when auto-configuration is off,
// set this to the full path to htpasswd if you want to use htpasswd, 
// and to the empty string if you want to use PHP routines.
$htpasswd_binary '';


////// UTILITY FUNCTIONS

global $alphabet_64;
$alphabet_64 
    
'./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';

// creates a random salt of a given length
function salt($length) {
    global 
$alphabet_64;
    
$length intval($length);
    
$salt '';
    for (
$i 0$i $length$i++)
        
$salt .= substr($alphabet_64mt_rand(063), 1);
    return 
$salt;
}

// Apache MD5 is not available directly, we need to recode it
// Source code adapted from PEAR::File_Passwd
function convert_64($value$count) {
    global 
$alphabet_64;
    
$result '';
    while(--
$count) {
        
$result .= $alphabet_64[$value 0x3f];
        
$value >>= 6;
    }
    return 
$result;
}

function 
crypt_apr_md5($password) {
    
$salt salt(8);
    
$length  strlen($password);
    
$context $password '$apr1$' $salt;
    
    
$binary md5($password $salt $passwordtrue);
        for (
$i $length$i 0$i -= 16) {
        
$context .= substr($binary0min(16$i));
    }
    for ( 
$i $length$i 0$i >>= 1) {
        
$context .= ($i 1) ? chr(0) : $password[0];
    }
    
    
$binary md5($contexttrue);
    
    for(
$i 0$i 1000$i++) {
        
$new = ($i 1) ? $password $binary;
        if (
$i 3) {
            
$new .= $salt;
        }
        if (
$i 7) {
            
$new .= $password;
        }
        
$new .= ($i 1) ? $binary $password;
        
$binary md5($newtrue);
    }
    
    
$p = array();
    for (
$i 0$i 5$i++) {
        
$k $i 6;
        
$j $i 12;
        if (
$j == 16) {
            
$j 5;
        }
        
$p[] = convert_64(
            (
ord($binary[$i]) << 16) |
            (
ord($binary[$k]) << 8) |
            (
ord($binary[$j])),
            
5
        
);
    }
    
    return 
'$apr1$' $salt '$'
           
implode($p) . convert_64(ord($binary[11]), 3);
}

// tries to locate the htpasswd binary and tests if it can be executed
// returns the path to the executable or an empty string
function locate_htpasswd() {
    
$htpasswd '';
    
    if (
is_executable('/usr/sbin/htpasswd2')) {
        
$htpasswd '/usr/sbin/htpasswd2';
    } else if (
is_executable('/usr/bin/htpasswd')) {
        
$htpasswd '/usr/bin/htpasswd';
    } else {
        
$output2 = array();
        
$return2 null;
        
exec('which htpasswd2', &$output2, &$return2);
        
$output = array();
        
$return null;
        
exec('which htpasswd', &$output, &$return);
        if (!
$return2)
            
$htpasswd $output2[0];
        else if (!
$return)
            
$htpasswd $output[0];
    }
    
    if (
strpos($htpasswd'htpasswd')) {
        
$output = array();
        
$return null;
        
exec($htpasswd ' -nbp user pass', &$output, &$return);
        if (
$return || $output[0] != 'user:pass')
            
$htpasswd '';
    } else {
        
$htpasswd '';
    }
    
    return 
$htpasswd;
}


//// SCRIPT

if ($auto_configuration)
    
$htpasswd_binary locate_htpasswd();
// now either $htpasswd_binary contains the path of an executable htpasswd
// or it is empty

// process POST arguments
if (get_magic_quotes_gpc()) {
    
$username stripslashes($_POST['username']);
    
$password stripslashes($_POST['password']);
    
$mode stripslashes($_POST['mode']);
} else {
    
$username $_POST['username'];
    
$password $_POST['password'];
    
$mode $_POST['mode'];
}

// basic checks on username and password
$valid true;
$valid $valid && $username;
$valid $valid && !strpos($username':');
$valid $valid && $password;

// a result is generated only if the arguments are valid
if ($valid) {

    
// htpasswd call generation mode
    
if ($htpasswd_binary) {
    
        
// escape shell arguments
        
if (!ini_get('safe_mode')) { 
            
$username_esc escapeshellarg($username);
            
$password_esc escapeshellarg($password);
        } else {
            
$username_esc $username;
            
$password_esc $password;
        }
        
        
// set flags, depending on encryption mode selected (see man htpasswd)
        
$flags '-bn';
        if (
$mode == 'crypt')
            
$flags .= 'd';
        else if (
$mode == 'md5')
            
$flags .= 'm'
        else if (
$mode == 'sha')
            
$flags .= 's'
        else if (
$mode == 'plain')
            
$flags .= 'p';
        
        
// call htpasswd
        
$output = array();
        
$return null;
        
exec("$htpasswd_binary $flags $username_esc $password_esc",
             &
$output, &$return);
        if (
$return) {
            
// switch to PHP generation mode if htpasswd failed
            // this should not happen (unless htpasswd usage changes)
            
$htpasswd_binary '';
        } else {
            
// we have the result!
            
$htpasswd_line $output[0];
        }
    }
    
    
// PHP routines generation mode
    // call encryption function, depending on encryption mode selected
    
if (!$htpasswd_binary) {
        if (
$mode == 'crypt' && CRYPT_STD_DES == 1)
            
$htpasswd_line $username ':'
                             
crypt($passwordsalt(2));
        else if (
$mode == 'md5')
            
$htpasswd_line $username ':'
                             
crypt_apr_md5($password);
        else if (
$mode == 'sha')
            
$htpasswd_line $username ':'
                             
'{SHA}' base64_encode(sha1($passwordtrue));
        else if (
$mode == 'plain')
            
$htpasswd_line $username ':'
                             
$password;
    }
}


//// PAGE OUTPUT

if (stristr($_SERVER['HTTP_USER_AGENT'], 'MSIE') === false) {
    
header("Content-Type: application/xhtml+xml; charset=us-ascii");
    echo 
'<?xml version="1.0" encoding="us-ascii" ?>'"\n";
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
                      "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">

<head>
<title>Htpasswd generator</title>
<link rel="stylesheet" type="text/css" media="screen" href="/css/clear.css" />
<link rel="icon" type="image/png" href="/images/favicon.png" />
<link rel="index" href="/index.php" />
</head>

<body>

<div id="global">

<div id="content">

<h2>Htpasswd generator</h2>

<h3>Overview</h3>

<p>.htaccess files can be used to restrict access to web pages on an Apache web 
server. A simple access control mode provided by Apache is called <em>basic 
authentication</em>. Usernames and passwords are stored in a file 
called .htpasswd; passwords are encrypted.</p>

<p> This tool generates a line that can be directly added into the .htpasswd for 
a given login and password.</p>

<p>For more info, please refer to the Apache documentation on
<a href="http://httpd.apache.org/docs/2.2/howto/auth.html">authentication, 
authorization and access control</a> and especially <code>mod_auth_basic</code>.
</p>

<h3>Enter username and password</h3>

<form accept-charset="US-ASCII" action="<?php echo htmlentities($_SERVER['REQUEST_URI']); ?>" method="post">

<table style="margin: auto;" summary="uername and password input form">

<tr>
<td style="text-align: right;"><label for="username">Username:</label></td>
<td style="text-align: left;"><input id="username" name="username" size="16" type="text" 
value="<?php echo htmlentities($username); ?>" /></td>
<td style="width: 16em; text-align: right;"><?php 
if (!$username && $_POST['generate'])
    echo 
"<em style=\"color: red;\">Empty username!</em>";
else if (
strpos($username":"))
    echo 
"<em style=\"color: red;\">Username can not contain ':'.</em>";
?></td>
</tr>

<tr>
<td style="text-align: right;"><label for="password">Password:</label></td>
<td style="text-align: left;"><input id="password" name="password" size="16" type="text" 
value="<?php echo htmlentities($password); ?>" /></td>
<td style="width: 16em; text-align: right;"><?php
if (!$password && $_POST['generate'])
    echo 
"<em style=\"color: red;\">Empty password!</em>";
?></td>
</tr>

<tr>
<td style="text-align: right;">Encryption mode:</td>
<td colspan="2">
<input type="radio" name="mode" id="crypt" value="crypt"<?php 
if ($mode == 'crypt' || !$mode) echo ' checked="checked"'?> />
<label for="crypt">crypt (default on Unix, unsupported on Windows)</label><br />
<input type="radio" name="mode" id="md5" value="md5"<?php 
if ($mode == 'md5') echo ' checked="checked"'?> />
<label for="md5">Apache MD5 (default on Windows)</label><br />
<input type="radio" name="mode" id="sha" value="sha"<?php 
if ($mode == 'sha') echo ' checked="checked"'?> />
<label for="sha">SHA</label><br />
<input type="radio" name="mode" id="plain" value="plain"<?php 
if ($mode == 'plain') echo ' checked="checked"'?> />
<label for="plain">Plain text (accepted on Windows)</label>
</td>
</tr>

<tr>
<td colspan="3" style="text-align: center;">
<input type="submit" value="Generate htpasswd line" name="generate"/>
</td>
</tr>

</table>

</form>

<?php if ($valid && $htpasswd_line) { ?>
<h3>Result</h3>

<pre style="font-size: 130%; text-align: center; color: black;">
<?php echo htmlentities($htpasswd_line) . "\n"?>
</pre>

<?php } else if ($valid && !$htpasswd_line) { ?>
<p style="color: red;">An unexpected error occured. We apologize for the 
inconvenience.</p>
<?php ?>
<h3>Remarks</h3>
<ul>
<?php if ($valid && $htpasswd_line && $htpasswd_binary) { ?>
<li>The result was generated by calling directly the htpasswd tool.</li>
<?php } else if ($valid && $htpasswd_line && !$htpasswd_binary) { ?>
<li>The result was generated by custom PHP code, because the htpasswd tool was 
not available.</li>
<?php ?>
<?php 
if ($valid && $htpasswd_line && ($mode == 'crypt' || $mode == 'md5')) { ?>
<li>The output changes every time you refresh this page; this is perfectly 
normal. It is related to the one-way encryption mechanism used.</li>
<?php ?>
<li>When choosing encryption mode, &quot;Unix&quot; means &quot;all platforms 
but Windows, Netware and TPF&quot; and &quot;Windows&quot; means &quot;Windows, 
Netware and TPF&quot;.</li>
<li>You may wonder if it is secure to enter your username and your password in 
this form. I do not log entries, and even if I did, I wouldn't know where you 
use these identifiers. You can also check <a href="/utilities/htpasswd.phps">
the source</a>.</li>
</ul>

</div>

<div id="footer">
<span id="copyright">&copy; 2007 Aymeric Augustin</span>
<a href="http://validator.w3.org/check/referer">
<img src="/images/xhtml11.png" alt="Valid XHTML 1.1!" height="15" width="80"/>
</a>
<a href="http://jigsaw.w3.org/css-validator/check/referer">
<img src="/images/css.png" alt="Valid CSS!" height="15" width="80"/>
</a>
</div>

</div>

</body>

</html>