############################################################################### #### S E C U R E P A S S W O R D L O O K U P #### #### #### #### Written by: JPDeni #### #### Modified: 6 Jun 2006 #### #### (thanks delicia!) #### #### #### #### What it does-- #### #### This is the most secure password lookup modification I can come up #### #### with. #### #### #### #### When a user signs up for an account, he enters his desired username #### #### and email address. The script sends a random password to the user at #### #### the email address he entered. This guarantees that email addresses are#### #### valid. #### #### #### #### Once the user receives his password by email, he may log in. #### #### #### #### If a user loses his password, he enters his email address. The script #### #### looks for the email address in the password file, generates a new #### #### random password and sends an email with the user's userid and new #### #### password. #### #### #### #### The password generation routine creates an 8-character "word" which #### #### consists of 4 consonant/vowel pairs. There are 100 million possible #### #### combinations of letters. #### #### #### Be certain that you also choose the appropriate html subroutines #### for the mod. #### #### #### Also available are optional subroutines-- #### #### get_email #### #### gets email address from the password file when a record is added #### #### change_email #### #### allows user to change his email address in the password file #### #### also (optionally) changes the email address in the user's record(s)#### #### Note that if a user changes his email address, a new password will #### #### be sent to him to verify that the address is valid. #### #### change_password #### #### allows user to change his password #### ############################################################################### ############################################################################### # script: default.cfg # # # # add new lines # # # # Where to put it-- # # after # # # Full path and file name of the html routines. # # require $db_script_path . "/html.pl"; # ############################################################################### # Full path to sendmail on your system # Be sure to leave the | character at the beginning!!! $mailprog = "|/usr/lib/sendmail -t"; # Your email address $admin_email = 'you@yourserver.com'; ############################################################################### # script: db.cgi # # sub main # # # # add lines # # # # Where to add them -- # # after # # # If we allow users to signup, and they want to, go to the signup form. # # elsif ($auth_signup and $in{'signup_form'}) { # # &html_signup_form; # # } # # elsif ($auth_signup and $in{'signup'}) { # # &signup; # # } # ############################################################################### #### Following two lines added for secure_password_lookup mod elsif ($in{'lookup_form'}) { &html_lookup_form; } elsif ($in{'lookup'}) { &lookup; } ############################################################################### # script: db.cgi # # sub admin_display # # # # replace subroutine # ############################################################################### sub admin_display { # -------------------------------------------------------- # Let's an admin add/update/remove users from the authorization file. # my ($message, @lines, $line); # Do we have anything to do? CASE: { # If we've been passed in new_username, then we are adding a new user. Do # some basic error checking and then add him into the password file. $in{'new_username'} and do { unless ((length($in{'new_username'}) >= 3) and (length($in{'new_username'}) <= 12) and ($in{'new_username'} =~ /^[a-zA-Z0-9]+$/)) { $message = "Invalid username: $in{'new_username'}. Must only contain letters and numbers and be less then 12 and greater then 3 characters."; last CASE; } unless ((length($in{'password'}) >= 3) and (length($in{'password'}) <= 12)) { $message = "Invalid password: '$in{'password'}'. Must be less then 12 and greater then 3 characters."; last CASE; } unless ($in{'email'} =~ /.+\@.+\..+/) { $message = "Invalid email address: '$in{'password'}'."; last CASE; } open (PASSWD, "<$auth_pw_file") || &cgierr("unable to open password file. Reason: $!\n"); @passwds = ; close PASSWD; foreach $pass (@passwds) { # Go through each pass and see if we match.. next if ($pass =~ /^$/); # Skip blank lines. next if ($pass =~ /^#/); # Skip Comment lines. chomp ($pass); ($userid, $pw, $view, $add, $del, $mod, $admin, $email) = split (/:/, $pass); if ($in{'new_username'} eq $userid) { $message = "userid already exists. Please try another."; last CASE; } if ($in{'email'} eq $email) { $message .= "email address already exists."; last CASE; } } open (PASS, ">>$auth_pw_file") or &cgierr ("unable to open: $auth_pw_file.\nReason: $!"); if ($db_use_flock) { flock(PASS, 2) or &cgierr("unable to get exclusive lock on $auth_pw_file.\nReason: $!"); } my @salt_chars = ('A' .. 'Z', 0 .. 9, 'a' .. 'z', '.', '/'); my $salt = join '', @salt_chars[rand 64, rand 64]; my $encrypted = crypt($in{'password'}, $salt); print PASS "$in{'new_username'}:$encrypted:$in{'per_view'}:$in{'per_add'}:$in{'per_del'}:$in{'per_mod'}:$in{'per_admin'}:$in{'email'}\n"; close PASS; $message = "User: $in{'new_username'} created."; last CASE; }; # If we've been passed in delete, then we are removing a user. Check # to make sure a user was selected then try and remove him. $in{'delete'} and do { unless ($in{'username'}) { $message = "No username selected to delete."; last CASE; } open (PASS, "<$auth_pw_file") or &cgierr ("unable to open: $auth_pw_file.\nReason: $!"); if ($db_use_flock) { flock(PASS, 1) } @lines = ; close PASS; open (PASS, ">$auth_pw_file") or &cgierr ("unable to open: $auth_pw_file.\nReason: $!"); if ($db_use_flock) { flock(PASS, 2) or &cgierr("unable to get exclusive lock on $auth_pw_file.\nReason: $!"); } my $found = 0; foreach $line (@lines) { ($line =~ /^$in{'username'}:/) ? ($found = 1) : print PASS $line; } close PASS; $found ? ($message = "User: $in{'username'} deleted.") : ($message = "Unable to find userid: $in{'username'} in password file."); last CASE; }; # If we have a username, and the admin didn't press inquire, then # we are updating a user. ($in{'username'} && !$in{'inquire'}) and do { open (PASS, "<$auth_pw_file") or &cgierr ("unable to open: $auth_pw_file.\nReason: $!"); if ($db_use_flock) { flock(PASS, 1); } @lines = ; close PASS; foreach $pass (@passwds) { # Go through each pass and see if we match.. next if ($pass =~ /^$/); # Skip blank lines. next if ($pass =~ /^#/); # Skip Comment lines. chomp ($pass); ($userid, $pw, $view, $add, $del, $mod, $admin, $email) = split (/:/, $pass); if (($in{'email'} eq $email) && ($in{'username'} ne $userid)) { $message .= "email address already exists."; last CASE; } } open (PASS, ">$auth_pw_file") or &cgierr ("unable to open: $auth_pw_file.\nReason: $!"); if ($db_use_flock) { flock(PASS, 2) or &cgierr("unable to get exclusive lock on $auth_pw_file.\nReason: $!"); } my $found = 0; foreach $line (@lines) { if ($line =~ /^$in{'username'}:/) { my $password = (split (/:/, $line))[1]; unless ($password eq $in{'password'}) { my @salt_chars = ('A' .. 'Z', 0 .. 9, 'a' .. 'z', '.', '/'); my $salt = join '', @salt_chars[rand 64, rand 64]; $password = crypt($in{'password'}, $salt); } print PASS "$in{'username'}:$password:$in{'per_view'}:$in{'per_add'}:$in{'per_del'}:$in{'per_mod'}:$in{'per_admin'}:$in{'email'}\n"; $found = 1; } else { print PASS $line; } } close PASS; $in{'inquire'} = $in{'username'}; $found ? ($message = "User: $in{'username'} updated.") : ($message = "Unable to find user: '$in{'username'}' in the password file."); last CASE; }; }; # Now let's load the list of users. open (PASS, "<$auth_pw_file") or &cgierr ("unable to open: $auth_pw_file.\nReason: $!"); if ($db_use_flock) { flock(PASS, 1); } @lines = ; close PASS; # If we are inquiring, let's look for the specified user. my (@data, $user_list, $perm, $password, $email); $user_list = qq~ Add Delete Modify Admin |; } } foreach $user (sort @users) { if ($in{'inquire'} and ($in{'username'} eq $user)) { $user_list .= qq~\n~; } else { $user_list .= qq~\n~; } } $user_list .= ""; # Build the permissions list if we haven't inquired in someone. if (!$perm) { $perm = qq| View Add Delete Modify Admin |; } &html_admin_display ($message, $user_list, $password, $perm, $email); } ############################################################################### # script: db.cgi # # sub signup # # # # replace subroutine # # # ############################################################################### sub signup { # -------------------------------------------------------- # Allows a user to sign up without admin approval. Must have $auth_signup = 1 # set. The user gets @default_permissions. # my ($message,$userid, $pw, $view, $add, $del, $mod, $admin, $email, $password); # Check to make sure userid is ok, pw ok, and userid is unique. unless ((length($in{'userid'}) >= 3) and (length($in{'userid'}) <= 12) and ($in{'userid'} =~ /^[a-zA-Z0-9]+$/)) { $message = "Invalid userid: $in{'userid'}. Must only contain only letters and be less then 12 and greater then 3 characters."; } unless ($in{'email'} =~ /.+\@.+\..+/) { $message = "Invalid email address format: '$in{'email'}'."; } open (PASSWD, "<$auth_pw_file") || &cgierr("unable to open password file. Reason: $!\n"); @passwds = ; close PASSWD; foreach $pass (@passwds) { # Go through each pass and see if we match.. next if ($pass =~ /^$/); # Skip blank lines. next if ($pass =~ /^#/); # Skip Comment lines. chomp ($pass); ($userid, $pw, $view, $add, $del, $mod, $admin, $email) = split (/:/, $pass); if (lc($in{'userid'}) eq lc($userid)) { $message = "userid already exists. Please try another.
"; } if (lc($in{'email'}) eq lc($email)) { $message .= "email address already exists."; } } if ($message) { &html_signup_form ($message); return; } $in{'pw'} = &generate_password; $in{'email'} = lc($in{'email'}); # Add the userid into the file with signup permissions. open (PASS, ">>$auth_pw_file") or &cgierr ("unable to open: $auth_pw_file.\nReason: $!"); if ($db_use_flock) { flock(PASS, 2) or &cgierr("unable to get exclusive lock on $auth_pw_file.\nReason: $!"); } srand( time() ^ ($$ + ($$ << 15)) ); # Seed Random Number my @salt_chars = ('A' .. 'Z', 0 .. 9, 'a' .. 'z', '.', '/'); my $salt = join '', @salt_chars[rand 64, rand 64]; my $encrypted = crypt($in{'pw'}, $salt); my $permissions = join (":", @auth_signup_permissions); print PASS "$in{'userid'}:$encrypted:$permissions:$in{'email'}\n"; close PASS; open (MAIL, "$mailprog") || &cgierr("Can't start mail program"); print MAIL "To: $in{'email'}\n"; print MAIL "From: $admin_email\n"; print MAIL "Subject: $html_title Account Created\n\n"; print MAIL "-" x 75 . "\n\n"; print MAIL "Your account at $html_title has been created.\n\n"; print MAIL "Your $html_title User ID is: $in{'userid'}\n"; print MAIL "Your $html_title password is: $in{'pw'}\n\n"; print MAIL "Please keep this email for future reference.\n\n"; print MAIL "To log on, go to\n\n"; print MAIL "$db_script_url?db=$db_setup\n"; print MAIL "and enter your User ID and password.\n\n"; print MAIL "Please contact $html_title support at: $admin_email\n"; print MAIL "if you have any questions.\n\n"; close (MAIL); &html_signup_success; } ############################################################################### # script: db.cgi # # sub lookup # # # # new subroutine # # # ############################################################################### sub lookup { # -------------------------------------------------------- # This subroutine added for the secure_password_lookup mod my ($message, $userid, $pw, $view, $add, $del, $mod, $admin, $email, $password, $found, $output); # Check to make sure email is ok unless ($in{'email'} =~ /.+\@.+\..+/) { &html_lookup_form ("Invalid email address format: '$in{'email'}'."); return; } open (PASSWD, "<$auth_pw_file") || &cgierr("unable to open password file. Reason: $!\n"); @passwds = ; close PASSWD; $found = ''; foreach $pass (@passwds) { # Go through each pass and see if we match. chomp ($pass); if (lc($pass) eq lc($in{'email'})) { $found = $pass; } else { $output .= $pass . "\n"; } } if (!$found) { &html_lookup_form ("The email address you entered was not found in the file."); return; } open (PASS, ">$auth_pw_file") or &cgierr ("unable to open: $auth_pw_file.\nReason: $!"); if ($db_use_flock) { flock(PASS, 2) or &cgierr("unable to get exclusive lock on $auth_pw_file.\nReason: $!"); } print PASS $output; close PASS; ($userid, $pw, $view, $add, $del, $mod, $admin, $email) = split (/:/, $found); $password = &generate_password; srand( time() ^ ($$ + ($$ << 15)) ); # Seed Random Number my @salt_chars = ('A' .. 'Z', 0 .. 9, 'a' .. 'z', '.', '/'); my $salt = join '', @salt_chars[rand 64, rand 64]; my $encrypted = crypt($password, $salt); # Add the userid into the file with default permissions. open (PASSWD, ">>$auth_pw_file") or &cgierr ("unable to open: $auth_pw_file.\nReason: $!"); if ($db_use_flock) { flock(PASSWD, 2) or &cgierr("unable to get exclusive lock on $auth_pw_file.\nReason: $!"); } print PASSWD "$userid:$encrypted:$view:$add:$del:$mod:$admin:$email\n"; close PASSWD; open (MAIL, "$mailprog") || &cgierr("Can't start mail program"); print MAIL "To: $in{'email'}\n"; print MAIL "From: $admin_email\n"; print MAIL "Subject: $html_title New Password\n\n"; print MAIL "-" x 75 . "\n\n"; print MAIL "Here is your new $html_title password.\n\n"; print MAIL "Your $html_title User ID is: $userid\n"; print MAIL "Your $html_title password is: $password\n\n"; print MAIL "Please keep this email for future reference.\n\n"; print MAIL "To log on, go to\n\n"; print MAIL "$db_script_url?db=$db_setup\n"; print MAIL "and enter your User ID and password.\n\n"; print MAIL "Please contact $html_title support at: $admin_email\n"; print MAIL "if you have any questions.\n\n"; close (MAIL); &html_lookup_success; } ############################################################################### # script: db.cgi # # sub generate_password # # # # new subroutine # # # # What it does -- # # Generates an 8-character password, consisting of 4 two-letter "syllables" # ############################################################################### sub generate_password { # -------------------------------------------------------- #### Following subroutine added for secure_password_lookup mod my (@c, @v, $password); srand( time() ^ ($$ + ($$ << 15)) ); # Seed Random Number @c = split(/ */, "bcdfghjklmnprstvwxyz"); @v = split(/ */, "aeiou"); for ($i=1; $i<=4; ++$i) { $password .= $c[int(rand(20))] . $v[int(rand(5))]; } return $password; }