Author: spadkins
Date: Sat Aug 28 14:37:13 2010
New Revision: 14348
Modified:
p5ee/trunk/App-Options/CHANGES
p5ee/trunk/App-Options/TODO
p5ee/trunk/App-Options/lib/App/Options.pm
p5ee/trunk/App-Options/t/main.t
Log:
Implemented secure attribute for options (i.e. passwords)
Modified: p5ee/trunk/App-Options/CHANGES
==============================================================================
--- p5ee/trunk/App-Options/CHANGES (original)
+++ p5ee/trunk/App-Options/CHANGES Sat Aug 28 14:37:13 2010
@@ -2,13 +2,11 @@
# CHANGE LOG
#############################################################################
-VERSION 1.08
+VERSION 1.1
x Supports the "secure" option attribute. (Also, all options which end in
"pass" or "password"
are assumed to be secure.) The value is a security level: 1=[don't print
the value in a help screen].
2=[ensure that the value can never be supplied on a command line or from
the environment but
only from a file that only the user running the program has read/write
access to]
- Note: secure=>2 and the security_policy_level variable are not yet
implemented.
- (It is expected that they will be implemented before the end of 2010.)
VERSION 1.07
x Automagically add $PREFIX/lib/perl5 to @INC (only if it exists), else
$PREFIX/lib/perl
Modified: p5ee/trunk/App-Options/TODO
==============================================================================
--- p5ee/trunk/App-Options/TODO (original)
+++ p5ee/trunk/App-Options/TODO Sat Aug 28 14:37:13 2010
@@ -3,7 +3,21 @@
######################################################################
TODO
+
o Implement {security_policy_level} variable
+ o "secure" attribute (to ensure that passwords are only stored in files not
+ readable by "world", and never in %ENV)
+
+ o Support "-f <file>" format of options (single-letter includes an arg
+ o enforce other option parsing rules (single letter + arg, single/double dash)
+ o option aliases/synonyms/alternates (i.e. -s = --silent)
+
+ o "strict" option:
+ 0 = no strictness
+ 1 = unknown cmd line options cause error (file can define options),
+ 2 = [1] + silently don't include env+file options not defined by program
+ 3 = [1+2] + unknown cmd line options cause error (program only can
define options)
+ 4 = [1+2+3] + file options not defined by program cause errors
These items are what will be required to go to the next release to CPAN
o Get the documentation to match the new organization of the code
@@ -16,19 +30,9 @@
o clean up use of --version_modules (when to show all)
o incorporate LWP::UserAgent->get() as a standard way to get a conf
o VERSION option in program sets the $main::VERSION
- o "strict" option:
- 0 = no strictness
- 1 = unknown cmd line options cause error (file can define options),
- 2 = [1] + silently don't include env+file options not defined by program
- 3 = [1+2] + unknown cmd line options cause error (program only can
define options)
- 4 = [1+2+3] + file options not defined by program cause errors
o make lots more tests (starting with the examples in the documentation)
o make example scripts (starting with the examples in the documentation)
- o enforce other option parsing rules (single letter + arg, single/double dash)
o consider ISO std datetimes: T instead of space, Z suffix, timezone suffix
- o option aliases/synonyms/alternates (i.e. -s = --silent)
- o "secure" attribute (to ensure that passwords are only stored in files not
- readable by "world", and never in %ENV)
o write "prefix.pod"
o try use lib "dir"; instead of unshift(@INC,"dir") (interaction with "arch")
o consider checking the PERL5LIB variable under -T
Modified: p5ee/trunk/App-Options/lib/App/Options.pm
==============================================================================
--- p5ee/trunk/App-Options/lib/App/Options.pm (original)
+++ p5ee/trunk/App-Options/lib/App/Options.pm Sat Aug 28 14:37:13 2010
@@ -14,7 +14,7 @@
use File::Spec;
use Config;
-$VERSION = "1.08";
+$VERSION = "1.1";
=head1 NAME
@@ -45,6 +45,7 @@
The "dbname" and other options could also be set in one of the
following configuration files
+ /etc/app/policy.conf
$HOME/.app/prog.conf
$HOME/.app/app.conf
$PROGDIR/prog.conf
@@ -258,17 +259,17 @@
description - printed next to the option in the "usage" page
secure - identifies an option as being "secure" (i.e. a password)
and that it should never be printed in plain text in a help
- message (-?). All options which end in "pass" or "password"
- are also assumed to be secure. If the value of the "secure"
- attribute is greater than 1, a heightened security level is
- enforced: 2=ensure that the value can never be supplied on a
- command line or from the environment but only from a file that
- only the user running the program has read/write access to.
- This value will also never be read from the environment or
- the command line because these are visible to other users.
- If the security_policy_level variable is set, any true value
- for the "secure" attribute will result in the value being set
- to the "security_policy_level" value.
+ message (-?). All options which end in "pass", "passwd", or
+ "password" are also assumed to be secure unless a secure => 0
+ setting exists. If the value of the "secure" attribute is greater
+ than 1, a heightened security level is enforced: 2=ensure that
+ the value can never be supplied on a command line or from the
+ environment but only from a file that only the user running the
+ program has read/write access to. This value will also never be
+ read from the environment or the command line because these are
+ visible to other users. If the security_policy_level variable
+ is set, any true value for the "secure" attribute will result in
+ the value being set to the "security_policy_level" value.
value_description - printed within angle brackets ("<>") in the
"usage" page as the description of the option value
(i.e. --option_name=<value_description>)
@@ -342,6 +343,7 @@
=cut
my ($default_option_processor); # a reference to the singleton App::Options
object that parsed the command line
+my (%path_is_secure);
# This translates the procedural App::Options::import() into the class method
App::Options->_import() (for subclassing)
sub import {
@@ -398,48 +400,48 @@
# populate "option" (the information about each option!)
#######################################################################
- my ($var, $value, @vars, $option);
+ my ($var, $value, @vars);
my $init_args = $self->{init_args};
- $option = $init_args->{option};
- my (%secure_options, %files_with_secure_options, %option_source);
+ my $option_defs = $init_args->{option} || {};
+ my (%secure_options, %option_source);
- if ($option) {
+ if ($option_defs) {
croak "App::Options->read_options(): 'option' arg must be a hash
reference"
- if (ref($option) ne "HASH");
+ if (ref($option_defs) ne "HASH");
- my (@args, $hash, $arg);
+ my (@args, $option_def, $arg);
# Convert archaic forms where everything is packed in a scalar, to the
newer,
# more verbose form where attributes of an option are in a hashref.
- foreach $var (keys %$option) {
- $value = $option->{$var};
+ foreach $var (keys %$option_defs) {
+ $value = $option_defs->{$var};
if (ref($value) eq "") {
- $hash = {};
- $option->{$var} = $hash;
+ $option_def = {};
+ $option_defs->{$var} = $option_def;
@args = split(/ *; */,$value);
foreach $arg (@args) {
if ($arg =~ /^([^=]+)=(.*)$/) {
- $hash->{$1} = $2;
+ $option_def->{$1} = $2;
}
- elsif (! defined $hash->{default}) {
- $hash->{default} = $arg;
+ elsif (! defined $option_def->{default}) {
+ $option_def->{default} = $arg;
}
else {
- $hash->{$arg} = 1;
+ $option_def->{$arg} = 1;
}
}
}
else {
- $hash = $value;
+ $option_def = $value;
}
- if ($hash->{secure} || $var =~ /pass(word|wd)?$/) {
- $secure_options{$var} = $hash->{secure} || 1;
+ if (! defined $option_def->{secure} && $var =~
/(pass|password|passwd)$/) {
+ $option_def->{secure} = 1;
}
}
}
if ($init_args->{options}) {
foreach $var (@{$init_args->{options}}) {
- if ($var =~ /pass(word|wd)?$/ && ! defined $secure_options{$var}) {
- $secure_options{$var} = 1;
+ if (! defined $option_defs->{$var}{secure} && $var =~
/(pass|password|passwd)$/) {
+ $option_defs->{$var}{secure} = 1;
}
}
}
@@ -470,6 +472,7 @@
my $debug_options = $values->{debug_options} || 0;
my $show_help = 0;
my $show_version = 0;
+ my $exit_status = -1;
if (! $init_args->{no_cmd_args}) {
my $options = $self->{options};
@@ -477,6 +480,10 @@
$var = $1;
$value = ($2 eq "") ? 1 : $3;
push(@$options, shift @ARGV);
+ if ($option_defs->{$var} && $option_defs->{$var}{secure} &&
defined $values->{security_policy_level} && $values->{security_policy_level} >=
2) {
+ $exit_status = 1;
+ print "Error: \"$var\" may not be supplied on the command line
because it is a secure option.\n";
+ }
$values->{$var} = $value;
$option_source{$var} = "CMDLINE";
}
@@ -516,9 +523,6 @@
$prog_dir = "." if ($prog_dir eq "");
$prog_dir = $prog_cat . $prog_dir if ($^O =~ /MSWin32/ and $prog_dir =~
m!^/!);
- print STDERR "2. Found Directory of Program. catalog=[$prog_cat]
dir=[$prog_dir] file=[$prog_file]\n"
- if ($debug_options);
-
#################################################################
# 3. guess the "prefix" directory for the entire
# software installation. The program is usually in
@@ -593,6 +597,7 @@
$values->{app} = $app;
}
print STDERR "4. Set app variable. app=[$app] origin=[$app_origin]\n" if
($debug_options);
+ #print STDERR "04 option_defs [", join("|", sort keys %$option_defs),
"]\n";
my ($env_var, @env_vars, $regexp);
if (! $init_args->{no_option_file}) {
@@ -600,6 +605,7 @@
# 5. Define the standard places to look for an option file
#################################################################
my @option_files = ();
+ push(@option_files, "/etc/app/policy.conf");
push(@option_files, $values->{option_file}) if
($values->{option_file});
push(@option_files, "$ENV{HOME}/.app/$app.conf") if ($ENV{HOME} &&
$app ne "app");
push(@option_files, "$ENV{HOME}/.app/app.conf") if ($ENV{HOME});
@@ -617,13 +623,14 @@
#################################################################
print STDERR "5. Scanning Option Files\n" if ($debug_options);
- $self->read_option_files($values, \...@option_files, $prefix);
+ $self->read_option_files($values, \...@option_files, $prefix,
$option_defs);
$debug_options = $values->{debug_options} || 0;
}
else {
print STDERR "5. Skip Option File Processing\n" if ($debug_options);
}
+ #print STDERR "05 option_defs [", join("|", sort keys %$option_defs),
"]\n" if ($prefix eq "/usr");
if ($values->{perl_restart} && !$ENV{MOD_PERL} && !$ENV{PERL_RESTART}) {
$ENV{PERL_RESTART} = 1;
exec($^X, $0, @{$self->{argv}});
@@ -641,8 +648,8 @@
push(@vars, @{$init_args->{options}});
}
- if ($option) {
- push(@vars, (sort keys %$option));
+ if ($option_defs) {
+ push(@vars, (sort keys %$option_defs));
}
print STDERR "6. Scanning for Environment Variables.\n" if
($debug_options);
@@ -651,12 +658,12 @@
if (!defined $values->{$var}) {
$value = undef;
if (!$init_args->{no_env_vars}) {
- if ($option && defined $option->{$var}{env}) {
- if ($option->{$var}{env} eq "") {
+ if ($option_defs && defined $option_defs->{$var}{env}) {
+ if ($option_defs->{$var}{env} eq "") {
@env_vars = ();
}
else {
- @env_vars = split(/[,;]/, $option->{$var}{env});
+ @env_vars = split(/[,;]/,
$option_defs->{$var}{env});
}
}
else {
@@ -694,6 +701,10 @@
$var = lc($env_var);
$var =~ s/^app_//;
if (! defined $values->{$var}) {
+ if ($option_defs->{$var} && $option_defs->{$var}{secure} &&
defined $values->{security_policy_level} && $values->{security_policy_level} >=
2) {
+ $exit_status = 1;
+ print "Error: \"$var\" may not be supplied from the
environment ($env_var) because it is a secure option.\n";
+ }
$values->{$var} = $ENV{$env_var};
$option_source{$var} = "ENV";
print STDERR " Env Var [$var] = [$value] from
[$env_var] (assumed).\n"
@@ -705,6 +716,7 @@
else {
print STDERR "6. Skipped Environment Variable Processing\n" if
($debug_options);
}
+ #print STDERR "06 option_defs [", join("|", sort keys %$option_defs),
"]\n" if ($prefix eq "/usr");
#################################################################
# 7. establish the definitive (not inferred) $prefix
@@ -722,19 +734,25 @@
$values->{prefix} = $prefix;
print STDERR "7. prefix Made Definitive [$prefix]\n" if
($debug_options);
}
+ #print STDERR "07 option_defs [", join("|", sort keys %$option_defs),
"]\n" if ($prefix eq "/usr");
#################################################################
# 8. set defaults
#################################################################
- if ($option) {
+ if ($option_defs) {
@vars = (defined $init_args->{options}) ? @{$init_args->{options}} :
();
- push(@vars, (sort keys %$option));
+ push(@vars, (sort keys %$option_defs));
print STDERR "8. Set Defaults.\n" if ($debug_options);
foreach $var (@vars) {
if (!defined $values->{$var}) {
- $value = $option->{$var}{default};
+ if (defined $option_defs->{$var} && defined
$option_defs->{$var}{default} && $option_defs->{$var}{secure} &&
+ defined $values->{security_policy_level} &&
$values->{security_policy_level} >= 2) {
+ $exit_status = 1;
+ print "Error: \"$var\" may not be supplied as a program
default because it is a secure option.\n";
+ }
+ $value = $option_defs->{$var}{default};
# do variable substitutions, var = ${prefix}/bin, var =
$ENV{PATH}
if (defined $value) {
if ($value =~ /\{.*\}/) {
@@ -753,6 +771,7 @@
else {
print STDERR "8. Skipped Defaults (no option defaults defined)\n" if
($debug_options);
}
+ #print STDERR "08 option_defs [", join("|", sort keys %$option_defs),
"]\n" if ($prefix eq "/usr");
#################################################################
# 9. add "perlinc" directories to @INC, OR
@@ -829,6 +848,7 @@
join("\n ", @INC), "\n";
}
}
+ #print STDERR "09 option_defs [", join("|", sort keys %$option_defs),
"]\n" if ($prefix eq "/usr");
#################################################################
# 10. print stuff out for options debugging
@@ -859,10 +879,10 @@
# 12. perform validations, print help, and exit
#################################################################
- my $exit_status = -1;
if ($show_help) {
$exit_status = 0;
}
+ #print STDERR "12 option_defs [", join("|", sort keys %$option_defs),
"]\n" if ($prefix eq "/usr");
#################################################################
# These are the actual Perl regular expressions which match
@@ -875,10 +895,10 @@
# 0x[0-9A-Fa-f](_?[0-9A-Fa-f])*
my ($type);
- if ($option) {
- @vars = (sort keys %$option);
+ if ($option_defs) {
+ @vars = (sort keys %$option_defs);
foreach $var (@vars) {
- $type = $option->{$var}{type};
+ $type = $option_defs->{$var}{type};
next if (!$type); # nothing to validate against
$value = $values->{$var};
next if (! defined $value);
@@ -931,12 +951,13 @@
}
}
foreach $var (@vars) {
- next if (!$option->{$var}{required} || defined $values->{$var});
+ next if (!$option_defs->{$var}{required} || defined
$values->{$var});
$exit_status = 1;
print "Error: \"$var\" is a required option but is not defined\n";
}
}
+ #print STDERR "13 option_defs [", join("|", sort keys %$option_defs),
"]\n" if ($prefix eq "/usr");
if ($exit_status >= 0) {
if ($init_args->{print_usage}) {
&{$init_args->{print_usage}}($values, $init_args);
@@ -967,6 +988,7 @@
$show_all = $init_args->{show_all};
$show_all = $values->{show_all} if (defined $values->{show_all});
$show_all = 1 if (!defined $show_all && !defined $init_args->{option} &&
!defined $init_args->{options});
+ #print "DEBUG: show_all=[$show_all] option=[$init_args->{option}]
options=[$init_args->{options}]\n" if ($values->{foo});
if ($init_args->{options}) {
@vars = @{$init_args->{options}};
}
@@ -976,27 +998,28 @@
if ($show_all) {
push(@vars, (sort keys %$values));
}
- my ($var, $value, $type, $desc, $option);
+ my ($var, $value, $type, $desc, $option_defs);
my ($var_str, $value_str, $type_str, $desc_str, $val_desc, $secure);
- $option = $init_args->{option} || {};
+ $option_defs = $init_args->{option} || {};
foreach $var (@vars) {
next if ($option_seen{$var});
$option_seen{$var} = 1;
next if ($var eq "?" || $var eq "help");
$value = $values->{$var};
- $type = $option->{$var}{type} || "";
- $desc = $option->{$var}{description} || "";
- $secure = $option->{$var}{secure};
- $secure = 1 if (! defined $secure && $var =~ /pass(word|wd)?$/);
+ $type = $option_defs->{$var}{type} || "";
+ $desc = $option_defs->{$var}{description} || "";
+ $secure = $option_defs->{$var}{secure};
+ $secure = 1 if (! defined $secure && $var =~
/(pass|password|passwd)$/);
$secure = $values->{security_policy_level} if (defined $secure &&
defined $values->{security_policy_level});
- $val_desc = $option->{$var}{value_description} || "";
+ $val_desc = $option_defs->{$var}{value_description} || "";
$var_str = ($type eq "boolean") ? $var : ($val_desc ?
"$var=<$val_desc>" : "$var=<value>");
- $value_str = $secure ? "********" : ((defined $value) ? $value :
"undef");
+ $value_str = (defined $value) ? ($secure ? "********" : $value) :
"undef";
$type_str = ($type) ? " ($type)" : "";
$desc_str = ($desc) ? " $desc" : "";
$desc_str =~ s/%/%%/g;
printf STDERR " --%-32s [%s]$type_str$desc_str\n", $var_str,
$value_str;
}
+ #print STDERR "PU option_defs [", join("|", sort keys %$option_defs),
"]\n" if ($values->{prefix} eq "/usr");
}
sub print_version {
@@ -1074,10 +1097,10 @@
}
sub read_option_files {
- my ($self, $values, $option_files, $prefix) = @_;
+ my ($self, $values, $option_files, $prefix, $option_defs) = @_;
my $init_args = $self->{init_args};
local(*App::Options::FILE);
- my ($option_file, $exclude_section, $option, $var, @env_vars, $env_var,
$value, $regexp);
+ my ($option_file, $exclude_section, $var, @env_vars, $env_var, $value,
$regexp);
my ($cond, @cond, $exclude, $heredoc_end);
my $debug_options = $values->{debug_options} || 0;
my $is_mod_perl = $ENV{MOD_PERL};
@@ -1225,12 +1248,12 @@
}
elsif (!defined $values->{$var}) {
if (!$init_args->{no_env_vars}) {
- if ($option && defined $option->{$var} && defined
$option->{$var}{env}) {
- if ($option->{$var}{env} eq "") {
+ if ($option_defs && defined $option_defs->{$var}
&& defined $option_defs->{$var}{env}) {
+ if ($option_defs->{$var}{env} eq "") {
@env_vars = ();
}
else {
- @env_vars = split(/[,;]/,
$option->{$var}{env});
+ @env_vars = split(/[,;]/,
$option_defs->{$var}{env});
}
}
else {
@@ -1253,6 +1276,13 @@
if ($debug_options >= 4);
}
print STDERR " Var Used : var=[$var]
value=[$value]\n" if ($debug_options >= 3);
+ if ($option_defs->{$var} &&
$option_defs->{$var}{secure} &&
+ defined $values->{security_policy_level} &&
$values->{security_policy_level} >= 2 && !&file_is_secure($option_file)) {
+ print "Error: \"$var\" may not be supplied
from an insecure file because it is a secure option.\n";
+ print " File: [$option_file]\n";
+ print " (The file and all of its parent
directories must be readable/writable only by the user running the program.)\n";
+ exit(1);
+ }
$values->{$var} = $value; # save all in
%App::options
}
}
@@ -1275,6 +1305,53 @@
}
}
+sub file_is_secure {
+ my ($file) = @_;
+ my ($secure, $dir);
+ my
($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks);
+ if ($^O =~ /MSWin32/) {
+ $secure = 1; # say it is without really checking
+ }
+ else {
+ $secure = $path_is_secure{$file};
+ if (!defined $secure) {
+
($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks)
= stat($file);
+ if (!($mode & 0400)) {
+ $secure = 0;
+ print "Error: Option file is not secure because it is not
readable by the owner.\n";
+ }
+ elsif ($mode & 0077) {
+ $secure = 0;
+ print "Error: Option file is not secure because it is
readable/writable by users other than the owner.\n";
+ }
+ else {
+ $dir =~ s!/?[^/]+$!!;
+ while ($dir && $secure) {
+ $secure = $path_is_secure{$file};
+ if (!defined $secure) {
+
($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks)
= stat("$dir/."); # navigate symlink to the directory
+ if ($uid >= 100 && $uid != $>) {
+ $secure = 0;
+ print "Error: Option file is not secure because a
parent directory is owned by a different user.\n";
+ print " Dir=[$dir]\n";
+ }
+ elsif ($mode & 0077) {
+ $secure = 0;
+ print "Error: Option file is not secure because a
parent directory is readable/writable by other users.\n";
+ print " Dir=[$dir]\n";
+ }
+ $path_is_secure{$file} = 1; # I don't know this yet,
but if we ever get around to asking again, it means that the directory was
secure.
+ }
+ $dir =~ s!/?[^/]+$!!;
+ }
+ $secure = 1 if (!defined $secure);
+ }
+ $path_is_secure{$file} = $secure;
+ }
+ }
+ return($secure);
+}
+
=head1 LOGIC FLOW: OPTION PROCESSING DETAILS
Basic Concept - By calling App::Options->init(),
Modified: p5ee/trunk/App-Options/t/main.t
==============================================================================
--- p5ee/trunk/App-Options/t/main.t (original)
+++ p5ee/trunk/App-Options/t/main.t Sat Aug 28 14:37:13 2010
@@ -1,4 +1,4 @@
-#!/usr/local/bin/perl -w
+#!/usr/bin/perl -w
BEGIN {
$ENV{VAR10} = "value10";