jim.shamlin.com

Functions for Access Administration

This document contains code for administering user accounts to a directory protected by HTTP access restrictions on UNIX/Apache servers. More detailed information about password protecting directories is presented elsewhere - this is some code that will facilitate maintaining individual user accounts ... which is advisable if you develop a site for a client and don't want to get a phone call every time they need to set up a new user, change a password, etc.


Prerequisites

This page documents some useful functions, not a complete program or a user-management system. I've got a few scripts for that, but haven't "productized" them just yet - so for now, it's here to snag and adapt.

Configuration Variables

In the script, use the following configuration variables:

$htaccess = "/path/.htaccess";
$htpasswd = "/path/.htpasswd";

It should be self-evident what those variables are meant to contain.

Common Functionality

Also, this leverages some of the common functionality in my script template - such as:

They're necessary, but not germane to the present topic.


The HTML Form

An HTML form that includes all user inputs that would be needed for any of the functions below is ...

<FORM METHOD="POST" ACTION="script.pl">
Username:          <INPUT TYPE="TEXT" NAME="un" SIZE="12">
Password:          <INPUT TYPE="PASSWORD" NAME="pa" SIZE="12">
Verify password:   <INPUT TYPE="PASSWORD" NAME="pb" SIZE="12">
</FORM>

Naturally, that's not formatted, and not all variables are needed for some operations (and for the groups, it would be better to dynamically build a selector than use free text). Once again, it's for illustration - you should be able to figure out what to include and when to include it.


Hacking .htpasswd

The following function will hack the .htpasswd file to add, change, or remove a password from the file. In the latter case, you should also remove the username from the .htaccess and/or .htgroup files.

Call this function as &hPassword('action') where the 'action' is either 'add', 'change', or 'remove'.

sub hPassword{
  if ($_[0] eq 'add'){
     $pw = &checkPass;
     open (WPFILE, ">>$htpasswd") || &scriptError(1003);
     print WPFILE "$in{'un'}:$pw\n";
     close (WPFILE);
     }elsif(($_[0] eq 'change') ||  ($_[0] eq 'remove')){
     $pw = &checkPass;
     $dd = 0; $ng = "";
     open (RPFILE, "$htpasswd") || &scriptError(1004);
     while (<RPFILE>){
          chomp ($_);
          @b = split(':',$_);
          if ($b[0] eq $in{'un'}){
             $ng .= "$b[0]:$pw\n" if ($_[0] eq 'change');
             $dd = 1;
             }else{
             $ng .= "$b[0]:$b[1]\n";
             }
          }
     close(RPFILE);
     &scriptError(1006) unless ($dd);
     open (WPFILE, ">$htpasswd") || &scriptError(1005);
     print WPFILE "$ng";
     close(WPFILE);
     }else{
     &scriptError(1002);
     }
  }

This function uses a second function to ensures that the password was entered the same way twice, contains at least six characters, and contains only letters and numbers, and return an encrypted password if all tests are passed.

sub checkpass{
  &scriptError(1001) unless ($in{'pa'} eq $in{'pp'});
  &scriptError(1001) if (length($in{'pa'}) < 6);
  &scriptError(1001) unless ($in{'pa'} =~ /\A[a-zA-z0-9]+\Z/);
  srand;
  @saltchars = ('a'..'z','A'..'Z',0..9);
  $salt = $saltchars[int(rand($#saltchars+1))] . $saltchars[int(rand($#saltchars+1))];
  return crypt($in{'p'},$salt);
  }

Some errors that could be thrown:


Hacking .htaccess

The following function will hack the .htaccess file to add or remove a username. It should not be necessary to "change" a username - in those instances, you should delete the old account and create a new one.

Call this function as &hAccount('action') where the 'action' is either 'add' or 'remove'.

sub hAccount{
  &scriptError(1007) unless ( ($_[0] eq 'add') || ($_[0] eq 'remove') );
  $dd = 0; $ng = "";
  open (RUFILE, "$htaccess") || &scriptError(1008);
  while (<RUFILE>){
     if (($_[0] eq 'add') && ($_ =~ /\A<\/limit>\n\Z/i)){
        $ng .= "require $in{'un'}\n";
        $dd = 1;
        }
     if (($_[0] eq 'remove') && ($_ =~ /\Arequire $in{'un'}\n\Z/) ){
        $dd = 1;
        }else{
        $ng .= "$_\n";
        }
     }
  close (RUFILE);
  &scriptError(1010) unless ($dd);
  open (WUFILE, ">$htaccess") || &scriptError(1009);
  print WUFILE "$ng";
  close(WUFILE);
  }

Some errors that could be thrown:

When removing an account, you will also want to run &hPassword('remove') to remove the user's password from that file (to prevent having a password file full of junk from defunct user accounts). If there is only one .htaccess file, you can add a call to remove the password to this function.

When adding an account, you should run a separate function to ensure that the username is not already in use, and run &hPassword('add') before running this function, as there's no sense adding them to the access file if the password is unacceptable.


What about .htgroup?

Things tend to be more complicated when managing users in groups, as there are often a number of different groups to which a user might belong, and you have to be finicky about getting the group names just right and making sure a user is added to and/or removed from each group, etc. Plus, you may need additional functionality to manage the groups themselves.

And so, it's doubtful a generic function will quite do the trick, but I've provided one just the same as a starting point, based on the unlikely situation where a user can only be added to or removed from one group (it can be adapted as needed, but the adaptation required will vary greatly).

You'll need a couple more config variables:

$htgroup = "/path/.htgroup";
$group = "group_name";

Once those are in place, you can call this function as &hGroup('action') where the 'action' is either 'add' or 'remove'.

sub hGroup{
  &scriptError(1011) unless ( ($_[0] eq 'add') || ($_[0] eq 'remove') );
  $dd = 0; $de = 0; $ng = "";
  open (RGFILE, "$htgroup") || &scriptError(1008);
  while (<RGFILE>){
    if ($_ =~ $group){
       $de = 1;
       if (($_ =~ /\b$in{'un'}\b/) || ($_[0] eq 'remove'));
          $_ =~ s/\s+$in{'un'}//;
          $dd = 1;
          }
       $_ =~ s/\n/ $in{'un'}\n/ if ($_[0] eq 'add');
       $ng .= $_;
       }
  close (RGFILE);
  &scriptError(1014) unless ($de);
  &scriptError(1015) unless ($dd);
  open (WGFILE, ">$htgroup") || &scriptError(1009);
  print WGFILE "$ng";
  close(WGFILE);
  }

Some errors that could be thrown:

When removing a user from a group, you will also want to test to determine if the user exists in any other group - and if not, then run &hPassword('remove') to remove the user's password from that file (to clean up junk).

When adding a user to his first group, you should ensure the username is unique, then run &hPassword('add') before running this function, as there's no sense adding them to the group file if the password is unacceptable.


Ensuring a Unique Username

Before adding a user to the access or group file, you'll need to ensure that the username is acceptable and unique. Here's some code for doing that:

# htaccess file
open (RUFILE, "$htaccess") || &scriptError(11);
while (<RUFILE>){
  next unless ($_ =~ /require (.*)\n/);
  @b = split(/\s+/,$_);
  foreach $b (@b){ $taken{$b} = 1; }
  }
close (RUFILE);
# htgroup file
open (RGFILE, "$htgroup") || &scriptError(11);
while (<RGFILE>){
  next unless ($_ =~ /.*: (.*)\n/);
  @b = split(/\s+/,$_);
  foreach $b (@b){ $taken{$b} = 1; }
  }
close (RGFILE);
# both files
&scriptError(10) if ($taken{$in{'un'}); 

After running this, a script error 10 will be thrown if the username is not unique or a script error error of 11 will be thrown if an error was encountered while trying to read a file. The code above covers both .htaccess and .htgrpoup files - strip out anything you don't need.


And finally

This is online for my own use and reference, but feel to snag it if you think it would be useful. It's a trifle and I don't expect to be credited or compensated in any way ... but nor does it come with any sort of guarantee.