#!perl
# Fun script to generate a password from "random" characters. Passwords
# generated by this script will be eight characters in length and contain
# exactly two numeric characters.
# Created 06-2000 by Andrew Greenburg <andrew@analogmarketing.net>.
# Modified 02-2001 by Andrew Greenburg <andrew@analogmarketing.net>.
 
# If we have a dictionary file, where is it?
# WARNING: Checking for real words makes this process amazingly slow.
# $dictfile = "/usr/share/dict/words";
 
# If there is an argument, that's our number of passwords.
if (length($ARGV[0])) {
    chomp($passnum = $ARGV[0]);
} else {
    $passnum = 1;
}
 
# If the argument was bad, tell the user.
if ((!($passnum =~ /^[0-9]+$/)) || ($passnum == 0)) {
    die "Usage: $0 <number of passwords>\n";
}
 
# Seed the random number generator.
srand(time ^ $$ ^ unpack "%L*", `ps auxww | gzip`);
 
# Read the dictionary into memory.
&dictread;
 
# Do this as many times as the user wants.
for ($count = 0; $count < $passnum; $count++) {
 
    # Initialize the variables.
    $pass = "";
    $goodpass = 0;
 
    # Run this loop until we get one we like.
    while ($goodpass != 1) {
        &passgen;
        &passcheck;
    }
 
    # Print it and we're done.
    print "$pass\n";
}
 
exit 0;
 
# This subroutine throws together the characters.
sub passgen {
 
    # Reset the variables.
    $pass = "";
 
    # Add until we have eight of 'em.
    for ($i = 0; $i < 8; $i++) {
 
        # Pull a random number out.
        $number = int(rand(36));
 
        # We only want numbers and lowercase letters.
        if ($number < 10) {
 
            # It's a number. Make it an ASCII number.
            $output = $number + 48;
        } else {
 
            # It's a letter. Make it an ASCII letter.
            $output = $number + 87;
        }
 
        # Make that ASCII number a character.
        $new = chr($output);
 
        # Append the character to the password.
        $pass = "$pass$new";
    }
}
 
# Now we decide whether this password is suitable. Now we check for dictionary
# words! (10-31-2000)
sub passcheck {
    
    # Two numbers or else!
    $pass =~ /[0-9].+[0-9]/ || return;
    
    # But, we don't want more than two.
    $pass =~ /[0-9].*[0-9].*[0-9]/ && return;
 
    # From 0-9,
    for ($i = 0; $i < 10; $i++) {
 
        # Let's make sure they're not both the same number.
        $pass =~ /$i.*$i/ && return;
    }
 
    # From "a"-"z",
    for ($i = 97; $i < 123; $i++) {
        # Check each letter.
        $chkchr = chr($i);
        # No double letters.
        $pass =~ /$chkchr{2,}/ && return;
        # No more than two of each in a password.
        $pass =~ /$chkchr.*$chkchr.*$chkchr/ && return;
    }
 
    # Check it against the dictionary.
    if ($#words) {
        foreach $word (@words) {
            # Does our password contain this real word?
            $pass =~ /$word/ && return;
        }
    }
 
    # We passed our tests.
    $goodpass = 1;
}
 
# Maybe this will make the batch processing faster, if we only access the file
# once...
sub dictread {
    # Open the dictionary file for reading.
    # You might not want to bother with this if you're doing them in
    # batches, because it makes everything crawl.
    if (open (DICTIONARY, "$dictfile")) {
        while (<DICTIONARY>) {
            # Make it case insensitive, kill those pesky newlines.
            chomp($currentword = lc($_));
 
            # If it's two or fewer letters, we're not concerned.
            if ((length($currentword) <= 2)) {
                next;
            }
 
            # Add it to the array
            push(@words, $currentword);
        }
 
        # That wasn't so bad, was it?
        close (DICTIONARY);
    }
}