App-Fetchware

 view release on metacpan or  search on metacpan

lib/App/Fetchware/Util.pm  view on Meta::CPAN

package App::Fetchware::Util;
our $VERSION = '1.016'; # VERSION: generated by DZP::OurPkgVersion
# ABSTRACT: Miscelaneous functions for App::Fetchware.
###BUGALERT### Uses die instead of croak. croak is the preferred way of throwing
#exceptions in modules. croak says that the caller was the one who caused the
#error not the specific code that actually threw the error.
use strict;
use warnings;

use File::Spec::Functions qw(catfile catdir splitpath splitdir rel2abs
    file_name_is_absolute rootdir tmpdir);
use Path::Class;
use Net::FTP;
use HTTP::Tiny;
use Perl::OSType 'is_os_type';
use Cwd;
use App::Fetchware::Config ':CONFIG';
use File::Copy 'cp';
use File::Temp 'tempdir';
use File::stat;
use Fcntl qw(S_ISDIR :flock S_IMODE);
# Privileges::Drop only works on Unix, so only load it on Unix.
use if is_os_type('Unix'), 'Privileges::Drop';
use POSIX '_exit';
use Sub::Mage;
use URI::Split qw(uri_split uri_join);
use Text::ParseWords 'quotewords';
use Data::Dumper;

# Enable Perl 6 knockoffs, and use 5.10.1, because smartmatching and other
# things in 5.10 were changed in 5.10.1+.
use 5.010001;

# Set up Exporter to bring App::Fetchware::Util's API to everyone who use's it.
use Exporter qw( import );

our %EXPORT_TAGS = (
    UTIL => [qw(
        msg
        vmsg
        run_prog
        no_mirror_download_dirlist
        download_dirlist
        ftp_download_dirlist
        http_download_dirlist
        file_download_dirlist
        no_mirror_download_file
        download_file
        download_ftp_url
        download_http_url
        download_file_url
        do_nothing
        safe_open
        drop_privs
        write_dropprivs_pipe
        read_dropprivs_pipe
        create_tempdir
        original_cwd
        cleanup_tempdir
    )],
);

#        create_config_options

# *All* entries in @EXPORT_TAGS must also be in @EXPORT_OK.
our @EXPORT_OK = map {@{$_}} values %EXPORT_TAGS;








###BUGALERT### Add Test::Wrap support to msg() and vmsg() so that they will
#inteligently rewrap any text they receive so newly filled in variables won't
#screw up the wrapping.
sub msg (@) {

    # If fetchware was not run in quiet mode, -q.
    unless (defined $fetchware::quiet and $fetchware::quiet > 0) {
        # print are arguments. Use say if the last one doesn't end with a
        # newline. $#_ is the last subscript of the @_ variable.

lib/App/Fetchware/Util.pm  view on Meta::CPAN

    my $http = HTTP::Tiny->new(%opts);
    ###BUGALERT### Should use request() instead of get, because request can
    #directly write the chunks of the file to disk as they are downloaded. get()
    #just uses RAM, so a 50Meg file takes up 50 megs of ram, and so on.
    ###BUGALERT### Also, if you use request instead, and get chunks of bytes
    #instead of just writing them to disk, you could also use a
    #Term::ProgressBar to print a cool progress bar during the download!
    #This could also be added to the ftp downloaders too, but probably not the
    #local file:// downloaders though.
    my $response = $http->get($http_url);

    die <<EOD unless $response->{success};
App-Fetchware: run-time error. HTTP::Tiny failed to download a directory listing
of your provided lookup_url. HTTP status code [$response->{status} $response->{reason}]
HTTP headers [@{[Data::Dumper::Dumper($response)]}].
See man App::Fetchware.
EOD


    while (my ($k, $v) = each %{$response->{headers}}) {
        for (ref $v eq 'ARRAY' ? @$v : $v) {
        }
    }

    die <<EOD unless length $response->{content};
App-Fetchware: run-time error. The lookup_url you provided downloaded nothing.
HTTP status code [$response->{status} $response->{reason}]
HTTP headers [@{[Data::Dumper::Dumper($response)]}].
See man App::Fetchware.
EOD
    return $response->{content};
}



sub file_download_dirlist {
    my $local_lookup_url = shift;

    $local_lookup_url =~ s!^file://!!; # Strip scheme garbage.

    # Prepend original_cwd() if $local_lookup_url is a relative path.
    unless (file_name_is_absolute($local_lookup_url)) {
        $local_lookup_url =  catdir(original_cwd(), $local_lookup_url);
    }

    # Throw an exception if called with a directory that does not exist.
    die <<EOD if not -e $local_lookup_url;
App-Fetchware-Util: The directory that fetchware is trying to use to determine
if a new version of the software is available does not exist. This directory is
[$local_lookup_url], and the OS error is [$!].
EOD


    my @file_listing;
    opendir my $dh, $local_lookup_url or die <<EOD;
App-Fetchware-Util: The directory that fetchware is trying to use to determine
if a new version of the software is availabe cannot be opened. This directory is
[$local_lookup_url], and the OS error is [$!].
EOD
    while (my $filename = readdir($dh)) {
        # Trim the useless '.' and '..' Unix convention fake files from the listing.
        unless ($filename eq '.' or $filename eq '..') {
            # Turn the relative filename into a full pathname.
            #
            # Full pathnames are required, because lookup()'s
            # file_parse_filelist() stat()s each file using just their filename,
            # and if it's relative instead of absolute these stat() checks will
            # fail.
            my $full_path = catfile($local_lookup_url, $filename);
            push @file_listing, $full_path;
        }
    }

    closedir $dh;

    # Throw another exception if the directory contains nothing.
    # Awesome, clever, and simple Path::Class based "is dir empty" test courtesy
    # of tobyinc on PerlMonks (http://www.perlmonks.org/?node_id=934482).
    my $pc_local_lookup_url = dir($local_lookup_url);
    die <<EOD if $pc_local_lookup_url->stat() && !$pc_local_lookup_url->children();
App-Fetchware-Util: The directory that fetchware is trying to use to determine
if a new version of the software is available is empty. This directory is
[$local_lookup_url].
EOD

    return \@file_listing;
}




###BUGALERT###I'm a 190 line disaster! Please refactor me. Oh, and
#download_dirlist() too please, because I'm just a copy and paste of that
#subroutine!
sub download_file {
    my %opts;
    my $url;
    # One arg means its a $url.
    if (@_ == 1) {
       $url = shift;
    # More than one means it's a PATH, and if it's not a path...
    } elsif (@_ == 2) {
        %opts = @_;
        # Or your param wasn't PATH
        if (not exists $opts{PATH} and not defined $opts{PATH}) {
            # Use goto for cool old-school C-style error handling to avoid copy
            # and pasting or insane nested ifs.
            goto PATHERROR;
        }
    # ...then it's an error.
    } else {
        PATHERROR: die <<EOD;
App-Fetchware-Util: You can only specify either PATH or URL never both. Only
specify one or the other when you call download_file().
EOD
    }
    # Ensure the user has specified a mirror, because otherwise download_file()
    # will try to just download a path, and that's not going to work.
    if (not config('mirror') and exists $opts{PATH}
        and
    # True if lookup_url is a file and if lookup_url is undef.

lib/App/Fetchware/Util.pm  view on Meta::CPAN

fetchware to store HTTP::Tiny's output. Os error [$!]. See perldoc
App::Fetchware.
EOD
    # Write HTTP::Tiny's downloaded file to a real file on the filesystem.
    print $fh $response->{content};
    close $fh
        or die <<EOS;
App-Fetchware: run-time error. Fetchware failed to close the file it created to
save the content it downloaded from HTTP::Tiny. This file was [$filename]. OS
error [$!]. See perldoc App::Fetchware.
EOS

    # The caller needs the $filename to determine the $package_path later.
    return $filename;
}




sub download_file_url {
    my $url = shift;

    $url =~ s!^file://!!; # Strip useless URL scheme.
    
    # Prepend original_cwd() only if the $url is *not* absolute, which will mess
    # it up.
    $url = catdir(original_cwd(), $url) unless file_name_is_absolute($url);

    # Download the file:// URL to the current directory, which should already be
    # in $temp_dir, because of start()'s chdir().
    #
    # Don't forget to clear taint. Fetchware does *not* run in taint mode, but
    # for some reason, bug?, File::Copy checks if data is tainted, and then
    # retaints it if it is already tainted, but for some reason I get "Insecure
    # dependency" taint failure exceptions when drop priving. The fix is to
    # always untaint my data as done below.
    ###BUGALERT### Investigate this as a possible taint bug in perl or just
    #File::Copy. Perhaps the cause is using File::Copy::cp(copy) after drop
    #priving with data from root?
    $url =~ /(.*)/;
    my $untainted_url = $1;
    my $cwd = cwd();
    $cwd =~ /(.*)/;
    my $untainted_cwd = $1;
    cp($untainted_url, $untainted_cwd) or die <<EOD;
App::Fetchware: run-time error. Fetchware failed to copy the download URL
[$untainted_url] to the working directory [$untainted_cwd]. Os error [$!].
EOD

    # Return just file filename of the downloaded file.
    return file($url)->basename();
}







###BUGALERT### safe_open() does not check extended file perms such as ext*'s
#crazy attributes, linux's (And other Unixs' too) MAC stuff or Windows NT's
#crazy file permissions. Could use Win32::Perms for just Windows, but its not
#on CPAN. And what about the other OSes.
###BUGALERT### Consier moving this to CPAN??? File::SafeOpen????
sub safe_open {
    my $file_to_check = shift;
    my $open_fail_message = shift // <<EOE;
Failed to open file [$file_to_check]. OS error [$!].
EOE

    my %opts = @_;

    my $fh;


    # Open the file first.
    unless (exists $opts{MODE} and defined $opts{MODE}) {
        open $fh, '<', $file_to_check or die $open_fail_message;
    } else {
        open $fh, $opts{MODE}, $file_to_check or die $open_fail_message;
    }

    my $info = stat($fh);# or goto STAT_ERROR;

    # Owner must be either me (whoever runs fetchware) or superuser. No one else
    # can be trusted.
    if(($info->uid() != 0) && ($info->uid() != $<)) {
        die <<EOD;
App-Fetchware-Util: The file fetchware attempted to open is not owned by root or
the person who ran fetchware. This means the file could have been dangerously
altered, or it's a simple permissions problem. Do not simly change the
ownership, and rerun fetchware. Please check that the file [$file_to_check] has
not been tampered with, correct the ownership problems and try again.
EOD
    }

    # Check if group and other can write $fh.
    # Use 066 to detect read or write perms.
    ###BUGALERT### What does this actually test?????
    if ($info->mode() & 022) { # Someone else can write this $fh.
        die <<EOD
App-Fetchware-Util: The file fetchware attempted to open [$file_to_check] is
writable by someone other than just the owner. Fetchwarefiles and fetchware
packages must only be writable by the owner. Do not only change permissions to
fix this error. This error may have allowed someone to alter the contents of
your Fetchwarefile or fetchware packages. Ensure the file was not altered, then
change permissions to 644.
EOD
    }
    
    # Then check the directories its contained in.

    # Make the file an absolute path if its not already.
    $file_to_check = rel2abs($file_to_check);

    # Create array of current directory and all parent directories and even root
    # directory to check all of their permissions below.
    my $dir = dir($file_to_check);
    my @directories = do {
        my @dirs;
        until ($dir eq rootdir()) {

lib/App/Fetchware/Util.pm  view on Meta::CPAN

App-Fetchware-Util: The file fetchware attempted to open [$file_to_check] is
writable by someone other than just the owner. Fetchwarefiles and fetchware
packages must only be writable by the owner. Do not only change permissions to
fix this error. This error may have allowed someone to alter the contents of
your Fetchwarefile or fetchware packages. Ensure the file was not altered, then
change permissions to 644. Permissions on failed directory were:
@{[Dumper($info)]}
Umask [@{[umask]}].
EOD
        }

    }
    # Return the proven above "safe" file handle.
    return $fh;

    # Use cool C style goto error handling. It beats copy and paste, and the
    # horrible contortions needed for "structured programming."
    STAT_ERROR: {
    die <<EOD;
App-Fetchware-Util: stat($fh) filename [$file_to_check] failed! This just
shouldn't happen unless of course the file you specified does not exist. Please
ensure files you specify when you run fetchware actually exist.
EOD
    }
}



sub drop_privs {
    my $child_code = shift;
    my $regular_user = shift // 'nobody';
    my %opts = @_;

    # Need to do this in 2 places.
    my $dont_drop_privs = sub {
        my $child_code = shift;

        my $output;
        open my $output_fh, '>', \$output or die <<EOD;
App-Fetchware-Util: fetchware failed to open an internal scalar reference as a
file handle. OS error [$!].
EOD
        $child_code->($output_fh);

        close $output_fh or die <<EOD;
App-Fetchware-Util: fetchware failed to close an internal scalar reference that
was open as a file handle. OS error [$!].
EOD
        return \$output;
    };

    # Execute $child_code without dropping privs if the user's configuration
    # file is configured to force fetchware to "stay_root."
    if (config('stay_root')) {
        msg <<EOM;
stay_root is set to true. NOT dropping privileges!
EOM
        return $dont_drop_privs->($child_code);
    }

    if (is_os_type('Unix') and ($< == 0 or $> == 0)) {
        # cmd_new() needs to skip the creation of this useless directory that it
        # does not use. Furthemore, the creation of this extra tempdir is not
        # needed by cmd_new(), and this tempdir presumes start() was called
        # before drop_privs(), which is always the case except for cmd_new().
        #
        # But another case where this temp dir's creations should be skipped is
        # if start() is overridden with hook() to make start() do something
        # other than create a temp dir, because in some cases such as using VCS
        # instead of Web sites and mirrors, you do not need to bother with
        # creating a tempdir, because the working dir of the repo can be used
        # instead. Therefore, if the parent directory is not /^fetchware-$$/,
        # then we'll also skip creating the tempd dir, because it most likely
        # means that a tempdir is not needed.
        $opts{SkipTempDirCreation} = 1
            unless file(cwd())->basename() =~  /^fetchware-$$/;
        unless (exists $opts{SkipTempDirCreation}
            and defined $opts{SkipTempDirCreation}
            and $opts{SkipTempDirCreation}) {
            # Ensure that $user_temp_dir can be accessed by my drop priv'd child.
            # And only try to change perms to 0755 only if perms are not 0755
            # already.
            my $st = stat(cwd());
            unless ((S_IMODE($st->mode) & 0755) >= 0755) {
                chmod 0755, cwd() or die <<EOD;
App-Fetchware-Util: Fetchware failed to change the permissions of the current
temporary directory [@{[cwd()]} to 0755. The OS error was [$!].
EOD
            }
            # Create a new tempdir for the droped prive user to use, and be sure
            # to chown it so they can actually write to it as well.
            # $new_temp_dir does not have a semaphore file, but its parent
            # directory does, which will still keep fetchware clean from
            # deleting this directory out from underneath us.
            #
            # Also note, that cwd() is "blindly" coded here, which makes it a
            # "dependency," but drop_privs() is meant to be called after start()
            # by fetchware::cmd_*(). It's not meant to be a generic subroutine
            # to drop privs, and it's also not really meant to be used by
            # fetchware extensions mostly just fetchware itself. Perhaps I
            # should move it back to bin/fetchware???
            #
            # Also also note, that CLEANUP option is *not* specified, because
            # that can cause this directory in cases of errors, and you can't
            # track down an error in a build script if the directory everything
            # is in has been deleted.
            my $new_temp_dir = tempdir("fetchware-$$-XXXXXXXXXX",
                DIR => cwd());
            # Determine /etc/passwd entry for the "effective" uid of the
            # current fetchware process. I should use the "effective" uid
            # instead of the "real" uid, because effective uid is used to
            # determine what each uid can do, and the real uid is only
            # really used to track who the original user was in a setuid
            # program.
            my ($name, $useless, $uid, $gid, $quota, $comment, $gcos, $dir,
                $shell, $expire)
                = getpwnam(config('user') // 'nobody');
            chown($uid, $gid, $new_temp_dir) or die <<EOD;
App-Fetchware-Util: Fetchware failed to chown [$new_temp_dir] to the user it is
dropping privileges to. This just shouldn't happen, and might be a bug, or
perhaps your system temporary directory is full. The OS error was [$!].

lib/App/Fetchware/Util.pm  view on Meta::CPAN

                    # because if its printed always, it could confuse users.
                    # Because priv_drop()ing is the default, this error would be
                    # seen all the time making getting confused by it likely.
                    vmsg <<EOM;
App-Fetchware-Util: An error occured forcing fetchware to exit while fetchware
has forked to drop its root priviledges to avoid downloading files and building
programs as root. Root priviledges are only maintained to install the software
in a system directory requiring root access. The error that caused the child to
fail will have already been printed above by the child.
EOM
                    msg <<EOM;
For help troublehsooting fetchware failed inside directory:
@{[cwd()]}
EOM
                    # Keep all of fetchware's temporary files and directories
                    # around so the user has access to them, so they can be
                    # troubleshooted to see what caused the failure. 
                    $File::Temp::KEEP_ALL = 1;
                    # Exit non-zero indicating failure, because whatever the
                    # child did failed, and the child's main eval {} in
                    # bin/fetchware caught that failure, printed it to the
                    # screen, and exit()ed non-zero for failure. And since the
                    # child failed ($? >> 8 != 0), the parent should fail too.
                    exit 1;
                # If successful, return to the child a ref of @output to caller.
                } else {
                    return \$output;
                }
            # Fork succeeded, child code goes here.
            } else {
                close $readonly or die <<EOD;
App-Fetchware-Util: Failed to close $readonly pipe in child. Os error [$!].
EOD
                # Drop privs.
                # drop_privileges() dies on an error just let drop_privs() caller
                # catch it.
                my ($uid, $gid) = drop_privileges($regular_user); 


                # Execute the coderef that is supposed to be done as non-root.
                $child_code->($writeonly);

                # Now close the pipe, to avoid creating a dead pipe causing a
                # SIGPIPE to be sent to the parent.
                close $writeonly or die <<EOD;
App-Fetchware-Util: Failed to close $writeonly pipe in child. Os error [$!].
EOD

                # Exit success, because failure is only indicated by a thrown
                # exception that bin/fetchware's main eval {} will catch, print,
                # and exit non-zero indicating failure.
                # Use POSIX's _exit() to avoid calling END{} blocks. This *must*
                # be done to prevent File::Temp's END{} block from attempting to
                # delete the temp directory that the parent still needs to
                # finish installing or uninstalling. The parent's END{} block's
                # will still be called, so this just turns off the child
                # deleting the temp dir not the parent.
                _exit 0;
            }
        }    
    # Non-Unix OSes just execute the $child_code.
    } else {
        return $dont_drop_privs->($child_code);
    }
}




###BUGALERT### Add quotemeta() support to pipe parsers to help prevent attacks.



{ # Bareblock just for the $MAGIC_NUMBER.
    # Determine $front_magic
    my $front_magic;
    $front_magic = int(rand(8128389023));
    # For no particular reason convert the random integer into hex, because I
    # never  store something in decimal and then exact same thing in hex.
    $front_magic = $front_magic . sprintf("%x", $front_magic);
    # Run srand() again to change random number generator between rand() calls.
    # Not really necessary, but should make it harder to guess correct magic
    # numbers.
    srand(time());
    # Same a $front_magic.
    my $back_magic = int(rand(986487516));
    # Octal this time :) for no real reason.
    $back_magic = $back_magic . sprintf("%o", $back_magic);
    my $MAGIC_NUMBER = $front_magic 
        . 'MAGIC_NUMBER_REPLACING_NEWLINE'
        . $back_magic;

sub write_dropprivs_pipe {
    my $write_pipe = shift;

    for my $a_var (@_) {
        die <<EOD if $a_var =~ /$MAGIC_NUMBER/;
fetchware: Huh? [$a_var] has fetchware's MAGIC_NUMBER in it? This shouldn't
happen, and messes up fetchware's simple IPC. You should never see this error,
because it's not a particuarly magic number if anybody actually uses it. This is
most likely a bug, so please report it.
EOD

        # Write to the $write_pipe, but use the $MAGIC_NUMBER instead of just
        # newline.
        print $write_pipe $a_var . $MAGIC_NUMBER;
    }
}



sub read_dropprivs_pipe {
    my $output = shift;

    die <<EOD if ref($output) ne 'SCALAR';
App-Fetchware-Util: pipe_read_newling() was called with an output variable
[$output] that was not a scalar reference. It must be a scalar reference.
EOD

    my @variables;
    for my $variable (split(/$MAGIC_NUMBER/, $$output)) {

lib/App/Fetchware/Util.pm  view on Meta::CPAN

    my $fh = safe_open($file_to_check, <<EOE);
    App-Fetchware-Extension???: Failed to open file [$file_to_check]! Because of
    OS error [$!].
    EOE

    # To open for writing instead of reading 
    my $fh = safe_open($file_to_check, <<EOE, MODE => '>');
    App-Fetchware-Extension???: Failed to open file [$file_to_check]! Because of
    OS error [$!].
    EOE

safe_open() takes $file_to_check and does a bunch of file checks on that
file to determine if it's safe to open and use the contents of that file in
your program. Instead of returning true or false, it returns a file handle of
the file you want to check that has already been open for you. This is done to
prevent race conditions between the time safe_open() checks the file's safety
and the time the caller actually opens the file.

safe_open() also takes an optional second argument that specifies a caller
specific error message that replaces the generic default one.

Fetchware occasionally needs to write files especially in fetchware's new()
command; therefore safe_open() also takes the fake hash argument
C<MODE =E<gt> 'E<gt>'>, which opens the file in a mode specified by the caller.
C<'E<gt>'> is for writing for example. See C<perldoc -f open> for a list of
possible modes.

In fetchware, this subroutine is used to check if every file fetchware
opens is safe to do so. It is based on is_safe() and is_very_safe() from the
Perl Cookbook by Tom Christiansen and Nathan Torkington.

What this subroutine checks:

=over

=item *

It opens the file you give to it as an argument, and all subsequent operations
are done on the opened filehandle to prevent race conditions.

=item *

Then it checks that the owner of the specified file must be either the superuser
or the user who ran fetchware.

=item *

It checks that the mode, as returned by File::stat's overridden stat, is not
writable by group or other. Fancy MAC permissions such as Linux's extfs's
extensions and fancy Windows permissions are B<not> currently checked.

=item *

Then safe_open() stat's each and every parent directory that is in this file's
full path, and runs the same checks that are run above on each parent directory.

=item *

_PC_CHOWN_RESTRICTED is not tested; instead what is_very_safe() does is simply
always done. Because even with A _PC_CHOWN_RESTRICTED test, /home, for example,
could be 777. This is Unix after all, and root can do anything including screw
up permissions on system directories.

=back

If you actually are some sort of security expert, please feel free to
double-check if the list of stuff to check for is complete, and perhaps even the
Perl implementation to see if the subroutine really does check if
safe_open($file_to_check) is actually safe.

=over

=item WARNING

According to L<perlport>'s chmod() documentation, on Win32 perl's Unixish file
permissions arn't supported only "owner" is:

"Only good for changing "owner" read-write access, "group", and "other" bits are
meaningless. (Win32)"

I'm not completely sure this means that under Win32 only owner perms mean
something, or if just chmod()ing group or ther bits don't do anything, but
testing if group and other are rwx does work. This needs testing.

And remember this only applies to Win32, and fetchware has not yet been properly
ported or tested under Win32 yet.

=back

=head2 drop_privs()

    my $output = drop_privs(sub {
        my $write_pipe = shift;
        # Do stuff as $regular_user
        ...
        # Use write_dropprivs_pipe to share variables back to parent.
        write_dropprivs_pipe($write_pipe, $var1, $var2, ...);

        }, $regular_user
    );

    # Back in the parent, use read_dropprivs_pipe() to read in whatever
    # variables the child shared with us.
    my ($var1, $var2, ...) = read_dropprivs_pipe($output);

Forks and drops privs to $regular_user, and then executes whatever is in the
first argument, which should be a code reference. Throws an exception on any
problems with the fork.

It only allows you to specify what the lower priveledged user does. The parent
process's behavior can not be changed. All the parent does:

=over

=item *

Create a pipe to allow the child to communicate any information back to the
parent.

=item *

Read any data the child may write to that pipe.

=item *

After the child has died, collect the child's exit status.

=item *

And return the output the child wrote on the pipe as a scalar reference.

=back

Whatever the child writes is returned. drop_privs() does not use Storable or
JSON or XML or anything. It is up to you to specify how the data is to be
represented and used. However, L<read_dropprivs_pipe()> and
L<write_dropprivs_pipe()> are provided.  They provide a simple way to store
multiple variables that can have any character in them including newline. See
their documentation for details.

=over

=item SECURITY NOTICE

The output returned by drop_privs() is whatever the child wants it to be. If
somehow the child got hacked, the $output could be something that could cause
the parent (which has root perms!) to execute some code, or otherwise do
something that could cause the child to gain root access. So be sure to check
how you use drop_privs() return value, and definitley don't just string eval it.
Structure it so the return value can only be used as data for variables, and
that those variables are never executed by root.

=back

drop_privs() handles being on nonunix for you. On a platform that is not Unix
that does not have Unix's fork() and exec() security model, drop_privs() simply
executes the provided code reference I<without> dropping priveledges.

=over

=item USABILITY NOTICE 

drop_privs()'s implementation depends on start() creating a tempdir and
chdir()ing to it. Furthermore, drop_privs() sometimes creates a tempdir of its
own, and it does not do a chdir back to another directory, so drop_privs()
depends on end() to chdir back to original_cwd(). Therefore, do not use
drop_privs() without also using start() and end() to manage a temporary
directory for drop_privs().

=back

drop_privs() also supports a C<SkipTempDirCreation =E<gt> 1> option that turns
off drop_privs() creating a temporary diretory to give the child a writable
temporary directory. This option is only used by cmd_new(), and probably only
really needs to be used there. Also, note that you must provide this option
after the $child_code coderef, and the $regular user options. Like so,
C<my $output = drop_privs($child_code, $regular_user, SkipTempDirCreation =E<gt> 1>.

=head2 drop_privs() PIPE PARSING UTILITIES

drop_privs() uses a pipe for IPC between the child and the parent. This section
contains utilties that help users of drop_privs() parse the input and output
they send from the child back to the parent.

Use write_dropprivs_pipe() to send data back to the parent, that later you'll read
with read_dropprivs_pipe() back in the parent.

=head3 write_dropprivs_pipe()

    write_dropprivs_pipe($write_pipe, $variable1, $variable2, $variable3);

Simply uses the caller provided $write_pipe file handle to write the rest of its
args to that file handle separated by a I<magic number>.

This magic number is just generated uniquely each time App::Fetchware::Util is
compiled. This number replaces using newline to separate each of the variables
that write_dropprivs_pipe() writes. This way you can include newline, and in
fact anything that does not contain the magic number, which is obviously
suitably unlikely.

=over

=item UNDEF AND EMPTY STRING WARNING 

write_dropprivs_pipe() and read_dropprivs_pipe() both bizarely, accidentily
I<preserve> undef. It's really a function of Perl's C<split> operators
side-effect of returning undef when there is no data to actually return, but the
seperator actually does exist. However, do B<not> depend on this so called
"preservation", because C<''>, empty string, is converted into undef by
read_dropprivs_pipe() preventing you from distinguishing between the two values.

=back

=head3 read_dropprivs_pipe()

    my ($variable1, $variable2, $variable3) = pipe_read_newling($output);



( run in 1.188 second using v1.01-cache-2.11-cpan-64ef6c95b5d )