<?php
/*************************************************************************
Filename: htpasswd.php
Date: 01.27.04
Author: John Urbanek

Purpose: Take a username and password and generate a .htpasswd entry.
Spit the entry back onto the screen in encrypted form.

 Extra Info
 Editor: Vim
 no cindent
 Termwidth: 132 char
 Tabstop: 3

register_globals requirement: None (Preferably Off for security)
magic_quotes_gpc requirement: None

It is important to note that if the page is not displayed securely
(using SSL) that the initial password will be transferred plaintext
through the HTTP POST mechanism.  Any party in between could intercept
this traffic.

Arguments are checked by check_arg before they are sent to the function
to execute them on the local system.  Before the arguments are sent to
the system (after they are checked by check_arg) they are again filtered
through another checking mechanism: escapeshellcmd() 

In addition, before being sent to escapeshellcmd(), they are also
stripped of any whitespace (any at all).

That is kind of confusing, but I am not rewriting that comment.
Here is a clearer rundown.

The order of processing goes like so (from first to last):

    1.  check_arg() (Check the length and for ".." and "-")
    2.  White space removal (courtesy of str_replace() )
    3.  escapeshellcmd()

Please don't gripe about the HTML formatting.  I code, I don't
design "web sites."

UPDATE: 02.19.04 John Urbanek

Changed any short tags (<?) usually used in quick printing (<?=) to
use the more portable <?php echo $var ?> scheme instead.

Added a reset button to the form.

TODO: Replace parts of check_arg() with PHP's ereg() and regex.

(C) Copyleft John Urbanek 2004

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

*************************************************************************/

/****************************GLOBALS*************************************/

//Maximum length of an argument, to prevent against shell code
$MAX_ARG_LENGTH 20;

//Path to "htpasswd," including the name of the binary (htpasswd)
$PATH_TO_HTPASSWD "/usr/local/apache/bin/htpasswd";

//Default switches (options) to be enabled
$DEFAULT_SWITCHES "-nb";
// -n --> Do not create new file, output to stdout
// -b --> Take password from commandline, non-interactive (what we want)

/****************************GLOBALS*************************************/
?>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
   <title>htpasswd Entry Generator</title>
   <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
</head>
<body bgcolor = "#E5E5E5">

<?php
//Make sure the form has been submitted before processing
if( isset( $_POST['username'] ) && isset( $_POST['password'] ) 
    && !empty( 
$_POST['username'] ) && !empty( $_POST['password'] ) )
{
    
//Make sure args pass check_arg() before further processing
    
if( check_arg$_POST['username'] ) && check_arg$_POST['password'] ) )
    {
        print( 
"The .htpasswd entry is below.  Assuming everything went correctly \n" );
        print( 
"you should be able to copy and paste.  Note: This script while secure, \n" );
        print( 
"is not foolproof.<br><br>\n" );
        
        
//Formulate the complete command before sending
        //The str_replace() nonsense is just there to remove any whitespace, which in addition
        //to having real value is also an added security measure argument wise.
        
$cmd $GLOBALS['PATH_TO_HTPASSWD'] . " " $GLOBALS['DEFAULT_SWITCHES'] . " "
        
str_replace" """$_POST['username'] ) . " " str_replace" """$_POST['password'] );

        
//One last security check ( escapeshellcmd() ), run the command, print the output.
        //exec() was used instead of system() and passthru() because we do not need ALL (there shouldn't be much
        //of the output to be sent to the browser.  htpasswd should only output a single line of output when
        //it has successfully created the u/p pair, no more.  If it is outputting more, either they have changed
        //the binary and broke many scripts or something has gone wrong, in which case we do not need to give
        //the [potential] attacker any more information.  exec() only returns the last (in our case, the only)
        //line of output.  Goodie.
        
print( "<pre>" );
        print( 
execescapeshellcmd$cmd ) ) );     
        print( 
"</pre><br>\n" );
        print( 
"<hr>" );
    }

    else
        print( 
"<br>An error has occurred.<br>\n" );
}

function 
check_arg$arg )
{
   
//Check for extra long strings (sign of shellcode, etc)

   
if( strlen$arg) > $GLOBALS["MAX_ARG_LENGTH"] )
      die( 
"<br>ERROR: Argument too long<br>" );

   
//Check for ".." and "-" in the arg.  
    //".." for possibility of accessing parent directory
    //'-' for possibility of adding extra arguments

   
else if( stristr$arg".." ) != "" || stristr$arg"-" ) != "" )
      die( 
"<br>ERROR: Argument contains naughty strings<br>" );

   
//If execution makes it this far, arg is assumed to be "legal"

   
else
      return 
true;
}

?>

Fill out the form to generate the .htpasswd entry.<br>
The .htpasswd will be printed out on the page once it is generated.<br><br>

<form action="<?php echo $_SERVER['PHP_SELF'?>" method="post">
    Username:    <input type="text" name="username" maxlength="<?php echo $GLOBALS['MAX_ARG_LENGTH'?>" size="15"><br>
    Password:    <input type="password" name="password" maxlength="<?php echo $GLOBALS['MAX_ARG_LENGTH'?>" size="15"><br>
    <br><input type="reset" name="reset" value="Reset"><input type="submit" name="submit" value="Generate">
</form>

<i>DISCLAIMER:  No logs are kept of any username<br>
and password that enter this host via this script.<br>
If you are not connected to this host securely over<br>
SSL (or tunneling traffic through a secure route),<br>
then the username and password will be sent across<br>
the Internet between the webserver and your browser<br>
in plaintext using the HTTP POST mechanism.<br> </i>
</body>
</html>