Link to home
Start Free TrialLog in
Avatar of falco
falco

asked on

Lock Routine to protect files

Is the following lock routine secure? Yesterday I have lost
the .htpasswd file.. now I am unsure!

(lock routine is included in all scripts that writes to
.htpasswd and userdata file)

######LOCK
$endtime = 20;
$endtime = time + $endtime;
while (-e "/web/domain/html/datafiles/lock" && time < $endtime)
  {
  sleep(1);
  }
open(LOCK_FILE, ">/web/domain/html/datafiles/lock")  || die ("Could not create lock file");
close(LOCK_FILE);
######

write modified files back.....

######UNLOCK
   unlink("/web/domain/html/datafiles/lock");
######
ASKER CERTIFIED SOLUTION
Avatar of jhance
jhance

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of falco
falco

ASKER

Please could you modify the above snippet? Thanks
#
# This will ONLY work on a platform that supports
# file locking and that has the flock() function
# implemented in it's port of PERL.  Note that some
# Windows PERL ports do not support this call (even though
# they should!)
#

# Open the .htpasswd file
open(PASSWD, ">>.htpasswd") || die "Error: $!";

# Lock for for exclusive access, flock will either lock and
# return, or will wait and then return when the file can be
# locked
flock(PASSWD, $LOCK_EX);

# Write stuff to the file here
print PASSWD "Stuff written to file\n";
#

# We're done, unlock it now
flock(PASSWD, $LOCK_UN);

# Close it
close(PASSWD);
Avatar of falco

ASKER

Have I to set the variables "$LOCK_EX" and "$LOCK_UN"?
Should the "flock(PASSWD, $LOCK_EX);" not be before we
open the file?

open(PASSWD, ">>.htpasswd") || die "Error: $!";
flock(PASSWD, $LOCK_EX);
print PASSWD "Stuff written to file\n";
flock(PASSWD, $LOCK_UN);
close(PASSWD);

open(USERFILE, ">>userdata.log") || die "Error: $!";
flock(USERFILE, $LOCK_EX);
print USERFILE "Stuff written to file\n";
flock(USERFILE, $LOCK_UN);
close(USERFILE);
                        # Close it

The flock() comes AFTER the open() call as you must have the file handle (PASSWD) before you can flock() it.  The variables $LOCK_EX and $LOCK_UN are predefined in PERL.
Avatar of falco

ASKER

Thanks! I'll try it now
Avatar of ozo
 use Fcntl ':flock';
will define LOCK_EX and LOCK_UN, (not $LOCK_EX and $LOCK_UN)
otherwise, they won't be predefined

Also, if you're appending, you should
  seek PASSWD,0,2;
after acquireing the lock, in case someone else appended while
you were waiting for the lock.
Avatar of falco

ASKER

------------------------------
open(HTPASSWD, "|$htpasswd $passfile $contents{'username'} >/dev/null 2>&1");
flock(HTPASSWD, $LOCK_EX);
print HTPASSWD "$passwort\n";
flock(HTPASSWD, $LOCK_UN);
close(HTPASSWD);

#print "Content-type: text/plain\n\n ";
open(USERINFO, ">>$pwlog");
flock(USERINFO, $LOCK_EX);
print USERINFO "......................\n";
flock(USERINFO, $LOCK_UN);
close (USERINFO);
------------------------------
Have I to alter above or is it secure??
It looks OK to me but I would heed ozo's suggestion to insert a:

seek PASSWD,0,2;

after any $LOCK_EX flock() call.  This will make sure that you are positioned at the end of the file after the flock() returns.

For example:

#print "Content-type: text/plain\n\n ";
open(USERINFO, ">>$pwlog");
flock(USERINFO, $LOCK_EX);
seek(USERINFO, 0, 2);
print USERINFO "......................\n";
flock(USERINFO, $LOCK_UN);
close (USERINFO);
Avatar of falco

ASKER

my last question about flock:

Please could you paste the necessary flocks to secure the following sequence?

  open (COUNTER_FILE, "$counterfile")  || die ("Can't open counterfile");

  while (<COUNTER_FILE>)
    {
    $current_counter = $_;
    }
  close (COUNTER_FILE);

  $current_counter++;
  $new_counter = $current_counter;

  open (COUNTER_FILE, ">$counterfile")  || die ("Can't open counterfile");
  print COUNTER_FILE "$new_counter";
  close (COUNTER_FILE);

  open (DATABASE, "$database")  || die ("Can't open database");
  @Ads = <DATABASE>;
  close DATABASE;
 
  @Ads1 = reverse (@Ads);
  push (@Ads1, "$new_counter|$data1|$data2|$data3|$data4\n");
  @Ads2 = reverse (@Ads1);
  open (DATABASE, ">$database")  || die ("Can't open database");
  print DATABASE @Ads2;
  close DATABASE;
Avatar of falco

ASKER

Great! Thank you
open (COUNTER_FILE, "$counterfile") || die ("Can't open counterfile");
flock(COUNTER_FILE, $FLOCK_EX);
seek(COUNTER_FILE, 0, 2);

while (<COUNTER_FILE>)
 {
 $current_counter = $_;
 }

flock(COUNTER_FILE, $FLOCK_UN);
close (COUNTER_FILE);

$current_counter++;
$new_counter = $current_counter;

open (COUNTER_FILE, ">$counterfile") || die ("Can't open counterfile");
flock(COUNTER_FILE, $FLOCK_EX);
seek(COUNTER_FILE, 0, 2);

print COUNTER_FILE "$new_counter";
flock(COUNTER_FILE, $FLOCK_UN);
close (COUNTER_FILE);

open (DATABASE, "$database") || die ("Can't open database");
flock(DATABASE, $FLOCK_EX);
seek(DATABASE, 0, 2);
@Ads = <DATABASE>;
flock(DATABASE, $FLOCK_UN);
close DATABASE;
 
@Ads1 = reverse (@Ads);
push (@Ads1, "$new_counter|$data1|$data2|$data3|$data4\n");
@Ads2 = reverse (@Ads1);
open (DATABASE, ">$database") || die ("Can't open database");
flock(DATABASE, $FLOCK_EX);
seek(DATABASE, 0, 2);
print DATABASE @Ads2;
flock(DATABASE, $FLOCK_UN);
close DATABASE;
Avatar of falco

ASKER

Dear Top Expert

Really dont know what to say. You are simply great!

Thanks again.

(There are just 80 points on my account. I'll come back to you to
assign some extra points)



If your version of perl is earlier than 5.004,
you should close or FileHandle::flush before you FLOCK_UN,
(Or just close and don't FLOCK_UN)
5.004 does an automatic flush, but it's probably best
not to FLOCK_UN before closing or flushing anyway, so someone who hasn't upgraded
doesn't try to use your code as a model.

Also, I still don't see $FLOCK_EX being defined,
most systems use 2, but you should
 use Fcntl ':flock';
 $FLOCK_EX = LOCK_EX;
to be sure you have the right value for your system
Avatar of falco

ASKER

I have problems with "seek(HTPASSWD, 0, 2)" in read routines.
write operations works properly.
-----------------
$Userexists = "0";
open (HTPASSWD, "$passfile");
flock(HTPASSWD, $LOCK_EX);
# seek(HTPASSWD, 0, 2);
@Users = <HTPASSWD>;
close HTPASSWD;
.....
------------------

Yes, my mistake.  seek(FILE, 0, 2) says position for file at the END of the file.  So if you are reading, you wouldn't get anything.  For the read operations, use:

seek(FILE, 0, 0);

This will position your reading to start at the BEGINNING of the file (assuming that's what you want to happen).
falco,

It ocurred to me that I had not posted the whole thing.  ozo was hinting at it with "use Fcntl ':flock';".  Be sure you define $FLOCK_EX and $FLOCK_UN like this:

$FLOCK_EX = 2;
$FLOCK_UN = 8;

at the beginning of your program.


Avatar of falco

ASKER

Thank you, but what is ment with "Fcntl ':flock'"?
It's like an include file in a C or C++ program.  You say:

use Fcntl':flock'

and it will load that package.  It's not in all versions of perl and I tend not to use it as you can't always count on it being there, and you don't always have the luxury of being able to convince people to upgrade their software.
Avatar of falco

ASKER

ITs unbelievable. I have lost the .htpasswd file again. (filesize 98kb)
Have I to alter the "htpasswd.pl" script too?

$LOCK_EX = 2;
$LOCK_UN = 8;
open(HTPASSWD, "|/path/htpasswd.pl /path/.htpasswd $contents{'username'} >/dev/null 2>&1");
flock(HTPASSWD, $LOCK_EX);
seek(HTPASSWD, 0, 2);
print HTPASSWD "$passwort\n";
flock(HTPASSWD, $LOCK_UN);
close(HTPASSWD);


--------------htpasswd.pl----
#!/usr/bin/perl

&GetArgs;               # Get Command line args or die
&LoadPwFile;            # Load current passwordfile
                        # (I'll add file locking later)
print &Prompt;          # Prompt for password
chop($pass=<>);         # Get new password
&MakeNewPassword;       # Encrypt the newly entered password with "salt".
$list{$name} = $cpw;    # Load new crypt pw into passwordfile array
&WriteNewFile;          # Write the amended array as the passwordfile

sub GetArgs {
        $file=shift(@ARGV);
        $name=shift(@ARGV);
        if ( (!$file) | (!$name) ) {
                die "Usage: $0 htpasswdfile username\n";
        }
}
sub MakeNewPassword {
        srand($$|time);                                 # random seed
        @saltchars=(a..z,A..Z,0..9,'.','/');            # valid salt chars
        $salt=$saltchars[int(rand($#saltchars+1))];     # first random salt char
        $salt.=$saltchars[int(rand($#saltchars+1))];    # second random salt char
        $cpw = crypt($pass,$salt);
}
sub LoadPwFile {
        open(HP, "$file") || open(HP, ">$file");
        while (<HP>) {
                chop;
                ($tname,$tpw) = split(':',$_);
                $list{$tname} = $tpw;
        }
}
sub Prompt {
        local($text);
        if ($list{$name}) {
                $text = "Changing password for $name\nEnter new password:";
        }
        else {
                $text = "Enter password for $name:";
        }
        return($text);
}
sub WriteNewFile {
        open(HTPASSWD, ">$file");
                foreach $key (sort keys(%list)) {
                        print HTPASSWD "$key:$list{$key}\n";  # print it
                }
        close(HTPASSWD);
}
---------------------------------------

You must assume that ANY file that is being written to in your web by a cgi-bin script is at risk of simultaneous access.  You should flock every file you open for WRITE or UPDATE in a cgi script.  No exceptions.  I am still paranoid, so I usually make periodic backup copies as well.


Avatar of falco

ASKER

I had a backup but I dont understand the following command:


                    open(HTPASSWD, "|/path/htpasswd.pl /path/.htpasswd $contents{'username'} >/dev/null 2>&1");

and how to set flock in this sub of htpasswd.pl

sub LoadPwFile {
                        open(HP, "$file") || open(HP, ">$file");
                        while (<HP>) {
                        chop;
                        ($tname,$tpw) = split(':',$_);
                        $list{$tname} = $tpw;
                        }
                    }