80 Managing Apache Passwords


#80 Managing Apache Passwords

One terrific feature of the Apache web server is that it offers built-in support for password-protected directories, even on a shared public server. It's a great way to have private, secure, and limited-access information on your website, whether you have a pay subscription service or you just want to ensure that family pictures are viewed only by family.

Standard configurations require that in the password-protected directory you manage a data file called .htaccess , which specifies the security "zone" name and, most importantly, points to a separate data file, which in turn contains the account name and password pairs that are used to validate access to the directory. Managing this file is not a problem, except that the only tool included with Apache for doing so is the primitive htpasswd program, which is run on the command line. Instead, this script, apm , one of the most complex and sophisticated scripts in this book, offers a password management tool that runs as a CGI script and lets you easily add new accounts, change the passwords on existing accounts, and delete accounts from the access list.

To get started, you will need a properly formatted .htaccess file to control access to the directory it's located within. For demonstration purposes, this file might look like the following:

 $  cat .htaccess  AuthUserFile /web/intuitive/wicked/examples/protected/.htpasswd AuthGroupFile /dev/null AuthName "Sample Protected Directory" AuthType Basic <Limit GET> require valid-user </Limit> 

A separate file, .htpasswd , contains all the account and password pairs. If this file doesn't yet exist, you'll need to create one, but a blank one is fine: Use touch.htpasswd and ensure that it's writable by the user ID that runs Apache itself (probably user nobody ). Then we're ready for the script.

The Code

 #!/bin/sh # apm - Apache Password Manager. Allows the administrator to easily #   manage the addition, update, or deletion of accounts and passwords #   for access to a subdirectory of a typical Apache configuration (where #   the config file is called .htaccess). echo "Content-type: text/html" echo "" echo "<html><title>Apache Password Manager Utility</title><body>" myname="$(basename 
 #!/bin/sh # apm - Apache Password Manager. Allows the administrator to easily # manage the addition, update, or deletion of accounts and passwords # for access to a subdirectory of a typical Apache configuration (where # the config file is called .htaccess). echo "Content-type: text/html" echo "" echo "<html><title>Apache Password Manager Utility</title><body>" myname ="$(basename $0)" temppwfile="/tmp/apm.$$"; trap "/bin/rm -f $temppwfile" 0 footer="apm-footer.html" htaccess=".htaccess" # if you use a /cgi-bin, make sure this points # to the correct .htaccess file! # Modern versions of 'htpasswd' include a -b flag that lets you specify # the password on the command line. If yours can do that, specify it # here, with the '-b' flag: # htpasswd="/usr/local/bin/htpasswd -b" # Otherwise, there's a simple Perl rewrite of this script that is a good # substitute, at http://www.intuitive.com/shellhacks/examples/httpasswd-b.pl htpasswd="/web/intuitive/wicked/examples/protected/htpasswd-b.pl" if [ "$REMOTE_USER" != "admin" -a -s $htpasswd ] ; then echo "Error: you must be user <b>admin</b> to use APM." exit 0 fi # Now get the password filename from the .htaccess file if [ ! -r "$htaccess" ] ; then echo "Error: cannot read $htaccess file in this directory." exit 0 fi passwdfile="$(grep "AuthUserFile" $htaccess  cut -d\ -f2)" if [ ! -r $passwdfile ] ; then echo "Error: can't read password file: can't make updates." exit 0 elif [ ! -w $passwdfile ] ; then echo "Error: can't write to password file: can't update." exit 0 fi echo "<center><h2 style='background:#ccf'>Apache Password Manager</h2>" action="$(echo $QUERY_STRING  cut -c3)" user="$(echo $QUERY_STRINGcut -d\& -f2cut -d= -f2tr '[:upper:]' '[:lower:]')" case "$action" in A ) echo "<h3>Adding New User <u>$user</u></h3>" if [ ! -z "$(grep -E "^${user}:" $passwdfile)" ] ; then echo "Error: user <b>$user</b> already appears in the file." else pass="$(echo $QUERY_STRINGcut -d\& -f3cut -d= -f2)" if [ ! -z "$(echo $pass  tr -d '[[:upper:][:lower:][:digit:]]')" ] then echo "Error: passwords can only contain a-z A-Z 0-9 ($pass)" else $htpasswd $passwdfile $user $pass echo "Added!<br>" fi fi ;; U ) echo "<h3>Updating Password for user <u>$user</u></h3>" if [ -z "$(grep -E "^${user}:" $passwdfile)" ] ; then echo "Error: user <b>$user</b> isn't in the password file?" echo "<pre>";cat $passwdfile;echo "</pre>" echo "searched for &quot;^${user}:&quot; in $passwdfile" else pass="$(echo $QUERY_STRINGcut -d\& -f3cut -d= -f2)" if [ ! -z "$(echo $pass  tr -d '[[:upper:][:lower:][:digit:]]')" ] then echo "Error: passwords can only contain a-z A-Z 0-9 ($pass)" else grep -vE "^${user}:" $passwdfile > $temppwfile mv $temppwfile $passwdfile $htpasswd $passwdfile $user $pass echo "Updated!<br>" fi fi ;; D ) echo "<h3>Deleting User <u>$user</u></h3>" if [ -z "$(grep -E "^${user}:" $passwdfile)" ] ; then echo "Error: user <b>$user</b> isn't in the password file?" elif [ "$user" = "admin" ] ; then echo "Error: you can't delete the 'admin' account." else grep -vE "^${user}:" $passwdfile > $temppwfile mv $temppwfile $passwdfile echo "Deleted!<br>" fi ;; esac # Always list the current users in the password file... echo "<br><br><table border='1' cellspacing='0' width='80%' cellpadding ='3'>" echo "<tr bgcolor ='#cccccc'><th colspan='3'>List " echo "of all current users</td></tr>" oldIFS=$IFS ; IFS=":" # change word split delimiter while read acct pw ; do echo "<tr><th>$acct</th><td align=center><a href=\"$myname?a=D&u=$acct\">" echo "[delete]</a></td></tr>" done < $passwdfile echo "</table>" IFS=$oldIFS # and restore it # Build optionstring with all accounts included optionstring="$(cut -d: -f1 $passwdfile  sed 's/^/<option>/'tr '\n' ' ')" # And output the footer sed -e "s/--myname--/$myname/g" -e "s/--options--/$optionstring/g" < $footer exit 0 
)" temppwfile="/tmp/apm.$$"; trap "/bin/rm -f $temppwfile" 0 footer="apm-footer.html" htaccess=".htaccess" # if you use a /cgi-bin, make sure this points # to the correct .htaccess file! # Modern versions of 'htpasswd' include a -b flag that lets you specify # the password on the command line. If yours can do that, specify it # here, with the '-b' flag: # htpasswd="/usr/local/bin/htpasswd -b" # Otherwise, there's a simple Perl rewrite of this script that is a good # substitute, at http://www.intuitive.com/shellhacks/examples/httpasswd-b.pl htpasswd="/web/intuitive/wicked/examples/protected/htpasswd-b.pl" if [ "$REMOTE_USER" != "admin" -a -s $htpasswd ] ; then echo "Error: you must be user <b>admin</b> to use APM." exit 0 fi # Now get the password filename from the .htaccess file if [ ! -r "$htaccess" ] ; then echo "Error: cannot read $htaccess file in this directory." exit 0 fi passwdfile="$(grep "AuthUserFile" $htaccess cut -d\ -f2)" if [ ! -r $passwdfile ] ; then echo "Error: can't read password file: can't make updates." exit 0 elif [ ! -w $passwdfile ] ; then echo "Error: can't write to password file: can't update." exit 0 fi echo "<center><h2 style='background:#ccf'>Apache Password Manager</h2>" action="$(echo $QUERY_STRING cut -c3)" user="$(echo $QUERY_STRINGcut -d\& -f2cut -d= -f2tr '[:upper:]' '[:lower:]')" case "$action" in A ) echo "<h3>Adding New User <u>$user</u></h3>" if [ ! -z "$(grep -E "^${user}:" $passwdfile)" ] ; then echo "Error: user <b>$user</b> already appears in the file." else pass="$(echo $QUERY_STRINGcut -d\& -f3cut -d= -f2)" if [ ! -z "$(echo $pass tr -d '[[:upper:][:lower:][:digit:]]')" ] then echo "Error: passwords can only contain a-z A-Z 0-9 ($pass)" else $htpasswd $passwdfile $user $pass echo "Added!<br>" fi fi ;; U ) echo "<h3>Updating Password for user <u>$user</u></h3>" if [ -z "$(grep -E "^${user}:" $passwdfile)" ] ; then echo "Error: user <b>$user</b> isn't in the password file?" echo "<pre>";cat $passwdfile;echo "</pre>" echo "searched for &quot;^${user}:&quot; in $passwdfile" else pass="$(echo $QUERY_STRINGcut -d\& -f3cut -d= -f2)" if [ ! -z "$(echo $pass tr -d '[[:upper:][:lower:][:digit:]]')" ] then echo "Error: passwords can only contain a-z A-Z 0-9 ($pass)" else grep -vE "^${user}:" $passwdfile > $temppwfile mv $temppwfile $passwdfile $htpasswd $passwdfile $user $pass echo "Updated!<br>" fi fi ;; D ) echo "<h3>Deleting User <u>$user</u></h3>" if [ -z "$(grep -E "^${user}:" $passwdfile)" ] ; then echo "Error: user <b>$user</b> isn't in the password file?" elif [ "$user" = "admin" ] ; then echo "Error: you can't delete the 'admin' account." else grep -vE "^${user}:" $passwdfile > $temppwfile mv $temppwfile $passwdfile echo "Deleted!<br>" fi ;; esac # Always list the current users in the password file... echo "<br><br><table border='1' cellspacing='0' width='80%' cellpadding='3'>" echo "<tr bgcolor='#cccccc'><th colspan='3'>List " echo "of all current users</td></tr>" oldIFS=$IFS ; IFS=":" # change word split delimiter while read acct pw ; do echo "<tr><th>$acct</th><td align=center><a href=\"$myname?a=D&u=$acct\">" echo "[delete]</a></td></tr>" done < $passwdfile echo "</table>" IFS=$oldIFS # and restore it # Build optionstring with all accounts included optionstring="$(cut -d: -f1 $passwdfile sed 's/^/<option>/'tr '\n' ' ')" # And output the footer sed -e "s/--myname--/$myname/g" -e "s/--options--/$optionstring/g" < $footer exit 0

How It Works

There's a lot working together for this script to function. Not only do you need to have your Apache configuration (or equivalent) correct, but you need to have the correct entries in the .htaccess file and you need an .htpasswd file with ( ideally ) at least an entry for the admin user.

The script itself extracts the htpasswd filename from the .htaccess file and does a variety of tests to sidestep common htpasswd error situations, including an inability for the script to write to the file. It also checks to ensure that the user is logged in as admin if the password file exists and is nonzero in size . All of this occurs before the main block of the script, the case statement.

Processing Changes to .htpasswd

The case statement ascertains which of three possible actions is requested ( A = add a user, U = update a user record, and D = delete a user) and invokes the correct segment of code accordingly . The action and the user account on which to perform the action are specified in the QUERY_STRING variable (sent by the web browser to the server) as a= X &u= Y , where X is the action letter code and Y is the specified username. When a password is being changed or a user is being added, a third argument, p , is needed and sent to the script.

For example, let's say I was adding a new user called joe , with the password knife . This action would result in the following QUERY_STRING being given to the script from the web server:

 a=A&u=joe&p=knife 

The script would unwrap this so that action was A , user was joe , and pass was knife . Then it would ensure that the password contains only valid alphabetic characters in the following test:

 if [ ! -z "$(echo $pass  tr -d '[[:upper:][:lower:][:digit:]]')" ] ; then   echo "Error: passwords can only contain a-z A-Z 0-9 ($pass)" 

Finally, if all was well, it would invoke the htpasswd program to encrypt the password and add the new entry to the .htpasswd file:

 $htpasswd $passwdfile $user $pass 

Listing All User Accounts

In addition to processing requested changes to the .htpasswd file, directly after the case statement this script also produces an HTML table that lists each user in the .htpasswd file, along with a [delete] link.

After producing three lines of HTML output for the heading of the table, the script continues with the interesting code:

 oldIFS=$IFS ; IFS=":"   # change word split delimiter while read acct pw ; do   echo "<tr><th>$acct</th><td align=center><a href=\"$myname?a=D&u=$acct\">"   echo "[delete]</a></td></tr>" done < $passwdfile echo "</table>" IFS=$oldIFS             # and restore it 

This while loop reads the name and password pairs from the .htpasswd file through the trick of changing the input field separator ( IFS ) to a colon (and changing it back when done).

Adding a Footer of Actions to Take

The script also relies on the presence of an HTML file called apm-footer.html that contains quite a bit of code itself, including occurrences of the strings "--myname--" and "--options--", which are replaced by the current name of the CGI script and the list of users, respectively, as the file is output to stdout .

 sed -e "s/--myname--/$myname/g" -e "s/--options--/$optionstring/g" < $footer 

The $myname variable is processed by the CGI engine, which replaces the variable with the actual name of the script. The script itself builds the $optionstring variable from the account name and password pairs in the .htpasswd file:

 optionstring="$(cut -d: -f1 $passwdfile  sed 's/^/<option>/'tr '\n' ' ')" 

And here's the HTML footer file itself, which provides the ability to add a user, update a user's password, and delete a user:

 <!-- footer information for APM system. --> <div style='margin-top: 10px;'> <table border='1' cellpadding='2' cellspacing='0' width="80%">  <tr><th colspan='4' bgcolor='#cccccc'>Password Manager Actions</th></tr>  <tr><td>   <form method="get" action="--myname--">   <table border='0'>     <tr><td><input type='hidden' name="a" value="A">      add user:</td><td><input type='text' name='u' size='10'>     </td></tr><tr><td>      password: </td><td> <input type='text' name='p' size='10'>      <input type='submit' value='+'>     </td></tr>   </table></form> </td><td>   <form method="get" action="--myname--">   <table border='0'>     <tr><td><input type='hidden' name="a" value="U">       update</td><td><select name='u'>--options--</select>     </td></tr><tr><td>       password: </td><td><input type='text' name='p' size='10'>       <input type='submit' value='@'>     </td></tr>   </table></form> </td><td>   <form method="get" action="--myname--"><input type='hidden'     name="a" value="D">delete <select name='u'> --options-- </select>     <input type='submit' value='-'> </form> </td><td>   <form method="get" action="--myname--"><input type='hidden'   name="a" value="L"><input type='submit' value='list all users'>   </form> </td></tr> </table> </div> </body> </html> 

Running the Script

You'll most likely want to have this script in the same directory you're endeavoring to protect with passwords, although you can also put it in your cgi-bin directory: Just tweak the htpasswd value at the beginning of the script as appropriate. You'll also need an .htaccess file defining access permissions and an .htpasswd file that's at least zero bytes and writable, if nothing else.

Very helpful tip  

When you use apm , make sure that the first account you create is admin , so you can use the script upon subsequent invocations! There's a special test in the code that allows you to create the admin account if .htpasswd is empty.

The Result

The result of running the apm script is shown in Figure 9-1. Notice in the screen shot that it not only lists all the accounts, with a delete link for each, but also, in the bottom section, offers options for adding another account, changing the password of an existing account, deleting an account, or listing all the accounts.

click to expand
Figure 9-1: A shell-script-based Apache password management system

Hacking the Script

The Apache htpasswd program offers a nice command-line interface for appending the new account and encrypted password information to the account database, but only one of the two commonly distributed versions of htpasswd supports batch use for scripts (that is, feeding it both an account and password from the command line). It's easy to tell whether your version does: If htpasswd doesn't complain when you try to use the - b flag, you've got the good, more recent version. Otherwise, there's a simple Perl script that offers the same functionality and can be downloaded from http://www.intuitive.com/wicked/examples/htpasswd-b.html and installed.




Wicked Cool Shell Scripts. 101 Scripts for Linux, Mac OS X, and Unix Systems
Wicked Cool Shell Scripts
ISBN: 1593270127
EAN: 2147483647
Year: 2004
Pages: 150
Authors: Dave Taylor

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net