#!/usr/bin/perl -w # GenPass.pl--Generate random passwords from the CLI or as a Web CGI # $Id$ # $URL$ my $VERSION = '$Version: 3.0 $'; my $COPYRIGHT = 'Copyright 2001-2005 JP Vossen (http://www.jpsdomain.org/)'; my $LICENSE = 'GNU GENERAL PUBLIC LICENSE'; my $USAGE = ''; # Placeholder for usage info below #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ((my $PROGRAM = $0) =~ s/^.*(\/|\\)//ig); # remove up to last "\" or "/" # This sub is here for quick documentation purposes. Other subs at bottom. sub Usage { # Called like: Usage ({exit code}) my $exit_code = $_[0] || 1; # Default of 1 if exit code not specified # Unlike sh, Perl does not have a built in way to skip leading # TABs (but not spaces) to allow indenting in HERE docs. So we cheat. ($USAGE = sprintf <<"EoN") =~ s/^\t//gm; NAME ${PROGRAM}--Generate random passwords from the CLI or as a Web CGI SYNOPSIS * If the script name ends in '.cgi' it will run as a CGI script and ignore the options below. You should also add '-T' to the #! line. * If the script name ends in '.pl' it will run as below: $PROGRAM [OPTIONS] -a|{ulnpx} [-s #] [-c #] OPTIONS -u = Use UPPER case letters -l = Use lower case letters -n = Use numbers -p = Use some punctuation -P = Use all punctuation -a = Same as -ulnp (default) -A = Same as -ulnP -x = Use only mixed case Hex characters ( 0..9, a..z, A..Z) -s {size} = Size of the password (default = 20) -c {count} = Count of how many passwords to generate (default = 10) -h = This usage -v = Be verbose -V = Show version, copyright and license information Examples, if no options are given runs as: $PROGRAM -a -c 10 -s 20 DESCRIPTION ($VERSION) Create reasonably random passwords and/or unpronounceable alien names for Science Fiction stories. If -a is used, it takes precedence over ANY other character set option and silently overrides it. BUGS It would be nice if -T was always there so you don't have to remember to add it, but I can't get that to work in CLI mode on Windows-- I get a 'Too late for "-T" option' error. :-( AUTHOR / BUG REPORTS JP Vossen (jp {at} jpsdomain {dot} org) http://www.jpsdomain.org/ COPYRIGHT & LICENSE $COPYRIGHT $LICENSE SEE ALSO http://sourceforge.net/projects/passwordsafe/ EoN print STDERR ("$USAGE"); # Print the usage exit $exit_code; # exit with the specified error code } # end of usage # Declare everything to keep -w and use strict happy our ($opt_u, $opt_l, $opt_n, $opt_p, $opt_P, $opt_a, $opt_A, $opt_x, $opt_s, $opt_c, $opt_h, $opt_v, $opt_V); our (@newpass, $HTMLBodyOptions); use strict; # For CGI use, set simple page formatting my $HTML_bgcolor = '#BABDD3'; my $HTML_background = 'http://www.jpsdomain.org/images/blutxtr1.jpg'; ########################################################################## # Main # Possible character sets our %char_set = ('upper' => join("",'A' .. 'Z'), 'lower' => join("",'a' .. 'z'), 'numbers' => join("",'0' .. '9'), 'somepunct' => '%!@$^&*~#+=', 'allpunct' => '%!@$^&*~#+=()_`-[]{}\\|;":\',./?', 'hex' => join("", 0 .. 9, 'a' .. 'f', 'A' .. 'F') ); # Selection of sets to actually use (options) our %use = ('upper' => 0, 'lower' => 0, 'numbers' => 0, 'somepunct' => 0, 'allpunct' => 0, 'hex' => 0 ); # Default specifications for length and count our %specs = ( 'size' => 20, 'count' => 10 ); if ($0 =~ m/\.cgi$/i) { # If my name ends in .cgi RunAsCGI(); # Run as a CGI program } elsif ($0 =~ m/\.pl$/i) { RunAsScript(); # Run as a command line interface (CLI) script } elsif ($^O eq "MSWin32") { # Oh no die ("Script does not end in '.cgi' or '.pl' so I don't know what to do!\n"); } # End of main ########################################################################## # Subroutines #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # Emit version and other information # Called like: Version ({exit code}) sub Version { my $exit_code = $_[0] || 1; # Default of 1 if exit code not specified print ("$PROGRAM version $VERSION\n\t$COPYRIGHT\n\t$LICENSE\n"); exit $exit_code; # exit with the specified error code } # end of sub Version #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # Run as a command line interface (CLI) script # Called like: RunAsScript() sub RunAsScript { my $newpass; use Getopt::Std; getopts('ulnpPaAxs:c:vV'); Usage(0) if $opt_h; Version(0) if $opt_V; # Set the options, first the detfault of -a if nothing was specified if (not ($opt_a or $opt_A or $opt_x or $opt_u or $opt_l or $opt_n or $opt_p or $opt_P)) { foreach (keys %use) { $use{$_} = 1; } $use{"allpunct"} = 0; $use{"hex"} = 0; } elsif ($opt_a) { # -u, -l, -n, -p foreach (keys %use) { $use{$_} = 1; } $use{"allpunct"} = 0; $use{"hex"} = 0; } elsif ($opt_A) { # -u, -l, -n, -P foreach (keys %use) { $use{$_} = 1; } $use{"somepunct"} = 0; $use{"hex"} = 0; } elsif ($opt_x) { # -x $use{"hex"} = 1; } else { # ala cart $use{"upper"} = 1 if $opt_u; $use{"lower"} = 1 if $opt_l; $use{"numbers"} = 1 if $opt_n; $use{"somepunct"} = 1 if $opt_p; $use{"allpunct"} = 1 if $opt_P; } # end of option set # Set the specs $specs{"size"} = $opt_s if $opt_s; $specs{"count"} = $opt_c if $opt_c; &BuildArray; # Actually do the work if ($opt_v) { # If we're being verbose print STDERR ("$PROGRAM version $VERSION\n\t$COPYRIGHT\n\t$LICENSE\n\n"); foreach $newpass (@newpass) { print ("Random password: $newpass\n"); } } else { foreach $newpass (@newpass) { print ("$newpass\n"); } } # end of if if ($opt_v) { print STDERR ("\n\a$PROGRAM finished in ",time()-$^T," seconds.\n"); } return 1; } # end of sub RunAsScript #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # Run as a CGI program # Called like: RunAsCGI() sub RunAsCGI { my ($set, $newpass, @char_set_out, @char_set_in, @checked); use CGI qw(:standard escapeHTML); use CGI::Pretty qw(:standard); # Emit slighly less hidiously formatted HTML # use CGI::Carp qw(fatalsToBrowser); # Sometimes helpful in debugging # Load possible char sets into an array to use with CGI's checkbox_group # Note the names of the options are the same as the values. foreach $set (sort values %char_set) { push (@char_set_out, $set) }; # Create the default checked list push (@checked, $char_set{"upper"}); push (@checked, $char_set{"lower"}); push (@checked, $char_set{"numbers"}); push (@checked, $char_set{"somepunct"}); # Generate an HTML form to get options print header(), # Send the HTML header start_html(-title=>"$PROGRAM $VERSION", -bgcolor=>"$HTML_bgcolor", -background=>"$HTML_background"); # CGI_Diagnostics(); # Display table of server and client debug diags print h1("Generate random passwords..."); # Header # General version and copyright info print ("$PROGRAM (source code)
"); print ("$VERSION
$COPYRIGHT

"); print hr; # Horizontal rule print start_form, # Start a form ("How many passwords (1-255)? ", textfield("HOW_MANY",$specs{"count"},4,3),"
"), ("How long should each password be (1-255)? ", textfield("SIZE",$specs{"size"},4,3),"

"), ("Hint: use the hex option with a length of 10 to generate random 40-bit WEP keys, or a length of 26 for 128-bit WEP keys.

"), ("Which character sets should be used (note that angle brackets are omitted due to issues with the resulting HTML code)?
"), # This group of checkboxes gets both its label AND value from the @char_set array built above ("

"),checkbox_group("CHARSET",\@char_set_out,\@checked,'true'),("
"), p(""), submit ("Generate Random Password(s)"); # Button to gen passwords print hr; # Horizontal rule # Overwrite the defaults with the form values, IF ANY $specs{"size"} = param("SIZE") if param("SIZE"); $specs{"count"} = param("HOW_MANY") if param("HOW_MANY"); @char_set_in = param("CHARSET") || @checked; foreach $set (@char_set_in) { # Flip all the flags # Since we're using checkboxes, you can and will have more than one choice!!! $use{"upper"} = 1 if ($set eq $char_set{"upper"}); $use{"lower"} = 1 if ($set eq $char_set{"lower"}); $use{"numbers"} = 1 if ($set eq $char_set{"numbers"}); $use{"somepunct"} = 1 if ($set eq $char_set{"somepunct"}); $use{"allpunct"} = 1 if ($set eq $char_set{"allpunct"}); } # end of foreach charset &BuildArray; # Actually do the work print h2 qq($specs{"count"} random password(s) of length $specs{"size"}:\n); # Header foreach $newpass (@newpass) { print ("${newpass}
\n"); # print random password(s) } print hr; # Horizontal rule print end_html; # Close HTML and end the page/program return; } # end of sub RunAsCGI #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # # Called like: BuildArray() sub BuildArray { my @chars; # Do some checking to prevent DoS attacks if ($specs{"count"} > 255) { warn ("Count greater than 255 passwords not allowed, using 255\n"); $specs{"count"} = 255; } if ($specs{"size"} > 255) { warn ("Size greater than 255 not allowed, using 255\n"); $specs{"size"} = 255; } # Build the array of valid password characters # This section, and the join below adapted from pg 52 of # _Perl_Cookbook_ 1 or page 72 of _Perl_Cookbook_ 2 # Basically, build an array (list) of the characters to randomly # select to build the password push (@chars, split('',$char_set{"upper"})) if $use{"upper"}; push (@chars, split('',$char_set{"lower"})) if $use{"lower"}; push (@chars, split('',$char_set{"numbers"})) if $use{"numbers"}; push (@chars, split('',$char_set{"somepunct"})) if $use{"somepunct"}; push (@chars, split('',$char_set{"allpunct"})) if $use{"allpunct"}; push (@chars, split('',$char_set{"hex"})) if $use{"hex"}; for (1..$specs{"count"}) { # Build the new password array by extracting random slices of the # array (i.e. single character) then joining them until we have a # random character string of the correct length push (@newpass, join ("", @chars[ map { rand @chars } (1 .. $specs{"size"}) ]) ); } return; } # end of sub BuildArray #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # Try some simple CGI diags if the script doesn't work. Also, try running # perl -T GenPass.cgi from the command line. You should get valid HTML # with no errors. If you do, you probably have a CGI, wen server or file # permittion (like lack of execute privs.) on the server side. # Called like: CGIDiagnostics() sub CGI_Diagnostics { print("", Tr(th ("Server Information")), Tr(td ("Server Name:"), td("$ENV{'SERVER_NAME'}")), Tr(td ("Server Using Port:"), td("$ENV{'SERVER_PORT'}")), Tr(td ("Server Software:"), td("$ENV{'SERVER_SOFTWARE'}")), Tr(td ("Server Protocol:"), td("$ENV{'SERVER_PROTOCOL'}")), Tr(td ("Server CGI Revision:"), td("$ENV{'GATEWAY_INTERFACE'}")), Tr(),Tr(), Tr(th ("Client/Browser Information")), Tr(td ("Media types accepted by your browser:"), td("$ENV{'HTTP_ACCEPT'}")), Tr(td ("Cookies defined for the URL:"), td("$ENV{'HTTP_COOKIE'}")), Tr(td ("Your E-mail address:"), td("$ENV{'HTTP_FROM'}")), Tr(td ("Last URL you read:"), td("$ENV{'HTTP_REFERER'}")), Tr(td ("Your Browser:"), td("$ENV{'HTTP_USER_AGENT'}")), Tr(td ("Your IP address:"), td("$ENV{'REMOTE_ADDR'}")), Tr(td ("Your hostname:"), td("$ENV{'REMOTE_HOST'}")), "
Diagnostic Information:
"); return; } # end of sub CGI_Diagnostics