File Coverage

blib/lib/App/Fetchware/Util.pm
Criterion Covered Total %
statement 292 415 70.3
branch 110 240 45.8
condition 49 92 53.2
subroutine 39 44 88.6
pod 21 21 100.0
total 511 812 62.9


line stmt bran cond sub pod time code
1             package App::Fetchware::Util;
2             our $VERSION = '1.016'; # VERSION: generated by DZP::OurPkgVersion
3             # ABSTRACT: Miscelaneous functions for App::Fetchware.
4             ###BUGALERT### Uses die instead of croak. croak is the preferred way of throwing
5             #exceptions in modules. croak says that the caller was the one who caused the
6             #error not the specific code that actually threw the error.
7 53     53   22672 use strict;
  53         58  
  53         1202  
8 53     53   176 use warnings;
  53         77  
  53         1222  
9              
10 53         3058 use File::Spec::Functions qw(catfile catdir splitpath splitdir rel2abs
11 53     53   1369 file_name_is_absolute rootdir tmpdir);
  53         1846  
12 53     53   1647 use Path::Class;
  53         109269  
  53         2024  
13 53     53   28491 use Net::FTP;
  53         1817158  
  53         3101  
14 53     53   38552 use HTTP::Tiny;
  53         408655  
  53         1848  
15 53     53   2789 use Perl::OSType 'is_os_type';
  53         1669  
  53         2413  
16 53     53   215 use Cwd;
  53         77  
  53         2513  
17 53     53   6821 use App::Fetchware::Config ':CONFIG';
  53         90  
  53         7467  
18 53     53   24147 use File::Copy 'cp';
  53         78406  
  53         2922  
19 53     53   241 use File::Temp 'tempdir';
  53         62  
  53         1891  
20 53     53   196 use File::stat;
  53         58  
  53         347  
21 53     53   2205 use Fcntl qw(S_ISDIR :flock S_IMODE);
  53         58  
  53         6271  
22             # Privileges::Drop only works on Unix, so only load it on Unix.
23 53     53   26472 use if is_os_type('Unix'), 'Privileges::Drop';
  53         378  
  53         230  
24 53     53   162021 use POSIX '_exit';
  53         63  
  53         383  
25 53     53   26810 use Sub::Mage;
  53         278778  
  53         332  
26 53     53   25118 use URI::Split qw(uri_split uri_join);
  53         72722  
  53         3083  
27 53     53   21307 use Text::ParseWords 'quotewords';
  53         46833  
  53         2439  
28 53     53   229 use Data::Dumper;
  53         59  
  53         1957  
29              
30             # Enable Perl 6 knockoffs, and use 5.10.1, because smartmatching and other
31             # things in 5.10 were changed in 5.10.1+.
32 53     53   711 use 5.010001;
  53         131  
33              
34             # Set up Exporter to bring App::Fetchware::Util's API to everyone who use's it.
35 53     53   170 use Exporter qw( import );
  53         56  
  53         224852  
36              
37             our %EXPORT_TAGS = (
38             UTIL => [qw(
39             msg
40             vmsg
41             run_prog
42             no_mirror_download_dirlist
43             download_dirlist
44             ftp_download_dirlist
45             http_download_dirlist
46             file_download_dirlist
47             no_mirror_download_file
48             download_file
49             download_ftp_url
50             download_http_url
51             download_file_url
52             do_nothing
53             safe_open
54             drop_privs
55             write_dropprivs_pipe
56             read_dropprivs_pipe
57             create_tempdir
58             original_cwd
59             cleanup_tempdir
60             )],
61             );
62              
63             # create_config_options
64              
65             # *All* entries in @EXPORT_TAGS must also be in @EXPORT_OK.
66             our @EXPORT_OK = map {@{$_}} values %EXPORT_TAGS;
67              
68              
69              
70              
71              
72              
73              
74              
75             ###BUGALERT### Add Test::Wrap support to msg() and vmsg() so that they will
76             #inteligently rewrap any text they receive so newly filled in variables won't
77             #screw up the wrapping.
78             sub msg (@) {
79              
80             # If fetchware was not run in quiet mode, -q.
81 2468 100 100 2468 1 14172 unless (defined $fetchware::quiet and $fetchware::quiet > 0) {
82             # print are arguments. Use say if the last one doesn't end with a
83             # newline. $#_ is the last subscript of the @_ variable.
84 2466 100       10783 if ($_[$#_] =~ /\w*\n\w*\z/) {
85 103         11159 print @_;
86             } else {
87 2363         186228 say @_;
88             }
89             # Quiet mode is turned on.
90             } else {
91             # Don't print anything.
92 2         196 return;
93             }
94             }
95              
96              
97              
98             sub vmsg (@) {
99              
100             # If fetchware was not run in quiet mode, -q.
101             ###BUGALERT### Can I do something like:
102             #eval "use constant quiet => 0;" so that the iffs below can be resolved at
103             #run-time to make vmsg() and msg() faster???
104 5233 100 100 5233 1 26659 unless (defined $fetchware::quiet and $fetchware::quiet > 0) {
105             # If verbose is also turned on.
106 5231 100 100     17666 if (defined $fetchware::verbose and $fetchware::verbose > 0) {
107             # print our arguments. Use say if the last one doesn't end with a
108             # newline. $#_ is the last subscript of the @_ variable.
109 4400 100       42421 if ($_[$#_] =~ /\w*\n\w*\z/) {
110 1362         201876 print @_;
111             } else {
112 3038         346533 say @_;
113             }
114             }
115             # Quiet mode is turned on.
116             } else {
117             # Don't print anything.
118 2         170 return;
119             }
120             }
121              
122              
123              
124              
125              
126              
127             ###BUGALERT### Add support for dry-run functionality!!!!
128             sub run_prog {
129 199     199 1 3631 my (@args) = @_;
130              
131             # Kill weird "Insecure dependency in system while running with -T switch."
132             # fatal exceptions by clearing the taint flag with a regex. I'm not actually
133             # running in taint mode, but it bizarrely thinks I am.
134 199         582 for my $arg (@args) {
135 340 50       1646 if ($arg =~ /(.*)/) {
136 340         1389 $arg = $1;
137             } else {
138 0         0 die <
139             php.Fetchwarefile: Match anything pattern match failed! Huh! This shouldn't
140             happen, and is probably a bug.
141             EOD
142             }
143             }
144              
145             # Use Text::ParseWords quotewords() subroutine to deal with spliting the
146             # arguments on whitespace, and to properly quote and keep single and double
147             # quotes.
148 199         368 my $program;
149 199         423 ($program, @args) = map {quotewords('\s+', 1, $_)} @args;
  340         9813  
150              
151             # If fetchware is run without -q.
152 199 100 100     10176 unless (defined $fetchware::quiet and $fetchware::quiet > 0) {
153 197         638 local $" = '][';
154 197         861 vmsg <
155             Running command [$program] with options [@args].
156             EOM
157 197 50       724836 system($program, @args) == 0 or die <
158             fetchware: run-time error. Fetchware failed to execute the specified program
159             [$program] with the arguments [@args]. The OS error was [$!], and the return
160 0         0 value was [@{[$? >> 8]}]. Please see perldoc App::Fetchware::Diagnostics.
161             EOD
162             # If fetchware is run with -q.
163             } else {
164             # Use a piped open() to capture STDOUT, so that STDOUT is not printed to
165             # the terminal like it usually is therby "quiet"ing it.
166             # If not on Windows use safer open call that doesn't work on Windows.
167 2 50       24 unless (is_os_type('Windows', $^O)) {
168 2 50       5538 open(my $fh, '-|', "$program", @args) or die <
169             fetchware: run-time error. Fetchware failed to execute the specified program
170             while capturing its input to prevent it from being copied to the screen, because
171             you ran fetchware with it's --quite or -q option. The program was [$program],
172             and its arguments were [@args]. OS error [$!], and exit value [$?]. Please see
173             perldoc App::Fetchware::Diagnostics.
174             EOD
175             # Close $fh, to cause perl to wait for the command to do its
176             # outputing to STDOUT.
177 2         2342 close $fh;
178             # We're on Windows.
179             } else {
180 0 0       0 open(my $fh, '-|', "$program @args") or die <
181             fetchware: run-time error. Fetchware failed to execute the specified program
182             while capturing its input to prevent it from being copied to the screen, because
183             you ran fetchware with it's --quite or -q option. The program was [$program],
184             and its arguments were [@args]. OS error [$!], and exit value [$?]. Please see
185             perldoc App::Fetchware::Diagnostics.
186             EOD
187             # Close $fh, to cause perl to wait for the command to do its
188             # outputing to STDOUT.
189 0         0 close $fh;
190             }
191             }
192             }
193              
194              
195              
196              
197              
198              
199             ###BUGALERT### All download routines should be modified to use HTTP::Tiny's
200             #iterative download interface so I can write the downloaded files straight to
201             #disk to avoid wasting 20, 30 or 120gigs or so or whatever the file size is in
202             #memory for each downloaded file.
203              
204              
205             sub download_dirlist {
206 3     3 1 9265 my %opts;
207             my $url;
208             # One arg means its a $url.
209 3 50       52 if (@_ == 1) {
    0          
210 3         24 $url = shift;
211             # More than one means it's a PATH, and if it's not a path...
212             } elsif (@_ == 2) {
213 0         0 %opts = @_;
214             # Or your param wasn't PATH
215 0 0 0     0 if (not exists $opts{PATH} and not defined $opts{PATH}) {
216             # Use goto for cool old-school C-style error handling to avoid copy
217             # and pasting or insane nested ifs.
218 0         0 goto PATHERROR;
219             }
220             # ...then it's an error.
221             } else {
222 0         0 PATHERROR: die <
223             App-Fetchware-Util: You can only specify either PATH or URL never both. Only
224             specify one or the other when you call download_dirlist().
225             EOD
226             }
227              
228             # Ensure the user has specified a mirror, because otherwise download_file()
229             # will try to just download a path, and that's not going to work.
230 3 50 33     36 die <
231             App-Fetchware-Util: You only called download_dirlist() with just a PATH
232             parameter, but also failed to specify any mirrors in your configuration. Without
233             any defined mirrors download_dirlist() cannot determine from what host to
234             download your file. Please specify a mirror and try again.
235             EOD
236              
237             # Set up our list of urls that we'll try to download the specified PATH or
238             # URL from.
239 3 50       23 my @urls = config('mirror') if defined config('mirror');
240 3 50 33     61 if (exists $opts{PATH}
    50 33        
      33        
241             and defined $opts{PATH}
242             and $opts{PATH}) {
243             # The PATH option means that $url is not a full blown URL, but just a
244             # path without a hostname or scheme portion.
245             # Therefore, we append $url, because the PATH option means it's actually
246             # just a path, so we append it to each @url.
247 0         0 for my $mirror_url (@urls) {
248             # Use URI to replace the current path with the one the caller
249             # specified in the $url parameter.
250 0         0 my ($scheme, $auth, undef, undef, undef) = uri_split($mirror_url);
251 0         0 $mirror_url = uri_join($scheme, $auth, $opts{PATH}, undef, undef);
252             }
253             } elsif (defined $url
254             and $url) {
255             # Add $url to @urls since it too has a hostname. And use unshift
256             # to put it in the first position instead of last if you were to use
257             # push.
258 3         12 unshift @urls, $url;
259              
260             # I must parse out the path portion of the specified URL, because this
261             # path portion will be appended to the mirrors you have specified.
262 3         28 my $url_path = ( uri_split($url) )[2];
263 3         50 for my $mirror_url (@urls) {
264             # If the $mirror_url has no path...
265 3         10 my ($scheme, $auth, $path, $query, $frag) =
266             uri_split($mirror_url);
267             ###BUGALERT### Should check below also check for $query and $frag,
268             #and if the mirror has specified those to include those too???
269 3 50       32 if ($path eq '') {
270             #...then append $url's path.
271             ###BUGALERT## As shown before I was using URI's much nicer
272             #interface, but it was deleting the path instead of replacing
273             #the path! I tried reproducing this with a small test file, but
274             #it worked just fine in the small test file. So, it must be some
275             #really weird bug to fail here, but work in a smaller test file.
276             #I don't know try replacing all of the URI::Split calls with the
277             #equivelent URI->path() calls, and you'll get the weird bug.
278             #$mirror_url->path($url_path);
279 0         0 $mirror_url =
280             uri_join($scheme, $auth, $url_path, $query, $frag);
281             # But if the $mirror_url does have a path...
282             } else {
283             #...Then keep the mirrors path intact.
284             #
285             # Because if you specify a path when you define that mirror
286             # chances are you did it, because that mirror stores it in a
287             # different directory. For example Apache is /apache on some
288             # mirrors, but apache.hostname on other mirrors.
289             }
290             }
291             }
292              
293 3         8 my $dirlist;
294              
295 3         10 for my $mirror_url (@urls) {
296 3         7 eval {
297 3         25 msg "Attempting to download [$mirror_url].";
298             # Try the mirror_url directly without trying any mirrors.
299 3         26 $dirlist = no_mirror_download_dirlist($mirror_url);
300             };
301 3 50       13 if ($@) {
302 0         0 msg "Directory download attempt failed! Error was[";
303 0         0 print $@;
304 0         0 msg "].";
305             }
306              
307             # Skip the rest of the @urls after we successfully download the $url.
308 3 50       13 if (defined $dirlist) {
309 3         16 msg "Successfully downloaded the directory listing.";
310 3         10 last;
311             }
312             }
313              
314 3 50       20 die <
315             App-Fetchware-Util: Failed to download the specifed URL [$url] or path
316             [$opts{PATH}] using the included hostname in the url you specifed or any
317 0         0 mirrors. The mirrors are [@{[config('mirror')]}]. And the urls
318             that fetchware tried to download were [@urls].
319             EOD
320              
321 3         34 return $dirlist;
322             }
323              
324              
325              
326             sub no_mirror_download_dirlist {
327 3     3 1 10 my $url = shift;
328              
329 3         6 my $dirlist;
330 3 50       59 if ($url =~ m!^ftp://.*$!) {
    50          
    50          
331 0         0 $dirlist = ftp_download_dirlist($url);
332             } elsif ($url =~ m!^http://.*$!) {
333 0         0 $dirlist = http_download_dirlist($url);
334             } elsif ($url =~ m!^file://.*$!) {
335 3         17 $dirlist = file_download_dirlist($url);
336             } else {
337 0         0 die <
338             App-Fetchware: run-time syntax error: the url parameter your provided in
339             your call to download_dirlist() [$url] does not have a supported URL scheme (the
340             http:// or ftp:// part). The only supported download types, schemes, are FTP and
341             HTTP. See perldoc App::Fetchware.
342             EOD
343             }
344              
345 3         14 return $dirlist;
346             }
347              
348              
349              
350             sub ftp_download_dirlist {
351 0     0 1 0 my $ftp_url = shift;
352 0         0 $ftp_url =~ m!^ftp://([-a-z,A-Z,0-9,\.]+)(/.*)?!;
353 0         0 my $site = $1;
354 0         0 my $path = $2;
355              
356             # Add debugging later based on fetchware commandline args.
357             # for debugging: $ftp = Net::FTP->new('$site','Debug' => 10);
358             # open a connection and log in!
359 0         0 my $ftp;
360 0 0       0 $ftp = Net::FTP->new($site)
361             or die <
362             App-Fetchware: run-time error. fetchware failed to connect to the ftp server at
363             domain [$site]. The system error was [$@].
364             See man App::Fetchware.
365             EOD
366              
367 0 0       0 $ftp->login("anonymous",'-anonymous@')
368             or die <
369             App-Fetchware: run-time error. fetchware failed to log in to the ftp server at
370 0         0 domain [$site]. The ftp error was [@{[$ftp->message]}]. See man App::Fetchware.
371             EOD
372              
373              
374 0 0       0 my @dir_listing = $ftp->dir($path)
375             or die <
376             App-Fetchware: run-time error. fetchware failed to get a long directory listing
377 0         0 of [$path] on server [$site]. The ftp error was [@{[$ftp->message]}]. See man App::Fetchware.
378             EOD
379              
380 0         0 $ftp->quit();
381              
382 0         0 return \@dir_listing;
383             }
384              
385              
386              
387             sub http_download_dirlist {
388 0     0 1 0 my $http_url = shift;
389              
390             # Forward any other options over to HTTP::Tiny. This is used mostly to
391             # support changing user agent strings, but why not support them all.
392 0 0       0 my %opts = @_ if @_ % 2 == 0;
393              
394             # Append user_agent if specified.
395 0 0       0 $opts{agent} = config('user_agent') if config('user_agent');
396              
397 0         0 my $http = HTTP::Tiny->new(%opts);
398             ###BUGALERT### Should use request() instead of get, because request can
399             #directly write the chunks of the file to disk as they are downloaded. get()
400             #just uses RAM, so a 50Meg file takes up 50 megs of ram, and so on.
401             ###BUGALERT### Also, if you use request instead, and get chunks of bytes
402             #instead of just writing them to disk, you could also use a
403             #Term::ProgressBar to print a cool progress bar during the download!
404             #This could also be added to the ftp downloaders too, but probably not the
405             #local file:// downloaders though.
406 0         0 my $response = $http->get($http_url);
407              
408 0 0       0 die <{success};
409             App-Fetchware: run-time error. HTTP::Tiny failed to download a directory listing
410             of your provided lookup_url. HTTP status code [$response->{status} $response->{reason}]
411 0         0 HTTP headers [@{[Data::Dumper::Dumper($response)]}].
412             See man App::Fetchware.
413             EOD
414              
415              
416 0         0 while (my ($k, $v) = each %{$response->{headers}}) {
  0         0  
417 0 0       0 for (ref $v eq 'ARRAY' ? @$v : $v) {
418             }
419             }
420              
421 0 0       0 die <{content};
422             App-Fetchware: run-time error. The lookup_url you provided downloaded nothing.
423             HTTP status code [$response->{status} $response->{reason}]
424 0         0 HTTP headers [@{[Data::Dumper::Dumper($response)]}].
425             See man App::Fetchware.
426             EOD
427 0         0 return $response->{content};
428             }
429              
430              
431              
432             sub file_download_dirlist {
433 9     9 1 623 my $local_lookup_url = shift;
434              
435 9         35 $local_lookup_url =~ s!^file://!!; # Strip scheme garbage.
436              
437             # Prepend original_cwd() if $local_lookup_url is a relative path.
438 9 50       36 unless (file_name_is_absolute($local_lookup_url)) {
439 0         0 $local_lookup_url = catdir(original_cwd(), $local_lookup_url);
440             }
441              
442             # Throw an exception if called with a directory that does not exist.
443 9 100       481 die <
444             App-Fetchware-Util: The directory that fetchware is trying to use to determine
445             if a new version of the software is available does not exist. This directory is
446             [$local_lookup_url], and the OS error is [$!].
447             EOD
448              
449              
450 7         14 my @file_listing;
451 7 50       271 opendir my $dh, $local_lookup_url or die <
452             App-Fetchware-Util: The directory that fetchware is trying to use to determine
453             if a new version of the software is availabe cannot be opened. This directory is
454             [$local_lookup_url], and the OS error is [$!].
455             EOD
456 7         305 while (my $filename = readdir($dh)) {
457             # Trim the useless '.' and '..' Unix convention fake files from the listing.
458 239 100 100     595 unless ($filename eq '.' or $filename eq '..') {
459             # Turn the relative filename into a full pathname.
460             #
461             # Full pathnames are required, because lookup()'s
462             # file_parse_filelist() stat()s each file using just their filename,
463             # and if it's relative instead of absolute these stat() checks will
464             # fail.
465 225         406 my $full_path = catfile($local_lookup_url, $filename);
466 225         456 push @file_listing, $full_path;
467             }
468             }
469              
470 7         67 closedir $dh;
471              
472             # Throw another exception if the directory contains nothing.
473             # Awesome, clever, and simple Path::Class based "is dir empty" test courtesy
474             # of tobyinc on PerlMonks (http://www.perlmonks.org/?node_id=934482).
475 7         39 my $pc_local_lookup_url = dir($local_lookup_url);
476 7 100 66     612 die <stat() && !$pc_local_lookup_url->children();
477             App-Fetchware-Util: The directory that fetchware is trying to use to determine
478             if a new version of the software is available is empty. This directory is
479             [$local_lookup_url].
480             EOD
481              
482 5         19846 return \@file_listing;
483             }
484              
485              
486              
487              
488             ###BUGALERT###I'm a 190 line disaster! Please refactor me. Oh, and
489             #download_dirlist() too please, because I'm just a copy and paste of that
490             #subroutine!
491             sub download_file {
492 5     5 1 144 my %opts;
493             my $url;
494             # One arg means its a $url.
495 5 100       27 if (@_ == 1) {
    50          
496 2         6 $url = shift;
497             # More than one means it's a PATH, and if it's not a path...
498             } elsif (@_ == 2) {
499 3         16 %opts = @_;
500             # Or your param wasn't PATH
501 3 0 33     20 if (not exists $opts{PATH} and not defined $opts{PATH}) {
502             # Use goto for cool old-school C-style error handling to avoid copy
503             # and pasting or insane nested ifs.
504 0         0 goto PATHERROR;
505             }
506             # ...then it's an error.
507             } else {
508 0         0 PATHERROR: die <
509             App-Fetchware-Util: You can only specify either PATH or URL never both. Only
510             specify one or the other when you call download_file().
511             EOD
512             }
513             # Ensure the user has specified a mirror, because otherwise download_file()
514             # will try to just download a path, and that's not going to work.
515 5 0 66     29 if (not config('mirror') and exists $opts{PATH}
    0 33        
    0          
516             and
517             # True if lookup_url is a file and if lookup_url is undef.
518             defined config('lookup_url') ?
519             config('lookup_url') =~ m!^file://! ? 1 : 0
520             : 1
521             ) {
522 0         0 die <
523             App-Fetchware-Util: You only called download_file() with just a PATH parameter,
524             but also failed to specify any mirrors in your configuration. Without any
525             defined mirrors download_file() cannot determine from what host to download your
526             file. Please specify a mirror and try again.
527             EOD
528             }
529              
530             # Set up our list of urls that we'll try to download the specified PATH or
531             # URL from.
532 5 100       21 my @urls = config('mirror') if defined config('mirror');
533             # If we're called with a PATH option and the lookup_url is for a local file,
534             # then we should just convert from a PATH into a $url.
535 5 50 66     37 if (exists $opts{PATH}
    50          
    100          
    50          
536             and
537             # Is lookup_url not a file://, true for undef and any other scheme.
538             defined config('lookup_url') ?
539             config('lookup_url') =~ m!^file://! ? 1 : 0
540             : 0
541             ) {
542 3         11 $url = "file://$opts{PATH}";
543 3         6 delete $opts{PATH};
544             # Otherwise, we should add lookup_url's hostname to the list of mirrors, but
545             # be sure to push it onto @urls so that it is used last.
546             #
547             # But only if lookup_url is defined.
548             } elsif (defined config('lookup_url')) {
549 0         0 my ($scheme, $auth, undef, undef, undef) =
550             uri_split(config('lookup_url'));
551 0         0 push @urls, uri_join($scheme, $auth, undef, undef, undef);
552             }
553              
554 5 50 33     67 if (exists $opts{PATH}
    50 33        
      33        
555             and defined $opts{PATH}
556             and $opts{PATH}) {
557             # The PATH option means that $url is not a full blown URL, but just a
558             # path without a hostname or scheme portion.
559             # Therefore, we append $url, because the PATH option means it's actually
560             # just a path, so we append it to each @url.
561 0         0 for my $mirror_url (@urls) {
562             # If the $mirror_url has no path...
563 0         0 my ($scheme, $auth, $path, $query, $frag) =
564             uri_split($mirror_url);
565             # Skip messing with the path if $path eq $opts{PATH}, which means the
566             # current $mirror_url is $url, so we shouldn't add its own path to
567             # itself--we should skip it instead.
568 0 0       0 next if $path eq $opts{PATH};
569 0 0       0 if ($path eq '') {
570             #...then append $url's path.
571             ###BUGALERT## As shown before I was using URI's much nicer
572             #interface, but it was deleting the path instead of replacing
573             #the path! I tried reproducing this with a small test file, but
574             #it worked just fine in the small test file. So, it must be some
575             #really weird bug to fail here, but work in a smaller test file.
576             #I don't know try replacing all of the URI::Split calls with the
577             #equivelent URI->path() calls, and you'll get the weird bug.
578             #$mirror_url->path($opts{PATH});
579             ###Add an unless ($opts{PATH} eq '')
580             $mirror_url =
581 0         0 uri_join($scheme, $auth, $opts{PATH}, $query, $frag);
582             # But if the $mirror_url does have a path...
583             } else {
584             #...Then keep the mirrors path intact.
585             #
586             # Because if you specify a path when you define that mirror
587             # chances are you did it, because that mirror stores it in a
588             # different directory. For example Apache is /apache on some
589             # mirrors, but apache.hostname on other mirrors.
590             #
591             #Except add $path's basename, because otherwise we'll ask
592             #for a dirlisting or try to download a directory as a file.
593 0 0       0 unless ($path =~ m!/$!) {
594             $mirror_url =
595             uri_join($scheme, $auth, $path . '/'
596 0         0 . file($opts{PATH})->basename(), $query, $frag);
597             # Skip adding a '/' if ones already there at the end.
598             } else {
599             $mirror_url =
600             uri_join($scheme, $auth, $path
601 0         0 . file($opts{PATH})->basename(), $query, $frag);
602             }
603             }
604             }
605             } elsif (defined $url
606             and $url) {
607             # Add $url to @urls since it too has a hostname. And use unshift
608             # to put it in the first position instead of last if you were to use
609             # push.
610 5         15 unshift @urls, $url;
611              
612             # I must parse out the path portion of the specified URL, because this
613             # path portion will be appended to the mirrors you have specified.
614 5         37 my $url_path = ( uri_split($url) )[2];
615 5         70 for my $mirror_url (@urls) {
616             # If the $mirror_url has no path...
617 8         22 my ($scheme, $auth, $path, $query, $frag) =
618             uri_split($mirror_url);
619             # Skip messing with the path if $path eq $url_path, which means the
620             # current $mirror_url is $url, so we shouldn't add its own path to
621             # itself--we should skip it instead.
622 8 100       117 next if $path eq $url_path;
623 1 50       3 if ($path eq '') {
624             #...then append $url's path.
625             ###BUGALERT## As shown before I was using URI's much nicer
626             #interface, but it was deleting the path instead of replacing
627             #the path! I tried reproducing this with a small test file, but
628             #it worked just fine in the small test file. So, it must be some
629             #really weird bug to fail here, but work in a smaller test file.
630             #I don't know try replacing all of the URI::Split calls with the
631             #equivelent URI->path() calls, and you'll get the weird bug.
632             #$mirror_url->path($url_path);
633             ###Add an unless ($url_path eq '')
634 1         3 $mirror_url =
635             uri_join($scheme, $auth, $url_path, $query, $frag);
636             # But if the $mirror_url does have a path...
637             } else {
638             #...Then keep the mirrors path intact.
639             #
640             # Because if you specify a path when you define that mirror
641             # chances are you did it, because that mirror stores it in a
642             # different directory. For example Apache is /apache on some
643             # mirrors, but apache.hostname on other mirrors.
644             #
645             #Except add $path's basename, because otherwise we'll ask
646             #for a dirlisting or try to download a directory as a file.
647 0 0       0 unless ($path =~ m!/$!) {
648 0         0 $mirror_url =
649             uri_join($scheme, $auth, $path . '/'
650             . file($url_path)->basename(), $query, $frag);
651             # Skip adding a '/' if ones already there at the end.
652             } else {
653 0         0 $mirror_url =
654             uri_join($scheme, $auth, $path
655             . file($url_path)->basename(), $query, $frag);
656             }
657             }
658             }
659             }
660              
661 5         25 my $filename;
662              
663 5         11 for my $mirror_url (@urls) {
664 5         241 eval {
665 5         29 msg "Attempting to download [$mirror_url].";
666             # Try the mirror_url directly without trying any mirrors.
667 5         25 $filename = no_mirror_download_file($mirror_url);
668             };
669 5 50       22 if ($@) {
670 0         0 msg "File download attempt failed! Error was[";
671 0         0 print $@;
672 0         0 msg "].";
673             }
674              
675             # Skip the rest of the @urls after we successfully download the $url.
676 5 50       18 if (defined $filename) {
677 5         37 msg "Successfully downloaded the file [$mirror_url].";
678 5         31 last;
679             }
680             }
681              
682 5 50       24 die <
683             App-Fetchware-Util: Failed to download the specifed URL [$url] or path
684             [$opts{PATH}] using the included hostname in the url you specifed or any
685 0         0 mirrors. The mirrors are [@{[config('mirror')]}]. And the urls
686 0         0 that fetchware tried to download were [@{[@urls]}].
687             EOD
688              
689 5         77 return $filename;
690             }
691              
692              
693              
694             sub no_mirror_download_file {
695 5     5 1 12 my $url = shift;
696              
697 5         5 my $filename;
698 5 50       54 if ($url =~ m!^ftp://!) {
    50          
    50          
699 0         0 $filename = download_ftp_url($url);
700             } elsif ($url =~ m!^http://!) {
701 0         0 $filename = download_http_url($url);
702             } elsif ($url =~ m!^file://!) {
703 5         22 $filename = download_file_url($url);
704             } else {
705 0         0 die <
706             App-Fetchware: run-time syntax error: the url parameter your provided in
707             your call to download_file() [$url] does not have a supported URL scheme (the
708             http:// or ftp:// part). The only supported download types, schemes, are FTP and
709             HTTP. See perldoc App::Fetchware.
710             EOD
711             }
712              
713 5         888 return $filename;
714             }
715              
716              
717              
718             sub download_ftp_url {
719 0     0 1 0 my $ftp_url = shift;
720              
721             ###BUGALERT### Replace custom regex with URI::Split's regex.
722 0         0 $ftp_url =~ m!^ftp://([-a-z,A-Z,0-9,\.]+)(/.*)?!;
723 0         0 my $site = $1;
724 0         0 my $path = $2;
725 0         0 my ($volume, $directories, $file) = splitpath($path);
726              
727             # for debugging: $ftp = Net::FTP->new('site','Debug',10);
728             # open a connection and log in!
729              
730 0 0       0 my $ftp = Net::FTP->new($site)
731             or die <
732             App-Fetchware: run-time error. fetchware failed to connect to the ftp server at
733             domain [$site]. The system error was [$@].
734             See man App::Fetchware.
735             EOD
736            
737 0 0       0 $ftp->login("anonymous",'-anonymous@')
738             or die <
739             App-Fetchware: run-time error. fetchware failed to log in to the ftp server at
740 0         0 domain [$site]. The ftp error was [@{[$ftp->message]}]. See man App::Fetchware.
741             EOD
742              
743             # set transfer mode to binary
744 0 0       0 $ftp->binary()
745             or die <
746             App-Fetchware: run-time error. fetchware failed to swtich to binary mode while
747             trying to download a the file [$path] from site [$site]. The ftp error was
748 0         0 [@{[$ftp->message]}]. See perldoc App::Fetchware.
749             EOD
750              
751             # change the directory on the ftp site
752 0 0       0 $ftp->cwd($directories)
753             or die <
754             App-Fetchware: run-time error. fetchware failed to cwd() to [$path] on site
755 0         0 [$site]. The ftp error was [@{[$ftp->message]}]. See perldoc App::Fetchware.
756             EOD
757              
758              
759             # Download the file to the current directory. The start() subroutine should
760             # have cd()d to a tempdir for fetchware to use.
761 0 0       0 $ftp->get($file)
762             or die <
763             App-Fetchware: run-time error. fetchware failed to download the file [$file]
764             from path [$path] on server [$site]. The ftp error message was
765 0         0 [@{[$ftp->message]}]. See perldoc App::Fetchware.
766             EOD
767              
768             # ftp done!
769 0         0 $ftp->quit;
770              
771             # The caller needs the $filename to determine the $package_path later.
772 0         0 return $file;
773             }
774              
775              
776              
777             sub download_http_url {
778 0     0 1 0 my $http_url = shift;
779              
780             # Forward any other options over to HTTP::Tiny. This is used mostly to
781             # support changing user agent strings, but why not support them all.
782 0 0       0 my %opts = @_ if @_ % 2 == 0;
783              
784             # Append user_agent if specified.
785 0 0       0 $opts{agent} = config('user_agent') if config('user_agent');
786              
787 0         0 my $http = HTTP::Tiny->new(%opts);
788             ###BUGALERT### Should use request() instead of get, because request can
789             #directly write the chunks of the file to disk as they are downloaded. get()
790             #just uses RAM, so a 50Meg file takes up 50 megs of ram, and so on.
791 0         0 my $response = $http->get($http_url);
792              
793             #use Test::More;
794             #diag("RESPONSE OBJECT[");
795             #diag explain $response->{status};
796             #diag explain $response->{headers};
797             #diag explain $response->{url};
798             #diag explain $response->{reason};
799             #diag explain $response->{success};
800             ## Should be commented out to avoid borking the terminal, but is needed when
801             ## HTTP::Tiny has internal 599 errors, because the error message is in the
802             ## content.
803             ##diag explain $response->{content};
804             #diag("]");
805              
806 0 0       0 die <{success};
807             App-Fetchware: run-time error. HTTP::Tiny failed to download a file or directory
808             listingfrom your provided url [$http_url]. HTTP status code
809             [$response->{status} $response->{reason}] HTTP headers
810 0         0 [@{[Data::Dumper::Dumper($response->{headers})]}].
811             See man App::Fetchware.
812             EOD
813              
814             # In this case the content is binary, so it will mess up your terminal.
815             #diag($response->{content}) if length $response->{content};
816 0 0       0 die <{content};
817             App-Fetchware: run-time error. The url [$http_url] you provided downloaded
818             nothing. HTTP status code [$response->{status} $response->{reason}]
819 0         0 HTTP headers [@{[Data::Dumper::Dumper($response)]}].
820             See man App::Fetchware.
821             EOD
822              
823             # Must convert the worthless $response->{content} variable into a real file
824             # on the filesystem. Note: start() should have cd()d us into a suitable
825             # tempdir.
826 0         0 my $path = $http_url;
827 0         0 $path =~ s!^http://!!;
828             # Determine filename from the $path.
829 0         0 my ($volume, $directories, $filename) = splitpath($path);
830             # If $filename is empty string, then its probably a index directory listing.
831 0   0     0 $filename ||= 'index.html';
832             ###BUGALERT### Need binmode() on Windows???
833             ###BUGALERT### Switch to safe_open()????
834 0 0       0 open(my $fh, '>', $filename) or die <
835             App-Fetchware: run-time error. Fetchware failed to open a file necessary for
836             fetchware to store HTTP::Tiny's output. Os error [$!]. See perldoc
837             App::Fetchware.
838             EOD
839             # Write HTTP::Tiny's downloaded file to a real file on the filesystem.
840 0         0 print $fh $response->{content};
841 0 0       0 close $fh
842             or die <
843             App-Fetchware: run-time error. Fetchware failed to close the file it created to
844             save the content it downloaded from HTTP::Tiny. This file was [$filename]. OS
845             error [$!]. See perldoc App::Fetchware.
846             EOS
847              
848             # The caller needs the $filename to determine the $package_path later.
849 0         0 return $filename;
850             }
851              
852              
853              
854              
855             sub download_file_url {
856 5     5 1 8 my $url = shift;
857              
858 5         28 $url =~ s!^file://!!; # Strip useless URL scheme.
859            
860             # Prepend original_cwd() only if the $url is *not* absolute, which will mess
861             # it up.
862 5 50       28 $url = catdir(original_cwd(), $url) unless file_name_is_absolute($url);
863              
864             # Download the file:// URL to the current directory, which should already be
865             # in $temp_dir, because of start()'s chdir().
866             #
867             # Don't forget to clear taint. Fetchware does *not* run in taint mode, but
868             # for some reason, bug?, File::Copy checks if data is tainted, and then
869             # retaints it if it is already tainted, but for some reason I get "Insecure
870             # dependency" taint failure exceptions when drop priving. The fix is to
871             # always untaint my data as done below.
872             ###BUGALERT### Investigate this as a possible taint bug in perl or just
873             #File::Copy. Perhaps the cause is using File::Copy::cp(copy) after drop
874             #priving with data from root?
875 5         77 $url =~ /(.*)/;
876 5         14 my $untainted_url = $1;
877 5         14894 my $cwd = cwd();
878 5         64 $cwd =~ /(.*)/;
879 5         35 my $untainted_cwd = $1;
880 5 50       71 cp($untainted_url, $untainted_cwd) or die <
881             App::Fetchware: run-time error. Fetchware failed to copy the download URL
882             [$untainted_url] to the working directory [$untainted_cwd]. Os error [$!].
883             EOD
884              
885             # Return just file filename of the downloaded file.
886 5         2686 return file($url)->basename();
887             }
888              
889              
890              
891              
892              
893              
894              
895             ###BUGALERT### safe_open() does not check extended file perms such as ext*'s
896             #crazy attributes, linux's (And other Unixs' too) MAC stuff or Windows NT's
897             #crazy file permissions. Could use Win32::Perms for just Windows, but its not
898             #on CPAN. And what about the other OSes.
899             ###BUGALERT### Consier moving this to CPAN??? File::SafeOpen????
900             sub safe_open {
901 181     181 1 2438476 my $file_to_check = shift;
902 181   100     876 my $open_fail_message = shift // <
903             Failed to open file [$file_to_check]. OS error [$!].
904             EOE
905              
906 181         423 my %opts = @_;
907              
908 181         210 my $fh;
909              
910              
911             # Open the file first.
912 181 100 66     749 unless (exists $opts{MODE} and defined $opts{MODE}) {
913 180 50       7261 open $fh, '<', $file_to_check or die $open_fail_message;
914             } else {
915 1 50       48 open $fh, $opts{MODE}, $file_to_check or die $open_fail_message;
916             }
917              
918 181         1503 my $info = stat($fh);# or goto STAT_ERROR;
919              
920             # Owner must be either me (whoever runs fetchware) or superuser. No one else
921             # can be trusted.
922 181 50 33     29962 if(($info->uid() != 0) && ($info->uid() != $<)) {
923 0         0 die <
924             App-Fetchware-Util: The file fetchware attempted to open is not owned by root or
925             the person who ran fetchware. This means the file could have been dangerously
926             altered, or it's a simple permissions problem. Do not simly change the
927             ownership, and rerun fetchware. Please check that the file [$file_to_check] has
928             not been tampered with, correct the ownership problems and try again.
929             EOD
930             }
931              
932             # Check if group and other can write $fh.
933             # Use 066 to detect read or write perms.
934             ###BUGALERT### What does this actually test?????
935 181 100       3525 if ($info->mode() & 022) { # Someone else can write this $fh.
936 2         32 die <
937             App-Fetchware-Util: The file fetchware attempted to open [$file_to_check] is
938             writable by someone other than just the owner. Fetchwarefiles and fetchware
939             packages must only be writable by the owner. Do not only change permissions to
940             fix this error. This error may have allowed someone to alter the contents of
941             your Fetchwarefile or fetchware packages. Ensure the file was not altered, then
942             change permissions to 644.
943             EOD
944             }
945            
946             # Then check the directories its contained in.
947              
948             # Make the file an absolute path if its not already.
949 179         2041 $file_to_check = rel2abs($file_to_check);
950              
951             # Create array of current directory and all parent directories and even root
952             # directory to check all of their permissions below.
953 179         3862 my $dir = dir($file_to_check);
954 179         9197 my @directories = do {
955 179         244 my @dirs;
956 179         1271 until ($dir eq rootdir()) {
957             # Add this dir to the array of dirs to keep.
958 539         25906 push @dirs, $dir;
959              
960             # This loops version of $i++ the itcremeter.
961 539         1722 $dir = $dir->parent();
962             }
963 179         9152 push @dirs, $dir->parent(); # $dir->parent() should be the root dir.
964              
965             # Return, by being the last statement, the list of parent dirs for
966             # $file_to_check.
967 179         6945 @dirs;
968             };
969             # Who cares if _PC_CHOWN_RESTRICTED is set, check all parent dirs anyway,
970             # because if say /home was 777, then anyone (other) can change any child
971             # file in any directory above /home now anyway even if _PC_CHOWN_RESTRICTED
972             # is set.
973 179         676 for my $dir (@directories) {
974              
975 716         8245 my $info = stat($dir);# or goto STAT_ERROR;
976              
977             # Owner must be either me (whoever runs fetchware) or superuser. No one
978             # else can be trusted.
979 716 50 33     71711 if(($info->uid() != 0) && ($info->uid() != $<)) {
980 0         0 die <
981             App-Fetchware-Util: The file fetchware attempted to open is not owned by root or
982             the person who ran fetchware. This means the file could have been dangerously
983             altered, or it's a simple permissions problem. Do not simly change the
984             ownership, and rerun fetchware. Please check that the file [$file_to_check] has
985             not been tampered with, correct the ownership problems and try again.
986             EOD
987             }
988              
989             # Check if group and other can write $fh.
990             # Use 066 to detect read or write perms.
991             ###BUGALERT### What does this actually test?????
992 716 100       11237 if ($info->mode() & 022) { # Someone else can write this $fh...
993             # ...except if this file has the sticky bit set and its a directory.
994 179 100 66     3297 die <mode & 01000 and S_ISDIR($info->mode);
995             App-Fetchware-Util: The file fetchware attempted to open [$file_to_check] is
996             writable by someone other than just the owner. Fetchwarefiles and fetchware
997             packages must only be writable by the owner. Do not only change permissions to
998             fix this error. This error may have allowed someone to alter the contents of
999             your Fetchwarefile or fetchware packages. Ensure the file was not altered, then
1000             change permissions to 644. Permissions on failed directory were:
1001 1         22 @{[Dumper($info)]}
1002 1         171 Umask [@{[umask]}].
1003             EOD
1004             }
1005              
1006             }
1007             # Return the proven above "safe" file handle.
1008 178         1942 return $fh;
1009              
1010             # Use cool C style goto error handling. It beats copy and paste, and the
1011             # horrible contortions needed for "structured programming."
1012             STAT_ERROR: {
1013 0         0 die <
  0         0  
1014             App-Fetchware-Util: stat($fh) filename [$file_to_check] failed! This just
1015             shouldn't happen unless of course the file you specified does not exist. Please
1016             ensure files you specify when you run fetchware actually exist.
1017             EOD
1018             }
1019             }
1020              
1021              
1022              
1023             sub drop_privs {
1024 128     128 1 27792 my $child_code = shift;
1025 128   100     1040 my $regular_user = shift // 'nobody';
1026 128         287 my %opts = @_;
1027              
1028             # Need to do this in 2 places.
1029             my $dont_drop_privs = sub {
1030 2     2   8 my $child_code = shift;
1031              
1032 2         4 my $output;
1033 2 50       68 open my $output_fh, '>', \$output or die <
1034             App-Fetchware-Util: fetchware failed to open an internal scalar reference as a
1035             file handle. OS error [$!].
1036             EOD
1037 2         12 $child_code->($output_fh);
1038              
1039 2 50       38 close $output_fh or die <
1040             App-Fetchware-Util: fetchware failed to close an internal scalar reference that
1041             was open as a file handle. OS error [$!].
1042             EOD
1043 2         14 return \$output;
1044 128         1206 };
1045              
1046             # Execute $child_code without dropping privs if the user's configuration
1047             # file is configured to force fetchware to "stay_root."
1048 128 100       422 if (config('stay_root')) {
1049 2         22 msg <
1050             stay_root is set to true. NOT dropping privileges!
1051             EOM
1052 2         12 return $dont_drop_privs->($child_code);
1053             }
1054              
1055 126 50 33     1004 if (is_os_type('Unix') and ($< == 0 or $> == 0)) {
      33        
1056             # cmd_new() needs to skip the creation of this useless directory that it
1057             # does not use. Furthemore, the creation of this extra tempdir is not
1058             # needed by cmd_new(), and this tempdir presumes start() was called
1059             # before drop_privs(), which is always the case except for cmd_new().
1060             #
1061             # But another case where this temp dir's creations should be skipped is
1062             # if start() is overridden with hook() to make start() do something
1063             # other than create a temp dir, because in some cases such as using VCS
1064             # instead of Web sites and mirrors, you do not need to bother with
1065             # creating a tempdir, because the working dir of the repo can be used
1066             # instead. Therefore, if the parent directory is not /^fetchware-$$/,
1067             # then we'll also skip creating the tempd dir, because it most likely
1068             # means that a tempdir is not needed.
1069 126 100       279405 $opts{SkipTempDirCreation} = 1
1070             unless file(cwd())->basename() =~ /^fetchware-$$/;
1071 126 50 66     21956 unless (exists $opts{SkipTempDirCreation}
      66        
1072             and defined $opts{SkipTempDirCreation}
1073             and $opts{SkipTempDirCreation}) {
1074             # Ensure that $user_temp_dir can be accessed by my drop priv'd child.
1075             # And only try to change perms to 0755 only if perms are not 0755
1076             # already.
1077 120         245034 my $st = stat(cwd());
1078 120 50       22631 unless ((S_IMODE($st->mode) & 0755) >= 0755) {
1079 120 50       265938 chmod 0755, cwd() or die <
1080             App-Fetchware-Util: Fetchware failed to change the permissions of the current
1081 0         0 temporary directory [@{[cwd()]} to 0755. The OS error was [$!].
1082             EOD
1083             }
1084             # Create a new tempdir for the droped prive user to use, and be sure
1085             # to chown it so they can actually write to it as well.
1086             # $new_temp_dir does not have a semaphore file, but its parent
1087             # directory does, which will still keep fetchware clean from
1088             # deleting this directory out from underneath us.
1089             #
1090             # Also note, that cwd() is "blindly" coded here, which makes it a
1091             # "dependency," but drop_privs() is meant to be called after start()
1092             # by fetchware::cmd_*(). It's not meant to be a generic subroutine
1093             # to drop privs, and it's also not really meant to be used by
1094             # fetchware extensions mostly just fetchware itself. Perhaps I
1095             # should move it back to bin/fetchware???
1096             #
1097             # Also also note, that CLEANUP option is *not* specified, because
1098             # that can cause this directory in cases of errors, and you can't
1099             # track down an error in a build script if the directory everything
1100             # is in has been deleted.
1101 120         242766 my $new_temp_dir = tempdir("fetchware-$$-XXXXXXXXXX",
1102             DIR => cwd());
1103             # Determine /etc/passwd entry for the "effective" uid of the
1104             # current fetchware process. I should use the "effective" uid
1105             # instead of the "real" uid, because effective uid is used to
1106             # determine what each uid can do, and the real uid is only
1107             # really used to track who the original user was in a setuid
1108             # program.
1109 120   50     46719 my ($name, $useless, $uid, $gid, $quota, $comment, $gcos, $dir,
1110             $shell, $expire)
1111             = getpwnam(config('user') // 'nobody');
1112 120 50       2536 chown($uid, $gid, $new_temp_dir) or die <
1113             App-Fetchware-Util: Fetchware failed to chown [$new_temp_dir] to the user it is
1114             dropping privileges to. This just shouldn't happen, and might be a bug, or
1115             perhaps your system temporary directory is full. The OS error was [$!].
1116             EOD
1117 120 50       1530 chmod(0700, $new_temp_dir) or die <
1118             App-Fetchware-Util: Fetchware failed to change the permissions of its new
1119             temporary directory [$new_temp_dir] to 0700 that it created, because its
1120             dropping privileges. This just shouldn't happen, and is bug, or perhaps your
1121             system temporary directory is full. The OS error is [$!].
1122             EOD
1123             # And of course chdir() to $new_temp_dir, because everything assumes
1124             # that the cwd() is where everything should be saved and done.
1125 120 50       1511 chdir($new_temp_dir) or die <
1126             App-Fetchware-Util: Fetchware failed to chdir() to its new temporary directory
1127             [$new_temp_dir]. This shouldn't happen, and is most likely a bug, or perhaps
1128             your system temporary directory is full. The OS error was [$!].
1129             EOD
1130             }
1131              
1132             # Open a pipe to allow the child to talk back to the parent.
1133 126 50       1732 pipe(READONLY, WRITEONLY) or die <
1134             App-Fetchware-Util: Fetchware failed to create a pipe to allow the forked
1135             process to communication back to the parent process. OS error [$!].
1136             EOD
1137             # Turn them into proper lexical file handles.
1138 126         1266 my ($readonly, $writeonly) = (*READONLY, *WRITEONLY);
1139              
1140             # Set up a SIGPIPE handler in case the writer closes the pipe before the
1141             # reader closes their pipe.
1142             $SIG{'PIPE'} = sub {
1143 0     0   0 die <
1144             App-Fetchware-Util: Fetchware received a PIPE signal from the OS indicating the
1145             pipe is dead. This should not happen, and is because the child was killed out
1146             from under the parent, or there is a bug. This is a fatal error, because it's
1147             possible the parent needs whatever information the child was going to use the
1148             pipe to send to the parent, and now it is unclear if the proper expected output
1149             has been received or not; therefore, we're just playing it safe and die()ing.
1150             EOD
1151 126         1851 };
1152            
1153             # Code below based on a cool forking idiom by Aristotle.
1154             # (http://blogs.perl.org/users/aristotle/2012/10/concise-fork-idiom.html)
1155 126         83717 for ( scalar fork ) {
1156             # Fork failed.
1157             # defined() operates on default variable, $_.
1158 126 50       2452 if (not defined $_) {
1159 0         0 die <
1160             App-Fetchware-Util: Fork failed! This shouldn't happen!?! Os error [$!].
1161             EOD
1162             }
1163              
1164             # Fork succeeded, Parent code goes here.
1165 126         877 my $kidpid = $_;
1166 126 50       936 if ( $kidpid ) {
1167 126 50       5443 close $writeonly or die <
1168             App-Fetchware-Util: Failed to close $writeonly pipe in parent. Os error [$!].
1169             EOD
1170 126         706 my $output;
1171              
1172             # Read the child's output until child closes pipe sending EOF.
1173 126         4410708 $output .= $_ while (<$readonly>);
1174              
1175             # Close $readonly pipe, because we have received the output from
1176             # the user.
1177 126 50       3307 close $readonly or die <
1178             App-Fetchware-Util: Failed to close $readonly pipe in parent. Os error [$!].
1179             EOD
1180              
1181             # Just block waiting for the child to finish.
1182 126         161408 waitpid($kidpid, 0);
1183             # If the child failed ($? >> 8 != 0), then the parent should
1184             # fail as well, because the child only exists to drop privs with
1185             # the ability to still at a later time execute something as root
1186             # again, so the fork is needed, because once you drop privs
1187             # you can't get them back, and you don't want to be able to for
1188             # security reasons.
1189 126 50       1413 if (($? >> 8) != 0) {
1190             # Note this message is only vmsg()'d instead of die()'d,
1191             # because if its printed always, it could confuse users.
1192             # Because priv_drop()ing is the default, this error would be
1193             # seen all the time making getting confused by it likely.
1194 0         0 vmsg <
1195             App-Fetchware-Util: An error occured forcing fetchware to exit while fetchware
1196             has forked to drop its root priviledges to avoid downloading files and building
1197             programs as root. Root priviledges are only maintained to install the software
1198             in a system directory requiring root access. The error that caused the child to
1199             fail will have already been printed above by the child.
1200             EOM
1201 0         0 msg <
1202             For help troublehsooting fetchware failed inside directory:
1203 0         0 @{[cwd()]}
1204             EOM
1205             # Keep all of fetchware's temporary files and directories
1206             # around so the user has access to them, so they can be
1207             # troubleshooted to see what caused the failure.
1208 0         0 $File::Temp::KEEP_ALL = 1;
1209             # Exit non-zero indicating failure, because whatever the
1210             # child did failed, and the child's main eval {} in
1211             # bin/fetchware caught that failure, printed it to the
1212             # screen, and exit()ed non-zero for failure. And since the
1213             # child failed ($? >> 8 != 0), the parent should fail too.
1214 0         0 exit 1;
1215             # If successful, return to the child a ref of @output to caller.
1216             } else {
1217 126         5173 return \$output;
1218             }
1219             # Fork succeeded, child code goes here.
1220             } else {
1221 0 0       0 close $readonly or die <
1222             App-Fetchware-Util: Failed to close $readonly pipe in child. Os error [$!].
1223             EOD
1224             # Drop privs.
1225             # drop_privileges() dies on an error just let drop_privs() caller
1226             # catch it.
1227 0         0 my ($uid, $gid) = drop_privileges($regular_user);
1228              
1229              
1230             # Execute the coderef that is supposed to be done as non-root.
1231 0         0 $child_code->($writeonly);
1232              
1233             # Now close the pipe, to avoid creating a dead pipe causing a
1234             # SIGPIPE to be sent to the parent.
1235 0 0       0 close $writeonly or die <
1236             App-Fetchware-Util: Failed to close $writeonly pipe in child. Os error [$!].
1237             EOD
1238              
1239             # Exit success, because failure is only indicated by a thrown
1240             # exception that bin/fetchware's main eval {} will catch, print,
1241             # and exit non-zero indicating failure.
1242             # Use POSIX's _exit() to avoid calling END{} blocks. This *must*
1243             # be done to prevent File::Temp's END{} block from attempting to
1244             # delete the temp directory that the parent still needs to
1245             # finish installing or uninstalling. The parent's END{} block's
1246             # will still be called, so this just turns off the child
1247             # deleting the temp dir not the parent.
1248 0         0 _exit 0;
1249             }
1250             }
1251             # Non-Unix OSes just execute the $child_code.
1252             } else {
1253 0         0 return $dont_drop_privs->($child_code);
1254             }
1255             }
1256              
1257              
1258              
1259              
1260             ###BUGALERT### Add quotemeta() support to pipe parsers to help prevent attacks.
1261              
1262              
1263              
1264             { # Bareblock just for the $MAGIC_NUMBER.
1265             # Determine $front_magic
1266             my $front_magic;
1267             $front_magic = int(rand(8128389023));
1268             # For no particular reason convert the random integer into hex, because I
1269             # never store something in decimal and then exact same thing in hex.
1270             $front_magic = $front_magic . sprintf("%x", $front_magic);
1271             # Run srand() again to change random number generator between rand() calls.
1272             # Not really necessary, but should make it harder to guess correct magic
1273             # numbers.
1274             srand(time());
1275             # Same a $front_magic.
1276             my $back_magic = int(rand(986487516));
1277             # Octal this time :) for no real reason.
1278             $back_magic = $back_magic . sprintf("%o", $back_magic);
1279             my $MAGIC_NUMBER = $front_magic
1280             . 'MAGIC_NUMBER_REPLACING_NEWLINE'
1281             . $back_magic;
1282              
1283             sub write_dropprivs_pipe {
1284 1     1 1 1250 my $write_pipe = shift;
1285              
1286 1         13 for my $a_var (@_) {
1287 4 50       94 die <
1288             fetchware: Huh? [$a_var] has fetchware's MAGIC_NUMBER in it? This shouldn't
1289             happen, and messes up fetchware's simple IPC. You should never see this error,
1290             because it's not a particuarly magic number if anybody actually uses it. This is
1291             most likely a bug, so please report it.
1292             EOD
1293              
1294             # Write to the $write_pipe, but use the $MAGIC_NUMBER instead of just
1295             # newline.
1296 4         26 print $write_pipe $a_var . $MAGIC_NUMBER;
1297             }
1298             }
1299              
1300              
1301              
1302             sub read_dropprivs_pipe {
1303 121     121 1 1708 my $output = shift;
1304              
1305 121 50       560 die <
1306             App-Fetchware-Util: pipe_read_newling() was called with an output variable
1307             [$output] that was not a scalar reference. It must be a scalar reference.
1308             EOD
1309              
1310 121         218 my @variables;
1311 121         1535 for my $variable (split(/$MAGIC_NUMBER/, $$output)) {
1312             # And some error handling just in case.
1313 232 50       573 die <
1314             fetchware: Huh? The child failed to write the proper variable back to the
1315             parent! The variable is [$variable]. This should be defined but it is
1316             not!
1317             EOD
1318             # Clear possibly tainted variables. It's a weird bug that makes no
1319             # sense. I don't turn -t or -T on, so what gives??? If you're curious
1320             # try commenting out the taint clearing code below, and running the
1321             # t/bin-fetchware-install.t test file (Or any other ones that call
1322             # drop_privs().).
1323 232         226 my $untainted;
1324             # Need the m//ms options to match strings with newlines in them.
1325 232 50       1512 if ($variable =~ /(.*)/ms) {
1326 232         756 $untainted = $1;
1327             } else {
1328 0         0 die <
1329             App::Fetchware::Util: Untaint failed! Huh! This just shouldn't happen! It's
1330             probably a bug.
1331             EOD
1332             }
1333              
1334             # Push $untainted instead of just $variable, because I want to return
1335             # untatined data instead of potentially tainted data.
1336 232         629 push @variables, $untainted;
1337             }
1338              
1339 121         551 return @variables;
1340             }
1341             ###BUGALERT### Add some pipe parsers that use Storable too.
1342              
1343             } # End $MAGIC_NUMBER bare block.
1344              
1345              
1346              
1347              
1348              
1349              
1350              
1351             sub do_nothing {
1352 3     3 1 11 return;
1353             }
1354              
1355              
1356              
1357              
1358              
1359              
1360             { # Begin scope block for $original_cwd.
1361              
1362             # $original_cwd is a scalar variable that stores fetchware's original
1363             # working directory for later use if its needed. It is access with
1364             # original_cwd() below.
1365             my $original_cwd;
1366             # $fh_sem is a semaphore lock file that create_tempdir() creates, and
1367             # cleanup_tempdir() closes clearing the lock. This is used to support
1368             # fetchware clean. The filehandle needs to be declared outside
1369             # create_tempdir()'s scope, because when this filehandle goes out of scope
1370             # the file is closed, and the lock is released, but fetchware needs to keep
1371             # hold of this lock for the life of fetchware to ensure that any fetchware
1372             # clean won't delete this fetchware temporary directory.
1373             my $fh_sem;
1374              
1375              
1376             ###BUGALERT### Add support for the -f/--force option to force deleting fetchware
1377             #temp dirs even if locked.
1378             sub create_tempdir {
1379 389     389 1 14015 my %opts = @_;
1380              
1381 389         973 msg 'Creating temp dir to use to install your package.';
1382              
1383             # Ask for better security.
1384 389         3151 File::Temp->safe_level( File::Temp::HIGH );
1385              
1386             # Create the temp dir in the portable locations as returned by
1387             # File::Spec->tempdir() using the specified template (the weird $$ is this
1388             # processes process id), and cleaning up at program exit.
1389 389         8956 my $exception = '';
1390 389         553 my $temp_dir;
1391 389 100       579 eval {
1392 389         522 local $@;
1393              
1394             # Determine tempdir()'s arguments.
1395 389         1773 my @args = ("fetchware-$$-XXXXXXXXXX");#, TMPDIR => 1);
1396              
1397             # Specify the caller's TempDir (DIR) if they specify it.
1398 389 100       1039 push @args, DIR => $opts{TempDir} if defined $opts{TempDir};
1399              
1400             # Specify either system temp directory or user specified directory.
1401             push @args,
1402 389 100       1460 (defined $opts{TempDir} ? (DIR => $opts{TempDir}) : (TMPDIR => 1));
1403              
1404             # Don't CLEANUP if KeepTempDir is set.
1405 389 100       1128 push @args, CLEANUP => 1 if not defined $opts{KeepTempDir};
1406              
1407             # Call tempdir() with the @args I've built.
1408 389         1676 $temp_dir = tempdir(@args);
1409              
1410             # Only when we do *not* drop privs...
1411 387 50 33     140422 if (config('stay_root')
      33        
1412             or ($< != 0 or $> != 0)
1413             ) {
1414             # ...Must chmod 700 so gpg's localized keyfiles are good.
1415 0 0       0 chmod(0700, $temp_dir) or die <
1416             App-Fetchware-Util: Fetchware failed to change the permissions of its temporary
1417             directory [$temp_dir] to 0700. This should not happen, and is a bug, or perhaps
1418             your system's temporary directory is full. The OS error was [$!].
1419             EOD
1420             }
1421              
1422 387         603 $exception = $@;
1423 387         1135 1; # return true unless an exception is thrown.
1424             } or die <
1425             App-Fetchware: run-time error. Fetchware tried to use File::Temp's tempdir()
1426             subroutine to create a temporary file, but tempdir() threw an exception. That
1427             exception was [$exception]. See perldoc App::Fetchware.
1428             EOD
1429              
1430 387         1077776 $original_cwd = cwd();
1431 387         4529 vmsg "Saving original working directory as [$original_cwd]";
1432              
1433             # Change directory to $CONFIG{TempDir} to make unarchiving and building happen
1434             # in a temporary directory, and to allow for multiple concurrent fetchware
1435             # runs at the same time.
1436 387 50       5542 chdir $temp_dir or die <
1437             App-Fetchware: run-time error. Fetchware failed to change its directory to the
1438             temporary directory that it successfully created. This just shouldn't happen,
1439             and is weird, and may be a bug. See perldoc App::Fetchware.
1440             EOD
1441 387         1875 vmsg "Successfully changed working directory to [$temp_dir].";
1442              
1443             # Create 'fetcwhare.sem' - the fetchware semaphore lock file.
1444 387 50       26554 open $fh_sem, '>', 'fetchware.sem' or die <
1445             App-Fetchware-Util: Failed to create [fetchware.sem] semaphore lock file! This
1446             should not happen, because fetchware is creating this file in a brand new
1447             directory that only fetchware should be accessing. You simply shouldn't see this
1448             error unless some one is messing with fetchware, or perphaps there actually is a
1449             bug? I don't know, but this just shouldn't happen. It's so hard to trigger it to
1450             happen, it can't easily be tested in fetchware's test suite. OS error [$!].
1451             EOD
1452 387         1021 vmsg "Successfully created [fetchware.sem] semaphore lock file.";
1453             # Now flock 'fetchware.sem.' This should
1454             # Use LOCK_NB so flock won't stupidly wait forever and ever until the lock
1455             # becomes available.
1456 387 50       3177 flock $fh_sem, LOCK_EX | LOCK_NB or die <
1457             App-Fetchware-Util: Failed to flock [fetchware.sem] semaphore lock file! This
1458             should not happen, because this is being done in a brand new temporary directory
1459             that only this instance of fetchware cares about. This just shouldn't happen. OS
1460             error [$!].
1461             EOD
1462 387         903 vmsg "Successfully locked [fetchware.sem] semaphore lock file using flock.";
1463              
1464 387         2256 msg "Temporary directory created [$temp_dir]";
1465              
1466 387         4533 return $temp_dir;
1467             }
1468              
1469              
1470              
1471             sub original_cwd {
1472 1131     1131 1 17143 return $original_cwd;
1473             }
1474              
1475              
1476              
1477             sub cleanup_tempdir {
1478 373     373 1 2516 msg 'Cleaning up temporary directory temporary directory.';
1479              
1480             # Close and unlock the fetchware semaphore lock file, 'fetchware.sem.'
1481 373 50       1373 if (defined $fh_sem) {
1482 373 50       4323 close $fh_sem or die <
1483             App-Fetchware-Util: Huh? close() failed! Fetchware failed to close(\$fh_sem).
1484             Perhaps some one or something deleted it under us? Maybe a fetchware clean was
1485             run with the force flag (--force) while this other fetchware was running?
1486             OS error [$!].
1487             EOD
1488 373         961 vmsg <
1489             Closed [fetchware.sem] filehandle to unlock this fetchware temporary directory from any
1490             fetchware clean runs.
1491             EOM
1492             }
1493              
1494             # chdir to original_cwd() directory, so File::Temp can delete the tempdir.
1495             # This is necessary, because operating systems do not allow you to delete a
1496             # directory that a running program has as its cwd.
1497 373 50       1332 if (defined(original_cwd())) {
1498 373         646 vmsg "Changing directory to [@{[original_cwd()]}].";
  373         727  
1499 373 50       1430 chdir(original_cwd()) or die <
1500             App-Fetchware: run-time error. Fetchware failed to chdir() to
1501 0         0 [@{[original_cwd()]}]. See perldoc App::Fetchware.
1502             EOD
1503             }
1504              
1505             # cleanup_tempdir() used to actually delete the temporary directory by using
1506             # File::Temp's cleanup() subroutine, but that subroutine deletes *all*
1507             # temporary directories that File::Temp has created and marked for deletion,
1508             # which might include directories created before this call to
1509             # cleanup_tempdir(), but are needed after. Therefore, cleanup_tempdir() no
1510             # longer actually deletes anything; instead, File::Temp can do it in its END
1511             # handler.
1512             #
1513             # The code below is left here on purpose, to remind everyone *not* to call
1514             # File::Temp's cleanup() here!! Do not do it!
1515             ###DONOTDO#### Call File::Temp's cleanup subrouttine to delete fetchware's temp
1516             ###DONOTDO#### directory.
1517             ###DONOTDO###vmsg 'Cleaning up temporary directory.';
1518             ###DONOTDO###File::Temp::cleanup();
1519              
1520 373         735 vmsg "Leaving tempdir alone. File::Temp's END handler will delete it.";
1521              
1522 373         1072 vmsg 'Clearing internal %CONFIG variable that hold your parsed Fetchwarefile.';
1523 373         2590 __clear_CONFIG();
1524              
1525 373         800 msg 'Cleaned up temporary directory.';
1526              
1527             # Return true.
1528 373         1081 return 'Cleaned up tempdir';
1529             }
1530              
1531              
1532             } # End scope block for $original_cwd and $fh_sem.
1533              
1534             1;
1535              
1536             =pod
1537              
1538             =head1 NAME
1539              
1540             App::Fetchware::Util - Miscelaneous functions for App::Fetchware.
1541              
1542             =head1 VERSION
1543              
1544             version 1.016
1545              
1546             =head1 SYNOPSIS
1547              
1548             use App::Fetchware::Util ':UTIL';
1549              
1550              
1551             # Logging subroutines.
1552             msg 'message to print to STDOUT';
1553              
1554             vmsg 'message to print to STDOUT';
1555              
1556              
1557             # Run external command subroutine.
1558             run_prog($program, @args);
1559              
1560              
1561             # Download subroutines.
1562             my $dir_list = download_dirlist($ftp_or_http_url)
1563              
1564             my $dir_list = ftp_download_dirlist($ftp_url);
1565              
1566             my $dir_list = http_download_dirlist($http_url);
1567              
1568              
1569             my $filename = download_file($url)
1570              
1571             my $filename = download_ftp_url($url);
1572              
1573             my $filename = download_http_url($url);
1574              
1575             my $filename = download_file_url($url);
1576              
1577              
1578             # Miscelaneous subroutines.
1579             just_filename()
1580              
1581             do_nothing();
1582              
1583              
1584             # Temporary directory subroutines.
1585             my $temp_dir = create_tempdir();
1586              
1587             my $original_cwd = original_cwd();
1588              
1589             cleanup_tempdir();
1590              
1591             =head1 DESCRIPTION
1592              
1593             App::Fetchware::Util holds miscelaneous utilities that fetchware needs for
1594             various purposes such as logging and controling executed processes based on -q
1595             or -v switches (msg(), vmsg(), run_prog()), subroutines for downloading
1596             directory listings (*_dirlist()) or files (download_*()) using ftp, http, or
1597             local files (file://), do_nothing() for extensions to fetchware, and subroutines
1598             for managing a temporary directory.
1599              
1600             =head1 LOGGING SUBROUTINES
1601              
1602             These subroutines' log messages generated by fetchware by printing them to
1603             C. They do not currently support logging to a file directly, but you
1604             could redirect fetchware's standard output to a file using your shell if you
1605             want to:
1606              
1607             fetchware any arguments > fetchware.log
1608             fetchware upgrade-all > fetchware.log
1609              
1610             =head2 Standards for using msg() and vmsg()
1611              
1612             msg() should be used to describe the main events that happen, while vmsg()
1613             should be used to describe what all of the main subroutine calls do.
1614              
1615             For example, cmd_uninstall() has a msg() at the beginning and at the end, and so
1616             do the main App::Fetchware subroutines that it uses such as start(), download(),
1617             unarchive(), end() and so on. They both use vmsg() to add more detailed messages
1618             about the particular even "internal" things they do.
1619              
1620             msg() and vmsg() are also used without parens due to their appropriate
1621             prototypes. This makes them stand out from regular old subroutine calls more.
1622              
1623             =head2 msg()
1624              
1625             msg 'message to print to STDOUT' ;
1626             msg('message to print to STDOUT');
1627              
1628             msg() simply takes a list of scalars, and it prints them to STDOUT according to
1629             any verbose (-v), or quiet (-q) options that the user may have provided to
1630             fetchware.
1631              
1632             msg() will still print its arguments if the user provided a -v (verbose)
1633             argument, but it will B print its argument if the user provided a -q (quiet)
1634             command line option.
1635              
1636             =over
1637             =item This subroutine makes use of prototypes, so that you can avoid using parentheses around its args to make it stand out more in code.
1638              
1639             =back
1640              
1641             =head2 vmsg()
1642              
1643             vmsg 'message to print to STDOUT' ;
1644             vmsg('message to print to STDOUT');
1645              
1646             vmsg() simply takes a list of scalars, and it prints them to STDOUT according to
1647             any verbose (-v), or quiet (-q) options that the user may have provided to
1648             fetchware.
1649              
1650             vmsg() will B print its arguments if the user provided a -v (verbose)
1651             argument, but it will B print its argument if the user provided a -q (quiet)
1652             command line option.
1653              
1654             =over
1655             =item This subroutine makes use of prototypes, so that you can avoid using parentheses around its args to make it stand out more in code.
1656              
1657             =back
1658              
1659             =head1 EXTERNAL COMMAND SUBROUTINES
1660              
1661             run_prog() should be the B function you use to execute external commands
1662             when you L, or L,
1663             because run_prog() properly checks if the user specified the quiet switch
1664             (C<-q>), and disables external commands from printing to C if it has
1665             been enabled.
1666              
1667             =head2 run_prog()
1668              
1669             run_prog($program, @args);
1670              
1671             # Or let run_prog() deal with splitting the $command into multiple pieces.
1672             run_prig($command);
1673              
1674             run_prog() uses L to execute the program for you. Only the secure way of
1675             avoiding the shell is used, so you can not use any shell redirection or any
1676             shell builtins.
1677              
1678             If the user ran fetchware with -v (verbose) then run_prog() changes none of its
1679             behavior it still just executes the program. However, if the user runs the
1680             program with -q (quiet) specified, then the the command is run using a piped
1681             open to capture the output of the program. This captured output is then ignored,
1682             because the user asked to never be bothered with the output. This piped open
1683             uses the safer shell avoiding syntax on systems with L, and systems
1684             without L, Windows, the older less safe syntax is used. Backticks are
1685             avoided, because they always use the shell.
1686              
1687             run_prog() when called with only one argument will split that one argument into
1688             multiple pieces using L quotewords() subroutine, which
1689             properly deals with quotes just like the shell does. quotewords() is always used
1690             even if you provide an already split up list of arguments to run_prog().
1691              
1692             =head2 Executing external commands without using run_prog()
1693              
1694             msg(), vmsg(), and run_prog() determine if -v and if -q were specified by
1695             checking the values of the global variables listed below:
1696              
1697             =over
1698              
1699             =item * $fetchware::quiet - is C<0> if -q was B specified.
1700              
1701             =item * $fetchware::verbose - is C<0> if -v was B specified.
1702              
1703             =back
1704              
1705             Both of these variables work the same way. If they are 0, then -q or -v was
1706             B specified. And if they are defined and greather than (>) 0, then -q or -v
1707             were specified on the command line. You should test for greater than 0 B
1708             B<== 1>, because Fetchware takes advantage of a cool feature in GetOpt::Long
1709             allowing the user to specify -v and -q more than once. This triggers either
1710             $fetchware::quiet or $fetchware::verbose to be greater than one, which would
1711             cause a direct C<== 1> test to fail even though the user is no asking for
1712             I verbose messages. Internally Fetchware only supports one verbositly
1713             level.
1714              
1715             =head1 DOWNLOAD SUBROUTINES
1716              
1717             App::Fetchware::Util's download_*() and *_dirlist() subroutines allow you to
1718             download FTP, HTTP, or local file (file://) directory listings or files
1719             respectively.
1720              
1721             =over
1722             =item NOTICE
1723             Each *_dirlist() subroutine returns its own format that is different from the
1724             others. Fetchware uses the *_parse_filelist() subroutines to parse this
1725             differing directory listings into a specifc format of an array of arrays of
1726             filenames and timestamps. You could load these subroutines from the
1727             C App::Fetchware export tag to use in your Fetchwarefile or
1728             your fetchware extension.
1729              
1730             =back
1731              
1732             =head2 download_dirlist()
1733              
1734             my $dir_list = download_dirlist($url)
1735              
1736             my $dir_list = download_dirlist(PATH => $path)
1737              
1738             Can be called with either a $url or a PATH parameter. When called with a $url
1739             parameter, the specified $url is downloaded using no_mirror_download_dirlist(),
1740             and returned if successful. If it fails then each C the user specified
1741             is also tried unitl there are no more mirrors, and then an exception is thrown.
1742              
1743             If you specify a PATH parameter instead of a $url parameter, then that path is
1744             appended to each C, and the resultant url is downloaded using
1745             no_mirror_download_dirlist().
1746              
1747             =head2 no_mirror_download_dirlist()
1748              
1749             my $dir_list = no_mirror_download_dirlist($ftp_or_http_url)
1750              
1751             Downloads a ftp or http url and assumes that it will be downloading a directory
1752             listing instead of an actual file. To download an actual file use
1753             L. download_dirlist returns the directory listing that it
1754             obtained from the ftp or http server. ftp server will be an arrayref of C
1755             like output, while the http output will be a scalar of the HTML dirlisting
1756             provided by the http server.
1757              
1758             =head2 ftp_download_dirlist()
1759              
1760             my $dir_list = ftp_download_dirlist($ftp_url);
1761              
1762             Uses Net::Ftp's dir() method to obtain a I directory listing. lookup()
1763             needs it in I format, so that the timestamp algorithm has access to each
1764             file's timestamp.
1765              
1766             Returns an array ref of the directory listing.
1767              
1768             =head2 http_download_dirlist()
1769              
1770             my $dir_list = http_download_dirlist($http_url);
1771              
1772             Uses HTTP::Tiny to download a HTML directory listing from a HTTP Web server.
1773              
1774             Returns an scalar of the HTML ladden directory listing.
1775              
1776             If an even number of other options are specified (a faux hash), then those
1777             options are forwarded on to L's new() method. See L for
1778             details about what these options are. For example, you couse use this to add a
1779             C header to your request if a download site annoying checks referrers.
1780              
1781             =head2 file_download_dirlist()
1782              
1783             my $file_listing = file_download_dirlist($local_lookup_url)
1784              
1785             Glob's provided $local_lookup_url, and builds a directory listing of all files
1786             in the provided directory.
1787              
1788             =head2 download_file()
1789              
1790             my $filename = download_file($url)
1791              
1792             my $filename = download_file(PATH => $path)
1793              
1794             Can be called with either a $url or a PATH parameter. When called with a $url
1795             parameter, the specified $url is downloaded using no_mirror_download_file(),
1796             and returned if successful. If it fails then each C the user specified
1797             is also tried unitl there are no more mirrors, and then an exception is thrown.
1798              
1799             If you specify a PATH parameter instead of a $url parameter, then that path is
1800             appended to each C, and the resultant url is downloaded using
1801             no_mirror_download_file().
1802              
1803             =head2 no_mirror_download_file()
1804              
1805             my $filename = no_mirror_download_file($url)
1806              
1807             Downloads one $url and assumes it is a file that will be downloaded instead of a
1808             file listing that will be returned. no_mirror_download_file() returns the file
1809             name of the file it downloads.
1810              
1811             Like its name says it does not try any configured mirrors at all. This
1812             subroutine should not be used; instead download_file() should be used, because
1813             you should respect your user's desired mirrors.
1814              
1815             =head2 download_ftp_url()
1816              
1817             my $filename = download_ftp_url($url);
1818              
1819             Uses Net::FTP to download the specified FTP URL using binary mode.
1820              
1821             =head2 download_http_url()
1822              
1823             my $filename = download_http_url($url);
1824              
1825             Uses HTTP::Tiny to download the specified HTTP URL.
1826              
1827             Supports adding extra arguments to HTTP::Tiny's new() constructor. These
1828             arguments are B checked for correctness; instead, they are simply forwarded
1829             to HTTP::Tiny, which does not check them for correctness either. HTTP::Tiny
1830             simply loops over its internal listing of what is arguments should be, and then
1831             accesses the arguments if they exist.
1832              
1833             This was really only implemented to allow App::FetchwareX::HTMLPageSync to change
1834             its user agent string to avoid being blocked or freaking out Web developers that
1835             they're being screen scraped by some obnoxious bot as HTMLPageSync is wimpy and
1836             harmless, and only downloads one page.
1837              
1838             You would add an argument like this:
1839             download_http_url($http_url, agent => 'Firefox');
1840              
1841             See HTTP::Tiny's documentation for what these options are.
1842              
1843             =head2 download_file_url()
1844              
1845             my $filename = download_file_url($url);
1846              
1847             Uses File::Copy to copy ("download") the local file to the current working
1848             directory.
1849              
1850             =head1 TEMPDIR SUBROUTINES
1851              
1852             These subroutines manage the creation of a temporary directory for you. They
1853             also implement the original_cwd() getter subroutine that returns the current
1854             working directory fetchware was at before create_tempdir() chdir()'d to the
1855             temporary directory you specify. File::Temp's tempdir() is used, and
1856             cleanup_tempdir() manages the C fetchware semaphore file.
1857              
1858             =over
1859             =item NOTICE
1860             App::Fetchware::Util's temporary directory creation utilities, create_tempdir(),
1861             original_cwd(), and cleanup_tempdir(), only keep track of one tempdir at a time. If
1862             you create another tempdir with create_tempdir() it will override the value of
1863             original_cwd(), which may mess up other functions that call create_tempdir(),
1864             original_cwd(), and cleanup_tempdir(). Therefore, becareful when you call these
1865             functions, and do B use them inside a fetchware extension if you reuse
1866             App::Fetchware's start() and end(), because App::Fetchware's start() and end()
1867             use these functions, so your use of them will conflict. If you still need to
1868             create a tempdir just call File::Temp's tempdir() directly.
1869              
1870             =back
1871              
1872             =head2 create_tempdir()
1873              
1874             my $temp_dir = create_tempdir();
1875              
1876             Creates a temporary directory, chmod 700's it, and chdir()'s into it.
1877              
1878             Accepts the fake hash argument C 1>, which tells create_tempdir()
1879             to B delete the temporary directory when the program exits.
1880              
1881             Also, accepts C '/tmp'> to specify what temporary directory to
1882             use. The default with out this argument is to use tempdir()'s default, which is
1883             whatever File::Spec's tmpdir() says to use.
1884              
1885             The C 1> option causes create_tempdir() to B chown to
1886             config('user').
1887              
1888             =head3 Locking Fetchware's temp directories with a semaphore file.
1889              
1890             In order to support C, create_tempdir() creates a semaphore
1891             file. The file is used by C (via bin/fetchware's cmd_clean())
1892             to determine if another fetchware process out there is currently using this
1893             temporary directory, and if it is not, the file is not currently locked with
1894             flock, then the entire directory is deleted using File::Path's remove_path()
1895             function. If the file is there and locked, then the directory is skipped by
1896             cmd_clean().
1897              
1898             cleanup_tempdir() is responsible for unlocking the semaphore file that
1899             create_tempdir() creates. However, the coolest part of using flock is that if
1900             fetchware is killed in any manner whether its C block or File::Temp's
1901             Cblock run, the OS will still unlock the file, so no edge cases need
1902             handling, because the OS will do them for us!
1903              
1904             =head2 original_cwd()
1905              
1906             my $original_cwd = original_cwd();
1907              
1908             original_cwd() simply returns the value of fetchware's $original_cwd that is
1909             saved inside each create_tempdir() call. A new call to create_tempdir() will
1910             reset this value. Note: App::Fetchware's start() also calls create_tempdir(), so
1911             another call to start() will also reset original_cwd().
1912              
1913             =head2 cleanup_tempdir()
1914              
1915             cleanup_tempdir();
1916              
1917             Cleans up B temporary files or directories that anything in this process used
1918             File::Temp to create. You cannot only clean up one directory or another;
1919             instead, you must just use this sparingly or in an END block although file::Temp
1920             takes care of that for you unless you asked it not to.
1921              
1922             It also closes $fh_sem, which is the filehandle of the 'fetchware.sem' file
1923             create_tempdir() opens and I. By closing it in cleanup_tempdir(), we're
1924             unlocking it. According to MJD's "File Locking Tips and Traps," it's better to
1925             just close the file, then use flock to unlock it.
1926              
1927             =head1 SECURITY SUBROUTINES
1928              
1929             This section describes Utilty subroutines that can be used for checking security
1930             of files on the file system to see if fetchware should open and use them.
1931              
1932             =head2 safe_open()
1933              
1934             my $fh = safe_open($file_to_check, <
1935             App-Fetchware-Extension???: Failed to open file [$file_to_check]! Because of
1936             OS error [$!].
1937             EOE
1938              
1939             # To open for writing instead of reading
1940             my $fh = safe_open($file_to_check, < '>');
1941             App-Fetchware-Extension???: Failed to open file [$file_to_check]! Because of
1942             OS error [$!].
1943             EOE
1944              
1945             safe_open() takes $file_to_check and does a bunch of file checks on that
1946             file to determine if it's safe to open and use the contents of that file in
1947             your program. Instead of returning true or false, it returns a file handle of
1948             the file you want to check that has already been open for you. This is done to
1949             prevent race conditions between the time safe_open() checks the file's safety
1950             and the time the caller actually opens the file.
1951              
1952             safe_open() also takes an optional second argument that specifies a caller
1953             specific error message that replaces the generic default one.
1954              
1955             Fetchware occasionally needs to write files especially in fetchware's new()
1956             command; therefore safe_open() also takes the fake hash argument
1957             C 'E'>, which opens the file in a mode specified by the caller.
1958             C<'E'> is for writing for example. See C for a list of
1959             possible modes.
1960              
1961             In fetchware, this subroutine is used to check if every file fetchware
1962             opens is safe to do so. It is based on is_safe() and is_very_safe() from the
1963             Perl Cookbook by Tom Christiansen and Nathan Torkington.
1964              
1965             What this subroutine checks:
1966              
1967             =over
1968              
1969             =item *
1970              
1971             It opens the file you give to it as an argument, and all subsequent operations
1972             are done on the opened filehandle to prevent race conditions.
1973              
1974             =item *
1975              
1976             Then it checks that the owner of the specified file must be either the superuser
1977             or the user who ran fetchware.
1978              
1979             =item *
1980              
1981             It checks that the mode, as returned by File::stat's overridden stat, is not
1982             writable by group or other. Fancy MAC permissions such as Linux's extfs's
1983             extensions and fancy Windows permissions are B currently checked.
1984              
1985             =item *
1986              
1987             Then safe_open() stat's each and every parent directory that is in this file's
1988             full path, and runs the same checks that are run above on each parent directory.
1989              
1990             =item *
1991              
1992             _PC_CHOWN_RESTRICTED is not tested; instead what is_very_safe() does is simply
1993             always done. Because even with A _PC_CHOWN_RESTRICTED test, /home, for example,
1994             could be 777. This is Unix after all, and root can do anything including screw
1995             up permissions on system directories.
1996              
1997             =back
1998              
1999             If you actually are some sort of security expert, please feel free to
2000             double-check if the list of stuff to check for is complete, and perhaps even the
2001             Perl implementation to see if the subroutine really does check if
2002             safe_open($file_to_check) is actually safe.
2003              
2004             =over
2005              
2006             =item WARNING
2007              
2008             According to L's chmod() documentation, on Win32 perl's Unixish file
2009             permissions arn't supported only "owner" is:
2010              
2011             "Only good for changing "owner" read-write access, "group", and "other" bits are
2012             meaningless. (Win32)"
2013              
2014             I'm not completely sure this means that under Win32 only owner perms mean
2015             something, or if just chmod()ing group or ther bits don't do anything, but
2016             testing if group and other are rwx does work. This needs testing.
2017              
2018             And remember this only applies to Win32, and fetchware has not yet been properly
2019             ported or tested under Win32 yet.
2020              
2021             =back
2022              
2023             =head2 drop_privs()
2024              
2025             my $output = drop_privs(sub {
2026             my $write_pipe = shift;
2027             # Do stuff as $regular_user
2028             ...
2029             # Use write_dropprivs_pipe to share variables back to parent.
2030             write_dropprivs_pipe($write_pipe, $var1, $var2, ...);
2031              
2032             }, $regular_user
2033             );
2034              
2035             # Back in the parent, use read_dropprivs_pipe() to read in whatever
2036             # variables the child shared with us.
2037             my ($var1, $var2, ...) = read_dropprivs_pipe($output);
2038              
2039             Forks and drops privs to $regular_user, and then executes whatever is in the
2040             first argument, which should be a code reference. Throws an exception on any
2041             problems with the fork.
2042              
2043             It only allows you to specify what the lower priveledged user does. The parent
2044             process's behavior can not be changed. All the parent does:
2045              
2046             =over
2047              
2048             =item *
2049              
2050             Create a pipe to allow the child to communicate any information back to the
2051             parent.
2052              
2053             =item *
2054              
2055             Read any data the child may write to that pipe.
2056              
2057             =item *
2058              
2059             After the child has died, collect the child's exit status.
2060              
2061             =item *
2062              
2063             And return the output the child wrote on the pipe as a scalar reference.
2064              
2065             =back
2066              
2067             Whatever the child writes is returned. drop_privs() does not use Storable or
2068             JSON or XML or anything. It is up to you to specify how the data is to be
2069             represented and used. However, L and
2070             L are provided. They provide a simple way to store
2071             multiple variables that can have any character in them including newline. See
2072             their documentation for details.
2073              
2074             =over
2075              
2076             =item SECURITY NOTICE
2077              
2078             The output returned by drop_privs() is whatever the child wants it to be. If
2079             somehow the child got hacked, the $output could be something that could cause
2080             the parent (which has root perms!) to execute some code, or otherwise do
2081             something that could cause the child to gain root access. So be sure to check
2082             how you use drop_privs() return value, and definitley don't just string eval it.
2083             Structure it so the return value can only be used as data for variables, and
2084             that those variables are never executed by root.
2085              
2086             =back
2087              
2088             drop_privs() handles being on nonunix for you. On a platform that is not Unix
2089             that does not have Unix's fork() and exec() security model, drop_privs() simply
2090             executes the provided code reference I dropping priveledges.
2091              
2092             =over
2093              
2094             =item USABILITY NOTICE
2095              
2096             drop_privs()'s implementation depends on start() creating a tempdir and
2097             chdir()ing to it. Furthermore, drop_privs() sometimes creates a tempdir of its
2098             own, and it does not do a chdir back to another directory, so drop_privs()
2099             depends on end() to chdir back to original_cwd(). Therefore, do not use
2100             drop_privs() without also using start() and end() to manage a temporary
2101             directory for drop_privs().
2102              
2103             =back
2104              
2105             drop_privs() also supports a C 1> option that turns
2106             off drop_privs() creating a temporary diretory to give the child a writable
2107             temporary directory. This option is only used by cmd_new(), and probably only
2108             really needs to be used there. Also, note that you must provide this option
2109             after the $child_code coderef, and the $regular user options. Like so,
2110             C 1>.
2111              
2112             =head2 drop_privs() PIPE PARSING UTILITIES
2113              
2114             drop_privs() uses a pipe for IPC between the child and the parent. This section
2115             contains utilties that help users of drop_privs() parse the input and output
2116             they send from the child back to the parent.
2117              
2118             Use write_dropprivs_pipe() to send data back to the parent, that later you'll read
2119             with read_dropprivs_pipe() back in the parent.
2120              
2121             =head3 write_dropprivs_pipe()
2122              
2123             write_dropprivs_pipe($write_pipe, $variable1, $variable2, $variable3);
2124              
2125             Simply uses the caller provided $write_pipe file handle to write the rest of its
2126             args to that file handle separated by a I.
2127              
2128             This magic number is just generated uniquely each time App::Fetchware::Util is
2129             compiled. This number replaces using newline to separate each of the variables
2130             that write_dropprivs_pipe() writes. This way you can include newline, and in
2131             fact anything that does not contain the magic number, which is obviously
2132             suitably unlikely.
2133              
2134             =over
2135              
2136             =item UNDEF AND EMPTY STRING WARNING
2137              
2138             write_dropprivs_pipe() and read_dropprivs_pipe() both bizarely, accidentily
2139             I undef. It's really a function of Perl's C operators
2140             side-effect of returning undef when there is no data to actually return, but the
2141             seperator actually does exist. However, do B depend on this so called
2142             "preservation", because C<''>, empty string, is converted into undef by
2143             read_dropprivs_pipe() preventing you from distinguishing between the two values.
2144              
2145             =back
2146              
2147             =head3 read_dropprivs_pipe()
2148              
2149             my ($variable1, $variable2, $variable3) = pipe_read_newling($output);
2150              
2151             read_dropprivs_pipe() opens the scalar $output, and returns a list of $outputs
2152             parsed out variables split on the $MAGIC_NUMBER, which is randomly generated
2153             during each time you run Fetchware to avoid you every actually using it.
2154              
2155             =over
2156              
2157             =item UNDEF AND EMPTY STRING WARNING
2158              
2159             write_dropprivs_pipe() and read_dropprivs_pipe() both bizarely, accidentily
2160             I undef. It's really a function of Perl's C operators
2161             side-effect of returning undef when there is no data to actually return, but the
2162             seperator actually does exist. However, do B depend on this so called
2163             "preservation", because C<''>, empty string, is converted into undef by
2164             read_dropprivs_pipe() preventing you from distinguishing between the two values.
2165              
2166             =back
2167              
2168             =head1 MISCELANEOUS UTILTY SUBROUTINES
2169              
2170             This is just a catch all category for everything else in App::Fetchware::Utility.
2171              
2172             =head2 do_nothing()
2173              
2174             do_nothing();
2175              
2176             do_nothing() does nothing but return. It simply returns doing nothing. It is
2177             meant to be used by App::Fetchware "subclasses" that "override" App::Fetchware's
2178             API subroutines to make those API subroutines do nothing.
2179              
2180             =head1 ERRORS
2181              
2182             As with the rest of App::Fetchware, App::Fetchware::Util does not return any
2183             error codes; instead, all errors are die()'d if it's App::Fetchware::Util's
2184             error, or croak()'d if its the caller's fault. These exceptions are simple
2185             strings, and are listed in the L section below.
2186              
2187             =head1 BUGS
2188              
2189             App::Fetchware::Util's temporary directory creation utilities, create_tempdir(),
2190             original_cwd(), and cleanup_tempdir(), only keep track of one tempdir at a time. If
2191             you create another tempdir with create_tempdir() it will override the value of
2192             original_cwd(), which may mess up other functions that call create_tempdir(),
2193             original_cwd(), and cleanup_tempdir(). Therefore, be careful when you call these
2194             functions, and do B use them inside a fetchware extension if you reuse
2195             App::Fetchware's start() and end(), because App::Fetchware's start() and end()
2196             use these functions, so your use of them will conflict. If you still need to
2197             create a tempdir just call File::Temp's tempdir() directly.
2198              
2199             =head1 AUTHOR
2200              
2201             David Yingling
2202              
2203             =head1 COPYRIGHT AND LICENSE
2204              
2205             This software is copyright (c) 2016 by David Yingling.
2206              
2207             This is free software; you can redistribute it and/or modify it under
2208             the same terms as the Perl 5 programming language system itself.
2209              
2210             =cut
2211              
2212             __END__