I have been scripting a shell script to allow me to easily create htaccess users and add them to existing htaccess groups etc.
However, the main part of the script that I am trying to accomplish is to store additional metadata in comments in the htpasswd file. I am trying to store an expiry date in the comments, such that the htpasswd file can be scanned and any users with expiry dates that are in the past can be deleted.
The syntax of the htpasswd file with expiry date metadata is:
username:ABCDEFG (tab) # comment [exp:YYYY-MM-DD]
I am now trying to write the script to scan the htpasswds file and delete expired users. This is what the script needs to do:
Check each line in htpasswds
If the user in htpasswds has no comment, or if the expiry date of the current user is a date that has not yet passed, then skip this user.
Else, delete this user from the htpasswds now.
Scan the htgroups file for all instances of this user and delete them (being weary of comments in the file)
If, after deleting the user from a group, the group is left empty, delete the group (as well as a few lines of comments appearing above the group) from htgroups.
This is my current code to achieve the above steps. I stopped however because I am sure there is a more efficient way to achieve this (Perl, Awk, Sed, ..., I'm not really sure).
# loop through each line of htpasswd
(
while read line; do
# extract the username from the line
line_user=$(echo `echo "$line" | cut -d: -f1`)
[ -n "$VERBOSE" ] && echo "Checking expiry of user '$line'" >&2
# extract the expiry date from the line
line_date=$(echo `echo "$line" | grep -o "\[exp:[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\]" | grep -o "[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}"`)
if [ -n "$line_date" ]; then
EXPIRED=
# break the expiry date into a year, month and date
line_year=$(echo `echo "$line_date" | cut -d- -f1`)
line_month=$(echo `echo "$line_date" | cut -d- -f2`)
line_date=$(echo `echo "$line_date" | cut -d- -f3`)
if [ $line_year -lt $today_year ]; then
EXPIRED=1
elif [ $line_year -eq $today_year ]; then
if [ $line_month -lt $today_month ]; then
EXPIRED=1
elif [ $line_month -eq $today_month ]; then
if [ $line_date -lt $today_date ]; then
EXPIRED=1
fi
fi
fi
if [ -z "$EXPIRED" ]; then
# not expired, output the user back to htpasswds
echo "$line"
else
[ -n "$VERBOSE" ] && echo "User has expired. Deleting..." >&2
echo "Haven't yet implemented deleting user from htgroups. This needs to be done manually!" >&2
# also delete the user from htgroups
#(
# while read line2; do
# #new_group=$(echo `echo "$line2" | grep -o "^[^#].*:*\[ $line_user \]" | grep -o "^[^#].*:*\[$line_user \]" | grep -o "^[^#].*:*\[ $line_user\]"sed 's/^[^#].*:$/'`)
# done < $HTGROUPS
#) > $HTGROUPS.tmp
fi
else
# user doesn't have an expiry date, output the user back to htpasswds
echo "$line"
fi
done < $HTPASSWD
) > $HTPASSWD.tmp
# move temp file
rm -f $HTPASSWD
mv $HTPASSWD.tmp $HTPASSWD
The other (harder) part, however, is to also delete these users from htgroups (I'm guessing this would need to be done before deleting the users from htpasswd)
Sorry, I forget to add about "real examples" - remove all confidential information if there is.
Ok, for trying to write the script this will be enough:
This script should work but I haven't tested it thoroughly.
#!/usr/bin/env perl
use warnings;
use strict;
use constant PASSWD => 'htpasswd';
use constant GROUPS => 'htgroups';
use constant NEW => '.new';
sub usage {
die "Usage: $0 EXPDATE, where EXPDATE is in YYYY-MM-DD format"
}
usage if @ARGV != 1 or $ARGV[0] !~ /\d{4}-\d{2}-\d{2}/;
my $expdate = shift;
my %users;
# don't output expired users from PASSWD to PASSWD.NEW
# and remember them in %users
open my $fpasswd, '<', PASSWD or die $!;
open my $fpasswd_new, '>', PASSWD . NEW or die $!;
while (<$fpasswd>) {
if (/^(\w+):.*\[exp:([\d-]+)\]/) {
if ($2 lt $expdate) {
$users{$1}++;
} else {
print $fpasswd_new $_;
}
} else {
print $fpasswd_new $_;
}
}
close $fpasswd;
close $fpasswd_new;
# remove users from %users from records of GROUPS file
# and don't output the records to GROUPS.NEW where no more users
# records in GROUPS should be separated by blank lines
open my $fgroups, '<', GROUPS or die $!;
open my $fgroups_new, '>', GROUPS . NEW or die $!;
local $/;
$/="";
# not very robust but this may be enough
while (<$fgroups>) {
for my $user (keys %users) {
s/$user//;
}
s/ +/ /g; # here may appear extra spaces so squash them
s/ +$//gm;
print $fgroups_new $_ if !/^[^#]\w+:\s*$/m;
}
close $fgroups;
close $fgroups_new;