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");
######
falcoAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

jhanceCommented:
No, this is not a valid way to lock a file to prevent simultaneous.  In PERL, you need to use the flock() function to lock and unlock the file.  In your example above, another process can (and will) get in between the time when you check for the existence of the lock file and when you create it.  Using flock() causes the check/lock to happen in one indivisible operation that is multi-processing safe.
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
falcoAuthor Commented:
Please could you modify the above snippet? Thanks
0
jhanceCommented:
#
# 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);
0
Introducing Cloud Class® training courses

Tech changes fast. You can learn faster. That’s why we’re bringing professional training courses to Experts Exchange. With a subscription, you can access all the Cloud Class® courses to expand your education, prep for certifications, and get top-notch instructions.

falcoAuthor Commented:
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

0
jhanceCommented:
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.
0
falcoAuthor Commented:
Thanks! I'll try it now
0
ozoCommented:
 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.
0
falcoAuthor Commented:
------------------------------
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??
0
jhanceCommented:
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);
0
falcoAuthor Commented:
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;
0
falcoAuthor Commented:
Great! Thank you
0
jhanceCommented:
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;
0
falcoAuthor Commented:
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)



0
ozoCommented:
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
0
falcoAuthor Commented:
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;
.....
------------------

0
jhanceCommented:
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).
0
jhanceCommented:
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.


0
falcoAuthor Commented:
Thank you, but what is ment with "Fcntl ':flock'"?
0
jhanceCommented:
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.
0
falcoAuthor Commented:
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);
}
---------------------------------------

0
jhanceCommented:
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.


0
falcoAuthor Commented:
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;
                        }
                    }
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Scripting Languages

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.