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";

Reply via email to