#!/usr/local/bin/perl # Copyright 1996 PHRI Mark Bergman # Script to check passwords for strength. Does NOTHING with the password # besides "checking" so it's "safe". Of course, in a multi-user # environment, the security of this program is only as good as the # communications path used to access the script and the machine it runs # on... eval 'exec /usr/local/bin/perl -S $0 ${1+"$@"}' if $running_under_some_shell; #use diagnostics; use integer; use vars qw($ohome $home $gid $shell $nmon $nmday); require 'look.pl'; require "flush.pl"; # for flush() $running_under_some_shell=""; $RCSversion='$Header: /home/bergman/Bin/RCS/checkpass,v 1.7 2001/05/30 19:17:04 bergman Exp bergman $'; ############### Initializations ####################################### # # $EXPLICIT="NO WAY, MAN!"; $DEBUG = 0; $DELAY=.25; # Delay to insert between checks...useful if the program # is really being used to change passwords, and we want to # slow things down enough to prevent brute forcing, and to # give users enough time to read the diagnostics $PASSMIN=7; # Minimum password length $PASSMAX=8; # Maximum password length @words=('/usr/share/dict/words','/usr/dict/words','./badwords'); # # #################### START OF STANDARD SUBROUTINES ################### ($NAME=$0)=~s#^.*/##; sub version { # Display the version number and date of modification extracted from # the RCS header. $RCSversion=~s/\S+\s\S+\s(\S+\s\S+).*/$1/; print $0 . ": " . $RCSversion . "\n"; exit 0 } sub limits { print "$NAME: No internal limits beyond perl\n"; print "\n"; print "Requires:\n"; print "\tflush.pl\tto flush() stderr\n"; print "\tlook.pl\tfor dictionary lookups\n"; exit 0; } sub usage { print "$NAME [-V|-L] [-x] [-M maximum_pass] [-m minimum_pass] [-d dict]\n"; print "\n"; print "-V\tversion information\n"; print "-L\tlimitations\n"; print "-x\tbe explicit about rejected passwords.\n\t** May expose your desired password in clear text. **\n"; print "-M\tspecify maximum password length (default $PASSMAX)\n"; print "-m\tspecify minimum password length, must be > 1 (default $PASSMIN)\n"; print "-d\tadditional dictionaries, defaults to:\n\t\t@words\n"; print "\n"; print "While the old password is prompted for, it's only used to ensure that\n"; print "the new one differs significantly; anything can be entered.\n"; print "\n"; print "Nothing is stored on disk, and no external processes are called; the\n"; print "program is reasonably secure. Passwords are vulnerable to being read\n"; print "from memory while the program is running, so it should only be run on\n"; print "a secure, single-user machine.\n"; print "\t@_\n"; exit 1; } sub debug { # Handle debugging. The debug routine depends on the presence of # the variable "DEBUG", which should be set as follows: # =\d only print debug statements that exactly match # the specified level # # \d print debug statements at or less than the level local($level,$statement)=@_; my $debug=$DEBUG; if ( $DEBUG !~ /=\d+/ ) { if ( $debug >= $level ) { print $statement; } } else { $debug=~s/=//; if ( $debug == $level ) { print $statement; } } } sub myGetopts { # Usage: # do Getopts('a:bc'); # -a takes arg. -b & -c not. Sets opt_* as a # # side effect. local($argumentative) = @_; local(@args,$_,$first,$rest); local($errs) = 0; @args = split( / */, $argumentative ); while(@ARGV && ($_ = $ARGV[0]) =~ /^-(.)(.*)/) { ($first,$rest) = ($1,$2); $pos = index($argumentative,$first); if($pos >= 0) { if($pos < $#args && $args[$pos+1] eq ':') { shift(@ARGV); if($rest eq '') { ++$errs unless @ARGV; $rest = shift(@ARGV); } ${"opt_$first"} = $rest; } else { ${"opt_$first"} = 1; if($rest eq '') { shift(@ARGV); } else { $ARGV[0] = "-$rest"; } } } else { return(1); ++$errs; if($rest ne '') { $ARGV[0] = "-$rest"; } else { shift(@ARGV); } } } return(0); } sub parse { if ( myGetopts("VLm:M:d:x") != 0 ) { usage("Invalid option"); } if ( defined($opt_V) && $opt_V == 1 ) { &version(); } if ( defined($opt_L) && $opt_L == 1 ) { &limits(); } if ( defined($opt_d) && "$opt_d" ne "1" ) { push(@words,$opt_d); # Add the specified dictionary to the array } if ( defined($opt_M) && "$opt_M" ne "1" ) { $PASSMAX=$opt_M; } if ( defined($opt_m) && "$opt_m" ne "1" ) { $PASSMIN=$opt_m; } if ( defined($opt_x) && $opt_x == 1 ) { $EXPLICIT="YES"; } } #################### END OF STANDARD SUBROUTINES parse(); debug(2,"\$EXPLICIT=\"$EXPLICIT\"\n"); select (STDOUT) ; $| = 1; # Un-buffer ouput. select (STDERR) ; $| = 1; # Un-buffer ouput. $;="\000"; # Set delim for arrays to be NUL. # Security blankets. $path=""; $ENV{'IFS'} = '' if $ENV{'IFS'}; $ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin:/usr/ucb:/usr/lib/nis'; $path=$ENV{'PATH'}; $ENV{'SHELL'}='/bin/sh'; umask (022); chop($host =`/bin/hostname`); # Make a list of dictionaries to search with &look $fh = 'dictaa'; foreach $dict (@words) { open($fh,$dict) && push(@dicts, eval "*$fh"); $fh++; } # Get passwd entry and remember all logins $login = ''; $me = getlogin || getpwuid($<) unless $me; open(PASSWD, ") { /^([^:]+)/; if ($1 eq $me) { ($ologin,$opasswd,$ouid,$ogid,$ogcos,$ohome,$oshell) = split(/:/); $login=$ologin; $uid=$ouid; $gid=$ogid; $home=$ohome; $shell=$oshell; die "You aren't you! ($< $uid $me $login)\n" if $< && $< != $uid; # just being paranoid... $salt = substr($opasswd,0,2); # Canonicalize name. $ogcos =~ s/,.*//; $mynames = $ogcos; $mynames =~ s/&/$login/g; $mynames =~ s/\W+/ /; $mynames =~ s/^ //; $mynames =~ s/ $//; $mynames =~ s/ . / /g; $mynames =~ s/ . / /g; $mynames =~ s/^. //; $mynames =~ s/ .$//; $mynames =~ s/ /|/; $mynames = '^$' if $mynames eq '' ; } ++$isalogin{$1} if length ($1) >= 6; } close(PASSWD); die "$me isn't in the passwd file.\n" unless $login; # Finally we can begin. # Turn off echo system 'stty', '-echo'; if ($<) { print "Old password: " ; chop($pass0 = ); system 'stty', 'echo'; print "\n"; # Note: We shouldn't use die while echo is off. myexit(1) unless $pass0; } # Pick a password for (;;) { $goodenough = 0; until ($goodenough) { $permcheck="no"; $delay=$DELAY; system 'stty', '-echo'; print "New password: "; chop($pass1 = ); system 'stty', 'echo'; print "\n"; myexit(1) unless $pass1; #print "\tChecking for poor passwords.\n"; $goodenough = &goodenough($pass1); if ( ! $goodenough ) { print ""; select(undef,undef,undef,0.5); print ""; } print "\n"; # If longer than $PASSMAX chars, check first $PASSMAX chars alone. if ($goodenough && length($pass1) > $PASSMAX) { $passtruncated = substr($pass1,0,$PASSMAX); print "\tRechecking first $PASSMAX ...\n"; unless ($goodenough = &goodenough($passtruncated)) { print "\n(Note that only the first $PASSMAX count.)\n"; print ""; select(undef,undef,undef,0.5); print ""; } #print "\n"; } if ( $goodenough && ( grep(/[0-9@!]/,$pass1) gt 0 ) ) { # There are numbers in the password... # Permute the numbers into letters, re-check # each permutation. $permcheck="yes"; $delay="0"; $noprefix=""; $nosuffix=""; if ( $pass1 =~ /^[^A-Za-z]/ ) { ($noprefix = $pass1) =~ s/^[^A-Za-z]*//; if ( (length($noprefix) <= 2 )) { $noprefix=""; } } if ( $pass1 =~ /[^A-Za-z]$/ ) { ($nosuffix = $pass1) =~ s/[^A-Za-z]*$//; if ( (length($nosuffix) <= 2 )) { $nosuffix=""; } } if ( $noprefix =~ /[^A-Za-z]$/ ) { ($noprenosuffix = $noprefix) =~ s/[^A-Za-z]*$//; if ( (length($nosuffix) <= 2 )) { $noprenosuffix=""; } } &permute("$pass1 $noprefix $nosuffix $noprenosuffix"); print "\tChecking $#tumbled permutations"; $PASSMIN=2; # Reset the minimum length to deal with the # tumbled passwords that are stripped on non-alphabetic # prefixes and suffixes. foreach $word (@tumbled ) { print "."; if ( $word =~ /^\S+$/ ) { debug(5,"Checking \"$word\" returned from permuting numbers into letters.\n"); $goodenough = &goodenough($word); if (! $goodenough ) { if ( $EXPLICIT =~ /^YES$/ ) { print "\nA permutation of numbers into letters results in a weak password ($word).\n"; } else { print "\nA permutation of numbers into letters results in a weak password.\n"; } print ""; select(undef,undef,undef,0.5); print ""; exit 3; } } } print "!\n"; } if ( $goodenough ) { print "\tNot a weak password after permutating numbers into letters...good.\n"; # print "\n"; } }; print "\tYour password was well-chosen.\n"; system 'stty', '-echo'; print "Confirm new passwd: "; chop($pass2 = ); system 'stty', 'echo'; print "\n"; if ($pass1 eq $pass2) { last; } else { print "Password mismatch-try again.\n"; exit 3; } } # Turn the echo back on system 'stty', 'echo'; ################################################### # This subroutine is the whole reason for this program. It # # checks for many different kinds of bad password. We # # don't tell people what kind of pattern they MUST have, # # because that would reduce the search space unnecessarily. # # # goodenough( ) returns 1 if password passes muster, else 0. # ################################################### sub goodenough { $pass = shift (@_); $mono = $pass !~/^.+([A-Z].*[a-z]|[a-z].*[A-Z])/; $mono = 0 if $pass =~ /[^a-zA-Z0-9 ]/; $now = time; $nsec=""; $nmin=""; $nhour=""; ($nsec,$nmin,$nhour,$nmday,$nmon,$nyear) = localtime($now); if ( $permcheck ne "yes" ) { if ( $pass =~ /^[A-Za-z]+$/ ) { print "\nYour password must contain at least one character that's not a letter.\n"; return 0; } print "\tNot just letters...good.\n"; select(undef,undef,undef,$delay); } # Embedded null can spoof crypt routine. if ($pass =~ /\0/) { print "\nPlease don't use the null character in your password.\n"; return 0; } if ( $permcheck ne "yes" ) { print "\tNo NULL characters...good.\n"; } select(undef,undef,undef,$delay); # Same password they just had? if (crypt($pass,$salt) eq $opasswd) { print "\nPlease use a different password than you just had.\n"; return 0; } if ( $permcheck ne "yes" ) { print "\tNot your previous password...good.\n"; } select(undef,undef,undef,$delay); # Too much like the old password? if ($pass0 && length($pass0) == length($pass)) { $diff = 0; for ($i = length($pass)-1; $i >= 0; --$i) { ++$diff if substr($pass,$i,1) ne substr($pass0,$i,1); } if ($diff <= 2) { print "\nThat's too close to your old password.\n"; return 0; } } if ( $permcheck ne "yes" ) { print "\tNot too similar to your previous password...good.\n";} select(undef,undef,undef,$delay); # Too short? Get progressively nastier. if (length($pass) < $PASSMIN) { printf "Too short.\n"; exit 4; } if ( $permcheck ne "yes" ) { print "\tNot too short...good.\n";} select(undef,undef,undef,$delay); # Is it in one of the dictionaries? ($foo = $pass) =~ y/A-Z/a-z/; # Truncate common suffixes before searching dict. $shorte = ''; $short = $pass; $even = ($short =~ s/\d+$//) ? " (even with a number) " : ""; $short =~ s/s$//; $short =~ s/ed$// && ($shorte = "${short}e"); $short =~ s/er$// && ($shorte = "${short}e"); $short =~ s/ly$//; $short =~ s/ing$// && ($shorte = "${short}e"); ($cshort = $short) =~ y/A-Z/a-z/; # We'll iterate over several dictionaries. @tmp = @dicts; while ($dict = shift(@tmp)) { local(*DICT) = $dict; debug(2,"Checking in \"$dict\" for \"$short\"\n"); # Do the lookup (dictionary order, case folded) &look($dict,$short,1,1); debug(2,"Checking in \"$dict\" for \"$short\"\n"); while () { ($cline = $_) =~ y/A-Z/a-z/; last if substr($cline,0,length($short)) ne $cshort; chop; ($_,$response) = split(/\t+/); if ($pass eq $_ || ($pass eq substr($_,0,$PASSMAX)) || ($pass =~ /^$_$/i && $mono) || $shorte eq $_ || ($shorte =~ /^$_$/i && $mono) || $short eq $_ || ($short =~ /^$_$/i && $mono)) { if ($response) { # Has a snide remark. if ( $permcheck ne "yes" ) { print $response, "\nPlease try again.\n"; } } elsif (/^[A-Z]/) { if (/a$|ie$|yn$|een$|is$/) { if ( $permcheck ne "yes" ) { print "\nDon't you use HER name that way!\n"; } } else { $also = ' also'; if ( $permcheck ne "yes" ) { print "\nThat name is$also too popular.\n"; } } } else { if ( $permcheck ne "yes" ) { print "\nPlease avoid words in the dictionary$even.\n"; } } return 0; } } } if ( $permcheck ne "yes" ) { print "\tNot in the dictionary...good.\n";} select(undef,undef,undef,$delay); $ssap=reverse $pass; ($foo = $ssap) =~ y/A-Z/a-z/; # Truncate common suffixes before searching dict. $shorte = ''; $short = $ssap; $even = ($short =~ s/\d+$//) ? " (even with a number) " : ""; $short =~ s/s$//; $short =~ s/ed$// && ($shorte = "${short}e"); $short =~ s/er$// && ($shorte = "${short}e"); $short =~ s/ly$//; $short =~ s/ing$// && ($shorte = "${short}e"); ($cshort = $short) =~ y/A-Z/a-z/; # We'll iterate over several dictionaries. @tmp = @dicts; while ($dict = shift(@tmp)) { local(*DICT) = $dict; # Do the lookup (dictionary order, case folded) &look($dict,$short,1,1); debug(2,"Checking in \"$dict\" for \"$short\"\n"); while () { ($cline = $_) =~ y/A-Z/a-z/; last if substr($cline,0,length($short)) ne $cshort; chop; ($_,$response) = split(/\t+/); if ($ssap eq $_ || ($ssap eq substr($_,0,$PASSMAX)) || ($ssap =~ /^$_$/i && $mono) || $shorte eq $_ || ($shorte =~ /^$_$/i && $mono) || $short eq $_ || ($short =~ /^$_$/i && $mono)) { if ($response) { # Has a snide remark. if ( $permcheck ne "yes" ) { print $response, " Please try again.\n"; } } elsif (/^[A-Z]/) { if (/a$|ie$|yn$|een$|is$/) { if ( $permcheck ne "yes" ) { print "\nDon't you use HER name that way!\n"; } } else { $also = ' also'; if ( $permcheck ne "yes" ) { print "\nThat name is$also too popular.\n"; } } } else { print "\nPlease avoid reversed words in the dictionary$even.\n"; } return 0; } } } if ( $permcheck ne "yes" ) { print "\tNot in the dictionary reversed...good.\n";} select(undef,undef,undef,$delay); # Now check for two word-combinations. This gets hairy. # We look up everything that starts with the same first # two letters as the password, and if the word matches the # head of the password, we save the rest of the password # in %others to be looked up later. Passwords which have # a single char before or after a word are special-cased. # We take pains to disallow things like "CamelAte", # "CameLate" and "CamElate" but allow things like # "CamelatE" or "CameLAte". # If the password is exactly $PASSMAX characters, we also have # to disallow passwords that consist of a word plus the # BEGINNING of another word, such as "CamelFle", which # will warn you about "camel" and "flea". %others = ( ); ($cpass = $pass) =~ y/A-Z/a-z/; ($oneup) = $pass =~ /.[a-z]*([A-Z][a-z]*)$/; $cpass =~ s/ //g; if ($pass !~ /.+[A-Z].*[A-Z]/) { $others{substr($cpass,1,999)}++ if $pass =~ /^..[a-z]+$/; @tmp = @dicts; while ($dict = shift(@tmp)) { local(*DICT) = $dict; $two = substr($cpass,0,2); &look($dict,$two,1,1); debug(2,"Checking in \"$dict\" for \"$two\"\n"); $two++; word: while () { chop; s/\t.*//; y/A-Z/a-z/; last if $_ ge $two; if (index($cpass,$_) == 0) { $key = substr($cpass,length($_),999); next word if $key =~ /\W/; $others{$key}++ unless $oneup && length($oneup) != length($key); } } } @tmp = @dicts; while ($dict = shift(@tmp)) { local(*DICT) = $dict; foreach $key (keys(%others)) { &look($dict,$key,1,1); debug(2,"Checking in \"$dict\" for \"$key\"\n"); $_ = ; chop; s/\t.*//; if ($_ eq $key || length($pass) == $PASSMAX && /^$key/) { $pre = substr($cpass,0,length($cpass) - length($key)); if (length($pre) == 1) { $pre = sprintf("^%c", ord($pre)^64) unless $pre =~ /[ -~]/; print "\nOne char \"$pre\" plus a word like \"$_\" is too easy to guess.\n"; return 0; } if ( $EXPLICIT eq "YES" ) { print "\nPlease avoid two-word combinations like \"$pre\" and \"$_\".\n"; }else { print "\nPlease avoid two-word combinations.\n"; } #if ( $permcheck ne "yes" ) { print "\nSuggestion: insert a random character in one of the words,\n"; } #if ( $permcheck ne "yes" ) { print "\nor misspell one of them.\n"; } return 0; } elsif (length($key) == 1 && $pass =~ /^.[a-z]+.$/) { chop($pre = $cpass); $key = sprintf("^%c", ord($key)^64) unless $key =~ /[-~]/; print "\nA word like \"$pre\" plus one char \"$key\" is too easy to guess.\n"; return 0; } } } } if ( $permcheck ne "yes" ) { print "\tNot a dictionary word with a simple change...good.\n";} select(undef,undef,undef,$delay); # Check for naughty words. :-) if ($pass =~ /(fuck|shit|piss|damn|hell|cunt|unix|dick|ibm|dec|sun|at&t|nasa|mac)/i) { if ( $EXPLICIT eq "YES" ) { print "\nA common substring such as \"$1\" makes your password too easy to guess.\n"; } else { print "\nA common substring makes your password too easy to guess.\n"; } return 0; } if ( $permcheck ne "yes" ) { print "\tNot a common substring...good.\n";} select(undef,undef,undef,$delay); # Does it look like a date? if ($pass =~ m!^[-\d/]*$!) { if ($pass =~ m!^\d{3}-\d{2}-\d{4}$! || $pass =~ m!-\d\d\d\d\d\d\d\d\d$!) { print "\nPlease don't use a pattern similar to a Social Security Number!\n"; return 0; } if ( $pass =~ /^\$[0-9]*\.[0-9]*[+-]*.*tax/i ) { print "\nPlease don't use a pattern similar to a sticker price\n"; return 0; } if ($pass =~ m!^\d*/\d*/\d*$! || $pass =~ m!^\d*-\d*-\d*$! || $pass =~ m!$nyear$!) { print "Please don't use dates.\n"; return 0; } if ($pass =~ m!^\d\d\d-?\d\d\d\d$!) { print "\nPlease don't use a phone number.\n"; return 0; } if ($pass =~ m!^\d{6,9}$!) { if ( $permcheck ne "yes" ) { print "\nPlease don't use a sequence of numbers.\n"; } return 0; } } if ($mo = ($pass =~ /^[ \d]*([a-zA-Z]{3,5})[ \d]*$/) && ($mo =~ /^(jan|feb|mar(ch)?|apr(il)?|may|june?)$/i || $mo =~ /^(july?|aug|sept?|oct|nov|dec)$/i) ) { print "Please don't use dates.\n"; return 0; } if ( $permcheck ne "yes" ) { print "\tNot a pattern similar to a date/SSN/short number/phone number...good.\n";} select(undef,undef,undef,$delay); # Login id? if ($pass =~ /$me/i) { print "\nPlease don't use part your name.\n"; return 0; } if ( $permcheck ne "yes" ) { print "\tNot your login name...good.\n";} select(undef,undef,undef,$delay); # My own name? if ($pass =~ /$mynames/i) { print "\nPlease don't use part of your name.\n"; return 0, } if ( $permcheck ne "yes" ) { print "\tNot part of your name...good.\n";} select(undef,undef,undef,$delay); # My host name? if ($pass =~ /$host/i) { print "\nPlease don't use your host name.\n"; return 0; } if ( $permcheck ne "yes" ) { print "\tNot the name of the computer you are using...good.\n";} select(undef,undef,undef,$delay); # License plate number? if ($pass =~ /^\d?[a-zA-Z][a-zA-Z][a-zA-Z]\d\d\d$/ || $pass =~ /^\d\d\d[a-zA-Z][a-zA-Z][a-zA-Z]$/ || $pass =~ /^\d\d\d[a-zA-Z][a-zA-Z][a-zA-Z]$/ || $pass =~ /^[a-zA-Z][a-zA-Z][a-zA-Z]\d\d\d$/ ) { print "\nPlease don't use pattern similar to a license plate number.\n"; return 0; } if ( $permcheck ne "yes" ) { print "\tNot a license-plate pattern...good.\n";} select(undef,undef,undef,$delay); if ( $pass =~ /^\$[0-9]*\.[0-9]*[+-]*.*tax/i ) { print "\nPlease don't use a pattern similar to a sticker price\n"; return 0; } if ( $permcheck ne "yes" ) { print "\tNot a sticker price pattern...good.\n";} select(undef,undef,undef,$delay); # A function key? (This pattern checks Sun-style fn keys.) if ($pass =~ /^\033\[\d+/) { print "\nPlease don't use a function key.\n"; return 0; } if ( $permcheck ne "yes" ) { print "\tDoes not contain SUN function key characters...good.\n";} select(undef,undef,undef,$delay); # A sequence of closely related ASCII characters? @ary = unpack('C*',$pass); $ok = 0; for ($i = 0; $i < $#ary; ++$i) { $diff = $ary[$i+1] - $ary[$i]; $ok = 1 if $diff > 1 || $diff < -1; } if (!$ok) { print "\nPlease don't use ASCII sequences.\n"; return 0; } if ( $permcheck ne "yes" ) { print "\tDoes not contain a sequence of ASCII characters...good.\n";} select(undef,undef,undef,$delay); # A sequence of keyboard keys? ($foo = $pass) =~ y/A-Z/a-z/; $foo =~ y/qwertyuiop[ ]asdfghjkl; 'zxcvbnm,.\//a-la-ka-j/; $foo =~ y/=-0987654321\][poiuytrewq';lkjhgfdsa\/.,mnbvcxz/a-la-ka-j/; $foo =~ y/~!@#\$%^&*( )_+|-/abcdefghijklmn/; $foo =~ y/-1234567890=\\'/kabcdefghijlmn/; # $foo =~ y/\/.,mnbvc/; # $foo =~ y/';lkjhgf/; # $foo =~ y/\\][poiuy/; # $foo =~ y/|}{poiuytrewq":lkjhgfdsa?> 1 || $diff < -1; } if (!$ok) { print "\nPlease don't use consecutive keys.\n"; return 0; } if ( $permcheck ne "yes" ) { print "\tDoes not contain a keyboard sequence...good.\n";} select(undef,undef,undef,$delay); # Repeated patterns: ababab, abcabc, abcdabcd if ( $pass=~ /^(..)\1\1/ || $pass=~ /^(...)\1/ || $pass=~ /^(.)\1\1/ || $pass =~ /^(....)\1/ ) { if ( $EXPLICIT eq "YES" ) { print "\nPlease don't use repeated sequences of \"$1\".\n"; } else { print "\nPlease don't use repeated sequences.\n"; } return 0; } if ( $permcheck ne "yes" ) { print "\tDoes not contain a sequence of repeated characters...good.\n";} select(undef,undef,undef,$delay); # Reversed patterns: abccba abcddcba if ( $pass =~ /^(.)(.)(.)\3\2\1/ || $pass =~ /^(.)(.)(.)(.)\4\3\2\1/ ) { if ( $EXPLICIT eq "YES" ) { print "\nPlease don't use palindromic sequences of \"$1$2$3$4\".\n"; }else { print "\nPlease don't use palindromic sequences.\n"; } return 0; } if ( $permcheck ne "yes" ) { print "\tDoes not contain a reversed sequence of repeated characters...good.\n";} select(undef,undef,undef,$delay); # Some other login name? if ($isalogin{$pass}) { print "\nPlease don't use somebody's login id.\n"; return 0; } if ( $permcheck ne "yes" ) { print "\tNot someone else's login name...good.\n";} select(undef,undef,undef,$delay); # A local host name? if (-f "/usr/hosts/$pass") { print "\nPlease don't use a local host name.\n"; return 0; } if ( $permcheck ne "yes" ) { print "\tNot the name of the local computer...good.\n";} select(undef,undef,undef,$delay); # Reversed login id? $reverse = reverse $me; if ($pass =~ /$reverse/i) { print "\nPlease don't use your login id spelled backwards.\n"; return 0; } if ( $permcheck ne "yes" ) { print "\tNot your login name reversed...good.\n";} select(undef,undef,undef,$delay); select(undef,undef,undef,$delay); 1; } sub myexit { system 'stty', 'echo'; exit shift(@_); } sub permute { # Needed: a function that accepts an array of words, substitutes the first # number in it for each possible match, and returns the resulting words. # Call this function recursively until all the returned words have no # numbers. Pass the resulting array of words to &goodenough(); @AT=(); # keep -w happy @BANG=(); @PLUS=(); @1=('a','l','i'); @2=('b','s'); @3=('c','e'); @4=('d','y'); @5=('e','s'); @6=('f','g'); @7=('g','f'); @8=('h','s'); @9=('i','d','g'); @0=('j','o','q','c','u'); @AT=('a','o'); @BANG=('i','l'); @PLUS=('t'); debug(3,"In &permute with \"@_\"\n"); @tumbled=@_; while ( grep(/[\d\@!\+]/,@tumbled) gt 0 ) { # While there are non-letters in the @tumbled array... @tumbled2=&tumble(@tumbled); debug(3, "\nIn permute(), \@tumbled2 is now \"@tumbled2\"\n"); @tumbled=@tumbled2; } @tumbled=uniqarray(@tumbled); debug(3, "\nIn permute(), uniqued \@tumbled is now \"@tumbled\"\n"); } sub tumble { local($before)=""; local($num)=""; local($after)=""; local(@newresult)=""; local(@words)=""; debug(3, "\&tumble called with \"@_\"\n"); local($words)="@_"; $words=~s/^\s\s*//; $words=~s/\s*$//; debug(3, "In \&tumble, \$words=\"$words\"\n"); @words=split(/\s+/,$words); foreach $word ( @words ) { debug(3, "Substituting in word: \"$word\"\n"); if ( $word =~ /[0-9@\!\+]/ ) { $_=$word; ($before,$num,$after)=/^([^0-9@\!\+]*)([0-9@\!\+])(.*)/; $num=~s/\@/AT/; $num=~s/!/BANG/; $num=~s/\+/PLUS/; foreach $sub ( @$num ) { $newword=$before . $sub . $after; push(@newresult,$newword); debug(3, "In &tumble, generated new word: \"$newword\"\n"); } } else { if ( $word =~ /^\S+$/ ) { push(@newresult,$word); debug(3, "In &tumble, generated new word: \"$word\"\n"); } } } debug(3, "In \&tumble, returning \"@newresult\"\n"); return(@newresult); } sub uniqarray { my @input=@_; my @uniqarray; my $item=""; my $prev=""; @input=sort(@input); foreach $item ( @input ) { if ( $item ne $prev ) { push(@uniqarray,$item); $prev=$item; } } debug(3, "In uniqarray(), returning \"@uniqarray\"\n"); return(@uniqarray); }