#!/perl -w
# NAME: genpass.pl
# AIM: generate a STRONG random password
# ==========================================================================
# 02/04/2008 - change from using batch file to control repeats, to directly
# getting the 'Y' from within Perl. This way AVOID
# adding unaccepted passwords to the file, since the file is ONLY written
# if 'Y' is entered ...
# ==========================================================================
# 12/07/2007 - add -s to show password list
# 05/07/2007 - add LOG of passwords generated
# 24/06/2007 - removed log file.
# 20/06/2007 geoff mclane - http://goeffair.net
use strict;
use warnings;
my @items = qw( A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 
a b c d e f g h i j k l m n o p q r s t u v w x y z 1 2 3 4 5 6 7 8 9 );

my $icnt = scalar @items;
my $cntuc = 0;
my $cntlc = 0;
my $cntnm = 0;
my $cyccnt = 0;
my $pwlen = 9;
my $finalpwd = '';
my $verb = 0;
my $single = 1;	# each item used ONLY once in each sequence
my $firstval = 0;
my $capfirst = 1;
my @vals = ();
my $passfile = 'C:\GTools\perl\genpass.txt';
my @passlines = ();
my $usrname = '';
my $dry_run = 0;
my $waitinp = 1;
my $passcnt = 0;
my @prevpasses = ();

sub load_passes {
	if (open INF, "<$passfile") {
		@passlines = <INF>;
		close INF;
	}
	@prevpasses = ();
	foreach my $pss (@passlines) {
		chomp $pss;
		my @arr = split(',',$pss);
		if (length($arr[0])) {
			push(@prevpasses, $arr[0]);
		}
	}
}

sub is_in_previous {
	my ($cp) = shift;
	###prt( "Checking [$cp] no tin previous ...\n" );
	foreach my $ps (@prevpasses) {
		###prt( "Comparing [$cp] with [$ps] ...\n" );
		if ($cp eq $ps) {
			###prt( "Found ...\n" );
			return 1;
		}
	}
	###prt( "$cp NOT FOUND ...\n" );
	return 0;
}

sub write_pass_file {
	if (open OUTF, ">$passfile") {
		foreach my $pw (@passlines) {
			chomp $pw;
			if (length($pw)) {
				print OUTF "$pw\n";
			}
		}
		close OUTF;
	}
}

sub give_help {
	prt( "genpass [Options] [User]\n" );
	prt( "Options:\n" );
	prt( " -?     This brief help.\n" );
	prt( " -c     Disable CAPITAL first ...\n" );
#	prt( " -d pwd Delete this entry.\n" );
	prt( " -l N   Number of charaters in password. Min. is 5. Default is $pwlen\n" );
	prt( " -s     Show current password list, if any ...\n" );
	mydie( " -v     Run in verbose mode.\n" );
}

sub show_passes {
	my $max = scalar @passlines;
	my $msg = '';
	if ($max) {
		prt( "List of $max passwords ...\n" );
		for (my $i = 0; $i < $max; $i++) {
			my $ps = $passlines[$i];
			chomp $ps;
			my @arr = split(',',$ps);
			my $len = scalar @arr;
			if ($len) {
				if ($len >= 2) {
					$msg = "pwd: ".$arr[0].",".$arr[1];
					for (my $i = 2; $i < $len; $i++) {
						$msg .= ','.$arr[$i];
					}
					prt( "$msg\n" );
				} else {
					prt( "pwd: ".$arr[0].",<none>\n" );
				}
			}
		}
	} else {
		prt( "Presently NO password list ...\n" );
	}
	exit(2);
}

sub delete_pass {
	my ($pwd) = shift;
	my $max = scalar @passlines;
	if ($max) {
		prt( "List of $max passwords ...\n" );
		for (my $i = 0; $i < $max; $i++) {
			my $ps = $passlines[$i];
			my @arr = split(',',$ps);
			if ($arr[0] eq $pwd) {

			}
		}
	} else {
		prt( "Presently NO password list ...\n" );
	}

	exit(3);
}

# Ensure argument exists, or die.
sub require_arg {
    my ($arg, @arglist) = @_;
    mydie( "ERROR: no argument given for option '$arg' ...\n" ) if ! @arglist;
}

sub parse_args {
	my (@av) = @_;
	my $ic = 0;
	my $un = 0;
	while (@av) {
		my $arg = $av[0];
		if ($arg =~ /^-{1}/) {
			if (($arg eq '-h')||($arg eq '-?')) {
				give_help();
#			} elsif ($arg eq '-d') {
#				require_arg(@av);
#				shift @av;
#				$arg = $av[0];
#				prt( "Delete password $arg ...\n" );
#				delete_pass($arg);
			} elsif ($arg eq '-s') {
				show_passes();
			} elsif ($arg eq '-v') {
				$verb = 1;
				prt( "Set verbose output ...\n" );
			} elsif ($arg eq '-c') {
				$capfirst = 0;
				prt( "Disabled CAPS first ...\n" );
			} elsif ($arg eq '-l') {
				require_arg(@av);
				shift @av;
				$pwlen = $av[0];
				prt( "Set password length to $pwlen ...\n" );
			} elsif ($arg eq '-dry-run') {
				$dry_run = 1;
				prt( "Set DRY RUN ONLY ...\n" );
			} else {
				mydie( "ERROR: Option [$arg] UNKNOWN! Try -? for HELP ...\n" );
			}
		} elsif (length($arg)) {
			$usrname .= ',' if length($usrname);
			$usrname .= $arg;
			$un = 1;
		}
		shift @av;
		$ic++;
	}
	if ($pwlen < 5) {
		mydie( "ERROR: Password can NOT be less than 5 characters\n" );
	}
	prt( "Set username to $usrname ...\n" ) if ($un);
}

load_passes();
parse_args(@ARGV);
###prt( "52 value = ". $items[52] . "\n" );
prt( "From $icnt characters i. A-Z, ii. a-z, iii. 1-9 ... Note no ZERO!\n" ) if ($verb);
prt( "Random password of length $pwlen ... with at least 1 from each group.\n" ) if ($verb);

sub get_password {
	my $lastpwd = $finalpwd;
	while( ($cntuc == 0) || ($cntlc == 0) || ($cntnm == 0) || ($firstval >= 52) ||
		($cntnm > 2)) {
		$cyccnt++;
		$cntuc = 0;
		$cntlc = 0;
		$cntnm = 0;
		$firstval = 0;
		@vals = ();
		$finalpwd = get_r_pass();
		if (is_in_previous($finalpwd)) {
			$cntuc = 0;
		} else {
			if ($single) {
				my @arr = split('',$finalpwd);
				my $max = scalar @arr;
				for (my $i = 0; $i < $max; $i++) {
					my $itm1 = $arr[$i];
					for (my $j = 0; $j < $max; $j++) {
						if ($i != $j) {
							my $itm2 = $arr[$j];
							if ($itm1 eq $itm2) {
								$cntuc = 0;
								$cntlc = 0;
								$cntnm = 0;
								last;
							}
						}
					}
				}
			}
		}
	}
}

sub get_r_pass {
	my $pw = '';
	my $val = int(rand($icnt));
	if ($capfirst) {
		while ($val >= 26) {
			$val = int(rand($icnt));
		}
	}
	push(@vals, $val);
	$firstval = $val;
	my $itm = $items[$val];
	$pw .= $itm;
	#prt( "$itm" );
	if ($val < 26) {
		$cntuc++;
	} elsif ($val < 52) {
		$cntlc++;
	} else {
		$cntnm++;
	}
	for (my $i = 1; $i < $pwlen; $i++) {
		$val = int(rand($icnt));
		push(@vals, $val);
		$itm = $items[$val];
		$pw .= $itm;
		#prt( "$itm" );
		if ($val < 26) {
			$cntuc++;
		} elsif ($val < 52) {
			$cntlc++;
		} else {
			$cntnm++;
		}
	}
	return $pw;
}

$cntuc = 0;
get_password();
prt("With $cntuc Upper, $cntlc Lower, $cntnm Number [in $cyccnt cycles]\n") if ($verb);
if ($dry_run) {
	prt("$finalpwd ($cyccnt) usr=[$usrname] DRY RUN ONLY!\n");
} else {
	$usrname = '<none given>' if (length($usrname) == 0);
	while ($waitinp) {
		prt("pwd=[ $finalpwd ] ($cyccnt) usr=[$usrname]\n" );
		prt( "Enter Y to accept, Q to exit, U=User Name, else cycle : " );
		my $inp = <STDIN>;
		chomp $inp;
		if (lc($inp) eq 'y') {
			push(@passlines, "$finalpwd,$usrname");
			$passcnt = scalar @passlines;
			prt( "Adding [$finalpwd] for usr=[$usrname]\nto [$passfile] ... total $passcnt lines ...\n" );
			write_pass_file();
			$waitinp = 0;
		} elsif ( uc($inp) eq 'Q' ) {
			prt( "Quitting ... with no pass update ...\n" );
			$waitinp = 0;
		} elsif ( uc(substr($inp,0,2)) eq 'U=' ) {
			$usrname = substr($inp,2);
			prt( "Set User Name to [$usrname] ...\n" );
		} else {
			prt( "Cycle for NEXT password generation ...\n" );
			push(@prevpasses, $finalpwd);
			$cntuc = 0;	# kill this to enter ...
			get_password();
		}
	}
}

exit(0);	# ZERO is SUCCESS pwd generation ...

sub prt {
	my ($txt) = shift;
	print $txt;
}

sub mydie {
	my ($txt) = shift;
	prt($txt);
	exit(1);
}

# eof - genpass.pl

