##! /usr/bin/perl -Tw # script to manipulate users, passwords, and permissions for svn # use strict; use CGI qw(:standard); use CGI::Carp qw(fatalsToBrowser); ###------------------------------------------------- #### variables ####------------------------------------------------- #debug my $debug = 0; #set to 0 to turn off, 1 (true) to turn on # variable to hold messages my $MSG; ## $PageTitle - title of page in html my $PageTitle = "Subversion: User Add | Delete | Change"; my $Current_Screen; # the current screen ## repository directory my $SVN = "/apps/repos"; ## location for htpasswd files my $HTDIR = "/apps/apache2/conf/htpasswd"; ## htpasswd command my $HTPASSWD = "/apps/apache2/bin/htpasswd"; ## Set untainted path $ENV{PATH} = '/apps/apache2/bin:/bin:/usr/bin:/usr/local/bin'; $ENV{IFS} = "" if $ENV{IFS} ne ""; # css my $css = ""; # path to css file, http://servername.domain.com/css/main.css for example my $headerimg = ""; # path to an image to use as header image, size roughly 420 px wide x 200 px high # http://servername.domain.com/css/header.img, for example ###------------------------------------------------- ### MAIN ###------------------------------------------------- $Current_Screen = param(".State") || "Default"; # Generate the current page. &standard_header; if ($Current_Screen eq "Default") { &PageOne; } elsif ($Current_Screen eq "Add_New_User") { &NewUser; } elsif ($Current_Screen eq "Change_User") { &ChangeUser; } elsif ($Current_Screen eq "Cancel, Do Not Delete!") { &PageOne; } elsif ($Current_Screen eq "Delete This User") { &RemoveUser; } elsif ($Current_Screen eq "Remove_User") { &CheckForDelete; } elsif ($Current_Screen eq "Cancel") { &PageOne; } elsif ($Current_Screen eq "Commit Changes") { &CommitChanges; } elsif ($Current_Screen eq "Create New User") { &CommitNew; } elsif ($Current_Screen eq "Run Script Again") { &PageOne; } elsif ($Current_Screen eq "Error") { &ErrorPage("ERROR:: main: set error condition: $MSG\n"); } else { &ErrorPage("ERROR:: main: no such .State: $Current_Screen\n"); # when we get a .State that doesn't exist } &standard_footer; exit; ###------------------------------------------------- ### header, footer, menu functions ###------------------------------------------------- sub standard_header { print header(); print start_html(-Title => "$PageTitle", -BGCOLOR=>"White", -style => { -src => "${css}" } ); print ("
\n"); print p("\"header\n"); print ("
\n"); print ("
\n"); print h3("SVN user admin tool
\nadd | delete | change\n"); print start_form(); # start_multipart_form() if file upload } sub standard_footer { print end_form(), end_html() } ###------------------------------------------------- ### subroutines for each screen ###------------------------------------------------- # The default page. sub PageOne { print <

This form is for use by administrators to
add subversion (svn) usernames and passwords.

Username should be corporate ID number.

Passwords are 8 to 12 characters, and should have at least
* one uppercase letter
* one lowercase letter
* one digit

Subversion Username:
New Password:
Confirm New Password:
Request:
Change Password & Repository Permissions
New User
Remove User

NEW_FORM } # Page to Create a new user sub NewUser { # place div for main.css print ("
\n"); # I should get username, passnew, passnew2 my $username = param("username"); my $passnew = param("passnew"); my $passnew2 = param("passnew2"); print hidden("username"); print hidden("passnew"); print hidden("passnew2"); # debug if ($debug) { print ("
\n"); print p("DEBUG:: Initial values after running param to retrieve...\n"); print p("DEBUG:: \$passnew: $passnew\n"); print p("DEBUG:: \$passnew2: $passnew2\n"); print p("DEBUG:: \$username: $username\n"); print ("\n
\n"); } ## does this user really not exist? # if the user DOES exist error out, but I'll have to verify exists or not... # can I write the params, and call out &ChangeUser? my ($user_exists, $ref_htpasswdhash, $ref_reposarray) = &UserExists($username); if ($user_exists) { # drop to ChangeUser # write the params print hidden("username") if $username; print hidden("passnew") if $passnew; print hidden("passnew2") if $passnew; print ("\n
\n"); # end the newuser div id... print ("
\n"); print h3("User $username found to exist... to change user\n"); print h3("use the following form, or hit Cancel to start over...\n"); print ("
\n"); print ("\n
\n"); &ChangeUser; return; } else { print ("
\n") if $debug; print p("DEBUG:: user NOT found to already exist\n") if $debug; print p("DEBUG:: verifying passwd and username for new user...\n") if $debug; print ("\n
\n") if $debug; } ## this is new user, need a blank set of checkbox|radio for repos ## check username for meeting crtieria &CheckUsername($username); ## check password for matching and for meeting password basics unless (($passnew) && ($passnew eq $passnew2)) { &ErrorPage("ERROR:: password not matching or missing for adding a new user"); } # ok, passwds submitted match, but do that meet criteria? if ($debug) { print ("
\n"); print p("DEBUG::values after &CheckUsername and validating passnew and passnew2 as the same...\n"); print p("DEBUG:: \$passnew: $passnew\n"); print p("DEBUG:: \$username: $username\n"); print ("\n
\n"); } &CheckPasswd($passnew); # get blank list of repositories my @repos = @$ref_reposarray; ### make new user form print ("
\n"); print p("New user to be added:
$username\n"); print p("This user will be added with the password supplied.\n"); print p("To complete this this user must be added to
AT LEAST ONE repository.
Please select which repository and then select
read-only or read and write priveliges.\n"); print ("
\n"); print ("

Select repositories to be
accessed by user $username:

\n"); print ("\n\n"); foreach my $rep (@repos) { print ("\n"); } print("\n
 $rep readread/write
\n"); # next page on submit (but not on cancel) print ("

\n"); print ("

\n"); print submit(-NAME => ".State", -VALUE => "Create New User"); print submit(-NAME => ".State", -VALUE => "Cancel"); print ("

\n"); print ("

\n"); print ("\n \n"); } sub CheckPasswd { my $pass = shift; # password must be 8 to 12 characters, one Capital letter (and at least one lower case), one number my @cnt = split(//, $pass); my $num = $#cnt; $num++; if (($num > 7) && ($num < 13)) { # check for uppercase/lowercase my ($upper, $lower, $digit); foreach my $char (@cnt) { if ($char =~ /[A-Z]/) { $upper = 1; } elsif ($char =~ /[a-z]/) { $lower = 1; } elsif ($char =~ /\d/) { $digit = 1; } } unless (($digit) && ($lower) && ($upper)) { &ErrorPage("ERROR:: Please set better password - Required: 1 uppercase, 1 lowercase letters; 1 digit"); } } else { &ErrorPage("ERROR:: passwd must be 8 to 12 characters - this password: $pass: $num characters\n"); } } sub CheckUsername { my $user = shift; # check username as corporate id # should be ^a, ^d or ^x, followed by digits unless (($user =~ /^d/) || ($user =~ /^a/) || ($user =~ /^x/)) { &ErrorPage ("ERROR:: username should be corporate ID: a######, d######, or x###### are allowed
You entered $user\n"); } unless ($user =~ /[adx]\d+$/) { &ErrorPage ("corporate ID not found: $user", "username should be corporate ID"); } # username should be equal or less than 8 characters... my @cnt = split(//, $user); my $num = $#cnt; $num++; unless ($num <= 8) { &ErrorPage("ERROR:: $user is greater than 8 characters in length, breaking length restriction\n"); } } sub GetBlankRepos { my @repos = (); # list $SVN opendir (DIR, $SVN) || croak "Failed to open directory $SVN for reading..."; while (defined(my $file = readdir(DIR))) { # skip ".", ".." and . files... if ($file =~ /^\./) { next; } else { push(@repos, $file); } } return (\@repos); } # Page to print and error message and exit... sub ErrorPage { print ("
\n"); my $err_msg = shift; print h4("I have an error condition:"); print h4("$err_msg\n"); print h4("please cancel
to start over and begin again...\n\n"); print ("
\n"); print submit(-NAME => ".State", -VALUE => "Cancel"); print ("
\n"); print ("\n
\n"); &standard_footer; exit; } sub ChangeUser { print ("
\n"); # a class used 2x's if called from NewUser # I should get username, passnew, passnew2 my ($username, $passnew, $passnew2); $username = param("username"); $passnew = param("passnew"); $passnew2 = param("passnew2"); # debug if ($debug) { print ("
\n"); print h2("DEBUG:: ChangeUser debug stuff...\n"); print p("DEBUG:: Initial values after running param to retrieve...\n"); print p("DEBUG:: \$passnew: $passnew\n"); print p("DEBUG:: \$passnew2: $passnew2\n"); print p("DEBUG:: \$username: $username\n"); print ("\n
\n"); } # make persist... print hidden("username"); print hidden("passnew"); print hidden("passnew2"); unless ($username) { &ErrorPage("ERROR:: ChangeUser: I didn't get a value for \$username: $username\n"); } ## this is changing an existing user... check if user exists... # that means going through ALL fo the possible htpasswd files and set user exists ## which means need a blank set of checkbox|radio for repos my ($user_exists, $ref_htpasswdhash, $ref_reposarray) = &UserExists($username); unless ($user_exists) { &ErrorPage ("ERROR:: $username: no such user found, exiting..."); } else { print ("
\n") if $debug; print h2("DEBUG:: user found to exist: $username\n") if $debug; print ("\n
\n") if $debug; } my %htpasswd = %$ref_htpasswdhash; # get back the hash my @repos = @$ref_reposarray; # get back the repos array ## check username for meeting crtieria &CheckUsername($username); # if there a password? if there is, does it have two entries and match? if ($passnew) { ## check password for matching and for meeting password basics unless (($passnew) && ($passnew eq $passnew2)) { &ErrorPage("ERROR:: password not matching or missing for adding a new user"); } &CheckPasswd($passnew); print p("The password for $username will be changed on \"Commit Changes\"...\n"); } else { # indicate no password changes will be made... print p("No password was submitted to be altered, password will remain the same\n"); } ### make new user form print ("

Select repository permissions for user $username:

\n"); print ("\n\n"); foreach my $rep (@repos) { # I have the data on which currently have permission... so that needs parsing if (($htpasswd{"${rep}_read"}) || ($htpasswd{"${rep}_write"})) { print ("\n"); } elsif ($htpasswd{"${rep}_read"}) { print (" $rep \n"); } else { print (" $rep \n"); } } print("\n
"); } else { print ("
"); } if ($htpasswd{"${rep}_write"}) { print (" $rep readread/write
readread/write
readread/write
\n"); # next page on submit (but not on cancel) print ("

\n"); print ("

\n"); print submit(-NAME => ".State", -VALUE => "Commit Changes"); print ("

\n"); print submit(-NAME => ".State", -VALUE => "Cancel"); print ("

\n"); print ("\n
\n"); } sub UserExists { my $username = shift; my $ref_repos = &GetBlankRepos; my @repos = @$ref_repos; #dereference my $user_exists = 0; my %htpasswd = (); # hash to hold htpasswd config data foreach my $rep (@repos) { # open and read file for read open (FILE, "$HTDIR/${rep}_read") || croak "Failed to open $HTDIR/${rep}_read for reading..."; my @file_lines = ; foreach my $line (@file_lines) { if ($line =~ /$username/) { $user_exists = 1; $htpasswd{"${rep}_read"} = 1; print p("
DEBUG:: UserExists: found $username in ${rep}_read
\n") if $debug; $htpasswd{"entry"} = "$line"; } } close FILE; open (FILE, "$HTDIR/${rep}_write") || croak "Failed to open $HTDIR/${rep}_write for reading"; my @file_lines = ; foreach my $line (@file_lines) { if ($line =~ /$username/) { $user_exists = 1; $htpasswd{"${rep}_write"} = 1; print p("
DEBUG:: UserExists: found $username in ${rep}_write
\n") if $debug; $htpasswd{"entry"} = "$line"; } } } print p("
DEBUG:: ...completed UserExists, returning to calling sub
\n") if $debug; return($user_exists, \%htpasswd, \@repos); } sub CommitChanges { # I should get username, passnew, passnew2 my $username = param("username"); my $passnew = param("passnew"); my $passnew2 = param("passnew2"); print ("
\n") if $debug; print p("DEBUG:: CommitChanges: \$username: $username\n") if $debug; print p("DEBUG:: CommitChanges: \$passnew: $passnew\n") if $debug; print ("
\n") if $debug; # passed to this would be the list of repositories and values assigned... # list fo repos... my ($user_exists, $ref_htpasswdhash, $ref_reposarray) = &UserExists($username); my @repos = @$ref_reposarray; #dereference my %htpasswd = %$ref_htpasswdhash; my $message; # ChangeUser MAY set password, may not... # untaint passwd passnew and username my $set_user = &UnTaintMe("$username"); my $set_passwd = &UnTaintMe("$passnew") if $passnew; # if no passwd to reset (in ChangeUser) then we'll be appending an existing line... # UserExists captures $htpasswd{entry}, the line for the user from any one of the files my ($htpassline, $set_line); unless ($passnew) { $htpassline = "$htpasswd{entry}"; $set_line = &UnTaintMe($htpassline); unless ($set_line) { &ErrorPage("ERROR:: CommitChanges: \$passnew not found AND \$set_line (line from htpasswd) not found..."); # if this isn't set we sure as hell don't want to tdelete the user from the files, which is the next step... } } # set the user to zero, deleting from files... my $at_least_one = 0; # user must remain part of at least on repository... # check through repositories for at least one remaining... foreach my $check (@repos) { my $rep_access = param("${check}_access"); if ($rep_access) { $at_least_one = 1; } } unless ($at_least_one) { &ErrorPage("ERROR:: CommitChanges: I failed to find at least one repository for this user to remain on
Please restart and remove user if this is what you intended...\n"); } foreach my $rep_zero (@repos) { my $set_rep_zero = &UnTaintMe("$rep_zero"); my $result = `$HTPASSWD -D $HTDIR/${set_rep_zero}_read $set_user`; $result = `$HTPASSWD -D $HTDIR/${set_rep_zero}_write $set_user`; } $message = "$message
| ...removed $set_user from all repositories (zero out)
\n"; # this moves to the loop and gets issued for each repos set as "true" and r or rw foreach my $rep (@repos) { #get params... my $rep_access = param("${rep}_access"); my $rep_type = param("${rep}_rw"); if ($rep_access) { # repository access checked (value set at 1) # untaint this my $set_rep = &UnTaintMe("$rep"); #what kind of access? "read" | "readwrite" if ($rep_type eq "read") { if ($passnew) { my $res = `$HTPASSWD -bm $HTDIR/${set_rep}_read $set_user $set_passwd`; # set user and passwd in read htpasswd $message = "$message | ...added $set_user to $HTDIR/${rep}_read
\n"; } else { # add to the file by appending the line `echo '${set_line}' >> $HTDIR/${set_rep}_read`; print ("
\n") if $debug; print p("DEBUG:: CommitChanges: hit echo line to ${set_rep}_read...\n") if $debug; print ("\n
\n") if $debug; } } elsif ($rep_type eq "read_write") { if ($passnew) { my $res = `$HTPASSWD -bm $HTDIR/${set_rep}_read $set_user $set_passwd`; $res = `$HTPASSWD -bm $HTDIR/${set_rep}_write $set_user $set_passwd`; $message = "$message
| ...added $set_user to $HTDIR/${rep}_read & ${rep}_write
\n"; } else { `echo '${set_line}' >> $HTDIR/${set_rep}_read`; `echo '${set_line}' >> $HTDIR/${set_rep}_write`; } } else { &ErrorPage("ERROR:: failed to find ${rep}:: \$rep_type (kind of access): $rep_type"); } } } $message = "$message
| ...completed placing changes for user $username
\n"; print p("
DEBUG:: ...I am at the end of CommitChanges
\n") if $debug; &Message("$message"); } sub UnTaintMe { my $tainted = shift; print ("
\n") if $debug; print p("DEBUG:: UnTaintMe: string passed: \$tainted: $tainted\n") if $debug; print ("
\n") if $debug; my $untainted; if ($tainted =~ /^([ &:#-\@\w.]+)$/) { $untainted = "$1"; } else { &ErrorPage("ERROR:: UnTaintMe: Tainted input: bad string: \$tainted: [$tainted]\n"); } return $untainted; } sub CheckForDelete { # I should get username my $username = param("username"); print hidden("username"); # preserve to pass on unless ($username) { &ErrorPage("ERROR:: CheckForDelete: I failed to be passed a username...\n"); } my ($user_exists, $ref_htpasswdhash, $ref_reposarray) = &UserExists($username); unless ($user_exists) { &ErrorPage ("ERROR:: CheckForDelete: \$username: $username | no such user found"); } else { print ("
\n") if $debug; print p("DEBUG:: CheckForDelete: user found to exist: $username\n") if $debug; print ("\n
\n") if $debug; } my $set_user = &UnTaintMe("$username"); #untaint the username here my %htpasswd = %$ref_htpasswdhash; # get back the hash my @repos = @$ref_reposarray; # get back the repos array print p("I will be removing $username from the following files:\n"); print ("
    \n"); foreach my $rep (@repos) { if ($htpasswd{"${rep}_read"}) { print ("
  • ${rep}_read
  • \n"); } if ($htpasswd{"${rep}_write"}) { print ("
  • ${rep}_write
  • \n"); } } print ("
\n"); print p("If this is what you intend for this user click \"Delete This User\" below\n"); print ("

\n"); print ("

\n"); print submit(-NAME => ".State", -VALUE => "Delete This User"); print ("

\n"); print submit(-NAME => ".State", -VALUE => "Cancel, Do Not Delete!"); print ("

\n"); } sub RemoveUser { my $msg; # I should get username, passnew, passnew2 - need username... my $username = param("username"); unless ($username) { &ErrorPage("ERROR:: RemoveUser: I failed to be passed a username...\n"); } # does this user exist? my ($user_exists, $ref_htpasswdhash, $ref_reposarray) = &UserExists($username); unless ($user_exists) { &ErrorPage ("ERROR:: RemoveUser: $username: no such user found, exiting..."); } else { print ("
\n") if $debug; print h2("DEBUG:: RemoveUser: user found to exist: $username\n") if $debug; print ("\n
\n") if $debug; } my $set_user = &UnTaintMe("$username"); #untaint the username here my %htpasswd = %$ref_htpasswdhash; # get back the hash my @repos = @$ref_reposarray; # get back the repos array foreach my $rep (@repos) { my $set_rep = &UnTaintMe("$rep"); # if the user exists in a file delete - %htpasswd contains that data # add to $MSG and then pass to &Message if ($htpasswd{"${rep}_read"}) { my $res = `$HTPASSWD -D $HTDIR/${set_rep}_read $set_user`; $msg = "$msg | found and removed $set_user from ${set_rep}_read htpasswd file...
"; } else { print ("
\n") if $debug; print p("DEBUG:: RemoveUser: user $set_user not found in ${set_rep}_read\n") if $debug; print ("\n
\n") if $debug; } if ($htpasswd{"${set_rep}_write"}) { my $res = `$HTPASSWD -D $HTDIR/${set_rep}_write $set_user`; $msg = "$msg | found and removed $set_user from ${set_rep}_write htpasswd file...
"; } else { print ("
\n") if $debug; print p("DEBUG:: RemoveUser: user $set_user not found in ${set_rep}_write\n") if $debug; print ("\n
\n") if $debug; } } &Message("$msg"); } #messages - final page on success sub Message { my $msg = shift; print ("
\n"); print p(" \n"); print p("$msg\n"); print p(" \n"); print h3("Successfully completed\n"); print p(" \n"); print ("
\n"); print ("
\n"); print submit(-NAME => ".State", -VALUE => "Run Script Again"); print ("
\n"); } sub CommitNew { # I should get username, passnew, passnew2 my $username = param("username"); my $passnew = param("passnew"); my $passnew2 = param("passnew2"); print ("
\n") if $debug; print p("DEBUG:: CommitNew: \$username: $username\n") if $debug; print p("DEBUG:: CommitNew: \$passnew: $passnew\n") if $debug; print ("
\n") if $debug; # passed to this would be the list of repositories and values assigned... # list fo repos... my ($user_exists, $ref_htpasswdhash, $ref_reposarray) = &UserExists($username); my @repos = @$ref_reposarray; #dereference my %htpasswd = %$ref_htpasswdhash; my $message; # ChangeUser MAY set password, may not... # untaint passwd passnew and username my $set_user = &UnTaintMe("$username"); my $set_passwd = &UnTaintMe("$passnew"); # this moves to the loop and gets issued for each repos set as "true" and r or rw my $at_least_one = 0; # at least one repository must be selected... foreach my $rep (@repos) { #get params... my $rep_access = param("${rep}_access"); my $rep_type = param("${rep}_rw"); print ("
\n") if $debug; print p("DEBUG:: CommitNew: ${rep}:: \$rep_access: $rep_access\t\$rep_type: $rep_type\n") if $debug; print ("
\n") if $debug; if ($rep_access) { # repository access checked (value set at 1) # untaint this my $set_rep = &UnTaintMe("$rep"); #what kind of access? "read" | "readwrite" if ($rep_type eq "read") { my $res = `$HTPASSWD -bm $HTDIR/${set_rep}_read $set_user $set_passwd`; # set user and passwd in read htpasswd $message = "$message | ...added $set_user to $HTDIR/${rep}_read
\n"; $at_least_one = 1; } elsif ($rep_type eq "read_write") { my $res = `$HTPASSWD -bm $HTDIR/${set_rep}_read $set_user $set_passwd`; $res = `$HTPASSWD -bm $HTDIR/${set_rep}_write $set_user $set_passwd`; $message = "$message
| ...added $set_user to $HTDIR/${rep}_read & ${rep}_write
\n"; $at_least_one = 1; } else { &ErrorPage("ERROR:: failed to find ${rep}:: \$rep_type (kind of access): $rep_type"); } } } # check for at least one repo added for this user... unless ($at_least_one) { &ErrorPage("ERROR:: CommitNew: You must add $username to at least ONE repository
I find the user added to no repositories...\n"); } $message = "$message
| ...completed add new user $username
\n"; print ("
\n") if $debug; print p("DEBUG:: I am at CommitNew\n") if $debug; print ("
\n") if $debug; &Message("$message"); }