3

I'm trying to use Perl's do EXPR function as a poor man's config parser, using a second .pl file that just returns a list as configuration information. (I think this is probably the ideal use for do, not least because I can write "do or die" in my code.) Here's an example:

main.pl

# Go read the config file
my %config = do './config.pl';

# do something with it
$web_object->login($config{username}, $config{password});

config.pl

# Configuration file for main script
(
  username => "username",
  password => "none_of_your_business",
  favorite_color => "0x0000FF",
);

Reading Perldoc for do gives a lot of helpful advice about relative paths - searching @INC and modifying %INC, special warnings about 5.26 not searching "." any more, etc. But it also has these bits:

# load the exact specified file (./ and ../ special-cased)...


Using do with a relative path (except for ./ and ../), like...

And then it never actually bothers to explain the Special Case path handling for "./" or "../" - an important omission!

So my question(s) are all variations on "what really happens when you do './file.pl';"? For instance...

  • Does this syntax still work in 5.26, though CWD is removed from @INC?
  • From whose perspective is "./" anyway: the Perl binary, the Perl script executed, CWD from the user's shell, or something else?
  • Are there security risks to be aware of?
  • Is this better or worse than modifying @INC and just using a base filename?

Any insight is appreciated.

Community
  • 1
  • 1
Greg Kennedy
  • 430
  • 4
  • 23
  • Re "*And then it never actually bothers to explain the Special Case path handling for "./" or "../" - an important omission!*", Huh, "`do './stat.pl'` is largely like `eval \`cat stat.pl\`;`" – ikegami Apr 12 '18 at 06:03
  • 1
    Re "*From whose perspective is "./" anyway*", Perl just passes the path to the OS, to whom `.` references the CWD. – ikegami Apr 12 '18 at 06:06
  • Re "*Are there security risks to be aware of?*", Forget security risks, how about getting it to work at all. Don't make assumptions about the CWD. [Solutions](https://stackoverflow.com/questions/46549671/doesnt-perl-include-current-directory-in-inc-by-default/46550384#46550384). (And yes, it's a security risk for setuid scripts.) – ikegami Apr 12 '18 at 06:07
  • 2
    The questions being answered, I'd like to add -- why would you want to do that? Just use the full path properly, like in the link from ikegami for instance. – zdim Apr 12 '18 at 06:33

1 Answers1

5

OK, so - to start with, I'm not sure your config.pl is really the right approach - it's not perl for starters, because it doesn't compile. Either way though, trying to evaluate stuff to 'parse config' isn't a great plan generally - it's rather prone to unpleasant glitches and security flaws, so should be reserved for when it's needed.

I would urge you to do it differently by either:

Write it as a module

Something like this:

package MyConfig;

# Configuration file for main script
our %config = (
   username       => "username",
   password       => "none_of_your_business",
   favorite_color => "0x0000FF",
);

You could then in your main script:

use MyConfig; #note - the file needs to be the same name, and in @INC

and access it as:

print $MyConfig::config{username},"\n"; 

If you can't put it in the existing @INC - which there may be reasons you can't, FindBin lets you use paths relative to your script location:

use FindBin; 
use lib "$FindBin::Bin"; 
use MyConfig;

Write your 'config' as a defined parsable format, rather than executable code.

YAML

YAML is very solid for a config file particularly:

use YAML::XS;

open ( my $config_file, '<', 'config.yml' ) or die $!; 
my $config = Load ( do { local $/; <$config_file> });

print $config -> {username};

And your config file looks like:

username: "username"
password: "password_here"
favourite_color: "green"
air_speed_of_unladen_swallow: "african_or_european?"

(YAML also supports multi-dimensional data structures, arrays etc. You don't seem to need these though.)

JSON

JSON based looks much the same, just the input is:

{
  "username": "username",
  "password": "password_here",
  "favourite_color": "green",
  "air_speed_of_unladen_swallow": "african_or_european?"
}

You read it with:

use JSON;
open ( my $config_file, '<', 'config.json' ) or die $!; 
my $config = from_json ( do { local $/; <$config_file> });

Using relative paths to config:

You don't have to worry about @INC at all. You can simply use based on relative path... but a better bet would be to NOT do that, and use FindBin instead - which lets you specify "relative to my script path" and that's much more robust.

 use FindBin;
 open ( my $config_file, '<', "$FindBin::Bin/config.yml" ) or die $!; 

And then you'll know you're reading the one in the same directory as your script, no matter where it's invoked from.

specific questions:

From whose perspective is "./" anyway: the Perl binary, the Perl script executed, CWD from the user's shell, or something else?

Current working directory passes down through processes. So user's shell by default, unless the perl script does a chdir

Are there security risks to be aware of?

Any time you 'evaluate' something as if it were executable code (and EXPR can be) there's a security risk. It's probably not huge, because the script will be running as the user, and the user is the person who can tamper with CWD. The core risks are:

  • user is in a 'different' directory where someone else has put a malicious thing for them to run. (e.g. imagine of 'config.pl' had rm -rf /* in it for example). Maybe there's a 'config.pl' in /tmp that they 'run' accidentally?
    • The thing you're evaling has a typo, and breaks the script in funky and unexpected ways. (E.g. maybe it redefines $[ and messes with program logic henceforth in ways that are hard to debug)
  • script does anything in a privileged context. Which doesn't appear to be the case, but see the previous point and imagine if you're root or other privileged user.

Is this better or worse than modifying @INC and just using a base filename?

Worse IMO. Actually just don't modify @INC at all, and use a full path, or relative one using FindBin. And don't eval things when it's not necessary.

Sobrique
  • 52,974
  • 7
  • 60
  • 101
  • 1
    related: https://stackoverflow.com/questions/5969417/why-is-it-a-bad-idea-to-write-configuration-data-in-code – daxim Apr 16 '18 at 10:08
  • It's funny that `do` to parse a config file is apparently frowned upon now... because I got the idea specifically from the official Perl documentation! http://perldoc.perl.org/functions/do.html (scroll to the bottom) – Greg Kennedy May 01 '18 at 14:04
  • That's an example of how you _can_ use it, not to say that you must. Both `do` and `eval` have a place - they _do_ let you run almost arbitrary/user specified code. By doing that, you're introducing a hazard into your code - sometimes it's worth the risk, but for something simple like 'load a config file'... it's simply not worth a run time 'exploding messily' when a simpler and safer approach is available. – Sobrique May 01 '18 at 14:18