# In this section, perl variables will be expanded during extraction.
# You can use $Config{...} to use Configure variables.
+my $versiononly = $Config{versiononly} ? $Config{version} : '';
+
print OUT <<"!GROK!THIS!";
$Config{startperl}
eval 'exec $Config{perlpath} -S \$0 \${1+"\$@"}'
if 0;
+use warnings;
use strict;
+
+# make sure creat()s are neither too much nor too little
+INIT { eval { umask(0077) } } # doubtless someone has no mask
+
+(my \$pager = <<'/../') =~ s/\\s*\\z//;
+$Config{pager}
+/../
my \@pagers = ();
-push \@pagers, "$Config{'pager'}" if -x "$Config{'pager'}";
+push \@pagers, \$pager if -x \$pager;
+
+(my \$bindir = <<'/../') =~ s/\\s*\\z//;
+$Config{scriptdirexp}
+/../
+
+(my \$pod2man = <<'/../') =~ s/\\s*\\z//;
+pod2man$versiononly
+/../
+
!GROK!THIS!
# In the following, perl variables are not expanded during extraction.
print OUT <<'!NO!SUBS!';
+use Fcntl; # for sysopen
+use Getopt::Std;
+use Config '%Config';
+use File::Spec::Functions qw(catfile splitdir);
+
#
# Perldoc revision #1 -- look up a piece of documentation in .pod format that
# is embedded in the perl installation tree.
#
-# This is not to be confused with Tom Christianson's perlman, which is a
+# This is not to be confused with Tom Christiansen's perlman, which is a
# man replacement, written in perl. This perldoc is strictly for reading
# the perl manuals, though it too is written in perl.
+#
+# Massive security and correctness patches applied to this
+# noisome program by Tom Christiansen Sat Mar 11 15:22:33 MST 2000
if (@ARGV<1) {
my $me = $0; # Editing $0 is unportable
$me -q FAQKeywords
The -h option prints more help. Also try "perldoc perldoc" to get
-aquainted with the system.
+acquainted with the system.
EOF
}
-use Getopt::Std;
-use Config '%Config';
-
my @global_found = ();
my $global_target = "";
my $Is_VMS = $^O eq 'VMS';
my $Is_MSWin32 = $^O eq 'MSWin32';
my $Is_Dos = $^O eq 'dos';
+my $Is_OS2 = $^O eq 'os2';
sub usage{
warn "@_\n" if @_;
-v Verbosely describe what's going on
-X use index if present (looks for pod.idx at $Config{archlib})
-q Search the text of questions (not answers) in perlfaq[1-9]
+ -U Run in insecure mode (superuser only)
PageName|ModuleName...
is the name of a piece of documentation that you want to look at. You
}
!NO!SUBS!
-my $getopts = "mhtluvriFf:Xq:n:";
+my $getopts = "mhtluvriFf:Xq:n:U";
print OUT <<"!GET!OPTS!";
use vars qw( @{[map "\$opt_$_", ($getopts =~ /\w/g)]} );
print OUT <<'!NO!SUBS!';
usage if $opt_h;
+
+# refuse to run if we should be tainting and aren't
+# (but regular users deserve protection too, though!)
+if (!($Is_VMS || $Is_MSWin32 || $Is_Dos || $Is_OS2) && ($> == 0 || $< == 0)
+ && !am_taint_checking())
+{{
+ if ($opt_U) {
+ my $id = eval { getpwnam("nobody") };
+ $id = eval { getpwnam("nouser") } unless defined $id;
+ $id = -2 unless defined $id;
+ #
+ # According to Stevens' APUE and various
+ # (BSD, Solaris, HP-UX) man pages setting
+ # the real uid first and effective uid second
+ # is the way to go if one wants to drop privileges,
+ # because if one changes into an effective uid of
+ # non-zero, one cannot change the real uid any more.
+ #
+ # Actually, it gets even messier. There is
+ # a third uid, called the saved uid, and as
+ # long as that is zero, one can get back to
+ # uid of zero. Setting the real-effective *twice*
+ # helps in *most* systems (FreeBSD and Solaris)
+ # but apparently in HP-UX even this doesn't help:
+ # the saved uid stays zero (apparently the only way
+ # in HP-UX to change saved uid is to call setuid()
+ # when the effective uid is zero).
+ #
+ eval {
+ $< = $id; # real uid
+ $> = $id; # effective uid
+ $< = $id; # real uid
+ $> = $id; # effective uid
+ };
+ last if !$@ && $< && $>;
+ }
+ die "Superuser must not run $0 without security audit and taint checks.\n";
+}}
+
$opt_n = "nroff" if !$opt_n;
my $podidx;
$podidx = "" unless -f $podidx && -r _ && -M _ <= 7;
}
-if ((my $opts = do{ local $^W; $opt_t + $opt_u + $opt_m + $opt_l }) > 1) {
+if ((my $opts = do{ no warnings; $opt_t + $opt_u + $opt_m + $opt_l }) > 1) {
usage("only one of -t, -u, -m or -l")
}
elsif ($Is_MSWin32
|| $Is_Dos
- || !(exists $ENV{TERM} && $ENV{TERM} !~ /dumb|emacs|none|unknown/i))
+ || !($ENV{TERM} && $ENV{TERM} !~ /dumb|emacs|none|unknown/i))
{
- $opt_t = 1 unless $opts
+ $opt_t = 1 unless $opts;
}
if ($opt_t) { require Pod::Text; import Pod::Text; }
# Does this look like a module or extension directory?
if (-f "Makefile.PL") {
- # Add ., lib and blib/* libs to @INC (if they exist)
- unshift(@INC, '.');
- unshift(@INC, 'lib') if -d 'lib';
- require ExtUtils::testlib;
+
+ # Add ., lib to @INC (if they exist)
+ eval q{ use lib qw(. lib); 1; } or die;
+
+ # don't add if superuser
+ if ($< && $> && -f "blib") { # don't be looking too hard now!
+ eval q{ use blib; 1 };
+ warn $@ if $@ && $opt_v;
+ }
}
sub containspod {
my($file, $readit) = @_;
- return 1 if !$readit && $file =~ /\.pod$/i;
+ return 1 if !$readit && $file =~ /\.pod\z/i;
local($_);
- open(TEST,"<$file");
+ open(TEST,"<", $file) or die "Can't open $file: $!";
while (<TEST>) {
if (/^=head/) {
- close(TEST);
+ close(TEST) or die "Can't close $file: $!";
return 1;
}
}
- close(TEST);
+ close(TEST) or die "Can't close $file: $!";
return 0;
}
sub minus_f_nocase {
my($dir,$file) = @_;
- my $path = join('/',$dir,$file);
+ my $path = catfile($dir,$file);
return $path if -f $path and -r _;
if (!$opt_i or $Is_VMS or $Is_MSWin32 or $Is_Dos or $^O eq 'os2') {
# on a case-forgiving file system or if case is important
return '';
}
local *DIR;
+ # this is completely wicked. don't mess with $", and if
+ # you do, don't assume / is the dirsep!
local($")="/";
my @p = ($dir);
my($p,$cip);
- foreach $p (split(/\//, $file)){
- my $try = "@p/$p";
+ foreach $p (splitdir $file){
+ my $try = catfile @p, $p;
stat $try;
if (-d _) {
push @p, $p;
if ( $p eq $global_target) {
- my $tmp_path = join ('/', @p);
+ my $tmp_path = catfile @p;
my $path_f = 0;
for (@global_found) {
$path_f = 1 if $_ eq $tmp_path;
elsif (-f _) {
warn "Ignored $try: unreadable\n";
}
- else {
+ elsif (-d "@p") {
my $found=0;
my $lcp = lc $p;
- opendir DIR, "@p";
+ opendir DIR, "@p" or die "opendir @p: $!";
while ($cip=readdir(DIR)) {
if (lc $cip eq $lcp){
$found++;
last;
}
}
- closedir DIR;
+ closedir DIR or die "closedir @p: $!";
return "" unless $found;
push @p, $cip;
return "@p" if -f "@p" and -r _;
my $ret;
my $i;
my $dir;
- $global_target = (split('/', $s))[-1];
+ $global_target = (splitdir $s)[-1]; # XXX: why not use File::Basename?
for ($i=0; $i<@dirs; $i++) {
$dir = $dirs[$i];
- ($dir = VMS::Filespec::unixpath($dir)) =~ s!/$!! if $Is_VMS;
- if ( ( $ret = check_file $dir,"$s.pod")
+ ($dir = VMS::Filespec::unixpath($dir)) =~ s!/\z!! if $Is_VMS;
+ if ( (! $opt_m && ( $ret = check_file $dir,"$s.pod"))
or ( $ret = check_file $dir,"$s.pm")
or ( $ret = check_file $dir,$s)
or ( $Is_VMS and
}
if ($recurse) {
- opendir(D,$dir);
- my @newdirs = map "$dir/$_", grep {
- not /^\.\.?$/ and
- not /^auto$/ and # save time! don't search auto dirs
- -d "$dir/$_"
+ opendir(D,$dir) or die "Can't opendir $dir: $!";
+ my @newdirs = map catfile($dir, $_), grep {
+ not /^\.\.?\z/s and
+ not /^auto\z/s and # save time! don't search auto dirs
+ -d catfile($dir, $_)
} readdir D;
- closedir(D);
+ closedir(D) or die "Can't closedir $dir: $!";
next unless @newdirs;
- @newdirs = map((s/.dir$//,$_)[1],@newdirs) if $Is_VMS;
+ # what a wicked map!
+ @newdirs = map((s/\.dir\z//,$_)[1],@newdirs) if $Is_VMS;
print STDERR "Also looking in @newdirs\n" if $opt_v;
push(@dirs,@newdirs);
}
join "\n\n", @data;
}
-sub printout {
- my ($file, $tmp, $filter) = @_;
- my $err;
-
- if ($opt_t) {
- open(OUT,">>$tmp") or warn("Can't open $tmp: $!"), return;
- Pod::Text->new()->parse_from_file($file,\*OUT);
- close OUT;
- }
- elsif (not $opt_u) {
- my $cmd = "pod2man --lax $_ | $opt_n -man";
- $cmd .= " | col -x" if $^O =~ /hpux/;
- my $rslt = `$cmd`;
- $rslt = filter_nroff($rslt) if $filter;
- unless (($err = $?)) {
- open(TMP,">>$tmp") or warn("Can't open $tmp: $!"), return;
- print TMP $rslt;
- close TMP;
- }
- }
- if ($opt_u or $err or -z $tmp) {
- open(OUT,">>$tmp") or warn("Can't open $tmp: $!"), return;
- open(IN,"<$file") or warn("Can't open $file: $!"), return;
- my $cut = 1;
- while (<IN>) {
- $cut = $1 eq 'cut' if /^=(\w+)/;
- next if $cut;
- print OUT;
- }
- close IN;
- close OUT;
- }
-}
-
sub page {
my ($tmp, $no_tty, @pagers) = @_;
if ($no_tty) {
- open(TMP,"<$tmp") or warn("Can't open $tmp: $!"), return;
- print while <TMP>;
- close TMP;
+ open(TMP,"<", $tmp) or die "Can't open $tmp: $!";
+ local $_;
+ while (<TMP>) {
+ print or die "Can't print to stdout: $!";
+ }
+ close TMP or die "Can't close while $tmp: $!";
}
else {
- foreach my $pager (@pagers) {
- system("$pager $tmp") or last;
+ # On VMS, quoting prevents logical expansion, and temp files with no
+ # extension get the wrong default extension (such as .LIS for TYPE)
+
+ $tmp = VMS::Filespec::rmsexpand($tmp, '.') if ($Is_VMS);
+ foreach my $pager (@pagers) {
+ if ($Is_VMS) {
+ last if system("$pager $tmp") == 0;
+ } else {
+ last if system("$pager \"$tmp\"") == 0;
+ }
}
}
}
-sub cleanup {
- my @files = @_;
- for (@files) {
- 1 while unlink($_); #Possibly pointless VMSism
- }
-}
-
-sub safe_exit {
- my ($val, @files) = @_;
- cleanup(@files);
- exit $val;
-}
-
-sub safe_die {
- my ($msg, @files) = @_;
- cleanup(@files);
- die $msg;
-}
-
my @found;
foreach (@pages) {
if ($podidx && open(PODIDX, $podidx)) {
- my $searchfor = $_;
- local($_);
- $searchfor =~ s,::,/,g;
+ my $searchfor = catfile split '::';
print STDERR "Searching for '$searchfor' in $podidx\n" if $opt_v;
+ local $_;
while (<PODIDX>) {
chomp;
- push(@found, $_) if m,/$searchfor(?:\.(?:pod|pm))?$,i;
+ push(@found, $_) if m,/$searchfor(?:\.(?:pod|pm))?\z,i;
}
- close(PODIDX);
+ close(PODIDX) or die "Can't close $podidx: $!";
next;
}
print STDERR "Searching for $_\n" if $opt_v;
- # We must look both in @INC for library modules and in PATH
- # for executables, like h2xs or perldoc itself.
- my @searchdirs = @INC;
if ($opt_F) {
next unless -r;
push @found, $_ if $opt_m or containspod($_);
next;
}
+ # We must look both in @INC for library modules and in $bindir
+ # for executables, like h2xs or perldoc itself.
+ my @searchdirs = ($bindir, @INC);
unless ($opt_m) {
if ($Is_VMS) {
my($i,$trn);
}
else {
# no match, try recursive search
- @searchdirs = grep(!/^\.$/,@INC);
+ @searchdirs = grep(!/^\.\z/s,@INC);
@files= searchfor(1,$_,@searchdirs) if $opt_r;
if (@files) {
print STDERR "Loosely found as @files\n" if $opt_v;
}
else {
- print STDERR "No documentation found for \"$_\".\n";
+ print STDERR "No " .
+ ($opt_m ? "module" : "documentation") . " found for \"$_\".\n";
if (@global_found) {
print STDERR "However, try\n";
for my $dir (@global_found) {
- opendir(DIR, $dir) or die "$!";
+ opendir(DIR, $dir) or die "opendir $dir: $!";
while (my $file = readdir(DIR)) {
- next if ($file =~ /^\./);
- $file =~ s/\.(pm|pod)$//;
+ next if ($file =~ /^\./s);
+ $file =~ s/\.(pm|pod)\z//; # XXX: badfs
print STDERR "\tperldoc $_\::$file\n";
}
- closedir DIR;
+ closedir DIR or die "closedir $dir: $!";
}
}
}
my $no_tty;
if (! -t STDOUT) { $no_tty = 1 }
+END { close(STDOUT) || die "Can't close STDOUT: $!" }
-# until here we could simply exit or die
-# now we create temporary files that we have to clean up
-# namely $tmp, $buffer
-
-my $tmp;
-my $buffer;
if ($Is_MSWin32) {
- $tmp = "$ENV{TEMP}\\perldoc1.$$";
- $buffer = "$ENV{TEMP}\\perldoc1.b$$";
push @pagers, qw( more< less notepad );
unshift @pagers, $ENV{PAGER} if $ENV{PAGER};
for (@found) { s,/,\\,g }
}
elsif ($Is_VMS) {
- $tmp = 'Sys$Scratch:perldoc.tmp1_'.$$;
- $buffer = 'Sys$Scratch:perldoc.tmp1_b'.$$;
push @pagers, qw( most more less type/page );
}
elsif ($Is_Dos) {
- $tmp = "$ENV{TEMP}/perldoc1.$$";
- $buffer = "$ENV{TEMP}/perldoc1.b$$";
- $tmp =~ tr!\\/!//!s;
- $buffer =~ tr!\\/!//!s;
push @pagers, qw( less.exe more.com< );
unshift @pagers, $ENV{PAGER} if $ENV{PAGER};
}
else {
if ($^O eq 'os2') {
- require POSIX;
- $tmp = POSIX::tmpnam();
- $buffer = POSIX::tmpnam();
unshift @pagers, 'less', 'cmd /c more <';
}
- else {
- $tmp = "/tmp/perldoc1.$$";
- $buffer = "/tmp/perldoc1.b$$";
- }
push @pagers, qw( more less pg view cat );
unshift @pagers, $ENV{PAGER} if $ENV{PAGER};
}
unshift @pagers, $ENV{PERLDOC_PAGER} if $ENV{PERLDOC_PAGER};
-# all exit calls from here on have to be safe_exit calls (see above)
-# and all die calls safe_die calls to guarantee removal of files and
-# dir as needed
-
if ($opt_m) {
foreach my $pager (@pagers) {
- system("$pager @found") or safe_exit(0, $tmp, $buffer);
+ if (system($pager, @found) == 0) {
+ exit;
+ }
+ }
+ if ($Is_VMS) {
+ eval q{
+ use vmsish qw(status exit);
+ exit $?;
+ 1;
+ } or die;
}
- if ($Is_VMS) { eval 'use vmsish qw(status exit); exit $?' }
- # I don't get the line above. Please patch yourself as needed.
- safe_exit(1, $tmp, $buffer);
+ exit(1);
}
my @pod;
if ($opt_f) {
my $perlfunc = shift @found;
- open(PFUNC, $perlfunc)
- or safe_die("Can't open $perlfunc: $!", $tmp, $buffer);
+ open(PFUNC, "<", $perlfunc)
+ or die("Can't open $perlfunc: $!");
# Functions like -r, -e, etc. are listed under `-X'.
my $search_string = ($opt_f =~ /^-[rwxoRWXOeszfdlpSbctugkTBMAC]$/)
? 'I<-X' : $opt_f ;
# Skip introduction
+ local $_;
while (<PFUNC>) {
last if /^=head2 Alphabetical Listing of Perl Functions/;
}
if (!@pod) {
die "No documentation for perl function `$opt_f' found\n";
}
+ close PFUNC or die "Can't open $perlfunc: $!";
}
if ($opt_q) {
local @ARGV = @found; # I'm lazy, sue me.
my $found = 0;
my %found_in;
+ my $rx = eval { qr/$opt_q/ } or die <<EOD;
+Invalid regular expression '$opt_q' given as -q pattern:
+ $@
+Did you mean \\Q$opt_q ?
+EOD
+
+ for (@found) { die "invalid file spec: $!" if /[<>|]/ }
+ local $_;
while (<>) {
if (/^=head2\s+.*(?:$opt_q)/oi) {
$found = 1;
push @pod, "=head1 Found in $ARGV\n\n" unless $found_in{$ARGV}++;
}
- elsif (/^=head2/) {
+ elsif (/^=head[12]/) {
$found = 0;
}
next unless $found;
push @pod, $_;
}
if (!@pod) {
- safe_die("No documentation for perl FAQ keyword `$opt_q' found\n",
- $tmp, $buffer);
+ die("No documentation for perl FAQ keyword `$opt_q' found\n");
}
}
+require File::Temp;
+
+my ($tmpfd, $tmp) = File::Temp::tempfile(UNLINK => 1);
+
my $filter;
if (@pod) {
- open(TMP,">$buffer") or safe_die("Can't open '$buffer': $!", $tmp, $buffer);
- print TMP "=over 8\n\n";
- print TMP @pod;
- print TMP "=back\n";
- close TMP;
+ my ($buffd, $buffer) = File::Temp::tempfile(UNLINK => 1);
+ print $buffd "=over 8\n\n";
+ print $buffd @pod or die "Can't print $buffer: $!";
+ print $buffd "=back\n";
+ close $buffd or die "Can't close $buffer: $!";
@found = $buffer;
$filter = 1;
}
foreach (@found) {
- printout($_, $tmp, $filter);
+ my $file = $_;
+ my $err;
+
+ if ($opt_t) {
+ Pod::Text->new()->parse_from_file($file, $tmpfd);
+ }
+ elsif (not $opt_u) {
+ my $cmd = catfile($bindir, $pod2man) . " --lax $file | $opt_n -man";
+ $cmd .= " | col -x" if $^O =~ /hpux/;
+ my $rslt = `$cmd`;
+ $rslt = filter_nroff($rslt) if $filter;
+ unless (($err = $?)) {
+ print $tmpfd $rslt
+ or die "Can't print $tmp: $!";
+ }
+ }
+ if ($opt_u or $err) {
+ open(IN,"<", $file) or die("Can't open $file: $!");
+ my $cut = 1;
+ local $_;
+ while (<IN>) {
+ $cut = $1 eq 'cut' if /^=(\w+)/;
+ next if $cut;
+ print $tmpfd $_
+ or die "Can't print $tmp: $!";
+ }
+ close IN or die "Can't close $file: $!";
+ }
}
+close $tmpfd
+ or die "Can't close $tmp: $!";
page($tmp, $no_tty, @pagers);
-safe_exit(0, $tmp, $buffer);
+exit;
+
+sub is_tainted {
+ my $arg = shift;
+ my $nada = substr($arg, 0, 0); # zero-length
+ local $@; # preserve caller's version
+ eval { eval "# $nada" };
+ return length($@) != 0;
+}
+
+sub am_taint_checking {
+ my($k,$v) = each %ENV;
+ return is_tainted($v);
+}
+
__END__
=item B<-X> use an index if present
-The B<-X> option looks for a entry whose basename matches the name given on the
+The B<-X> option looks for an entry whose basename matches the name given on the
command line in the file C<$Config{archlib}/pod.idx>. The pod.idx file should
contain fully qualified filenames, one per line.
+=item B<-U> run insecurely
+
+Because B<perldoc> does not run properly tainted, and is known to
+have security issues, it will not normally execute as the superuser.
+If you use the B<-U> flag, it will do so, but only after setting
+the effective and real IDs to nobody's or nouser's account, or -2
+if unavailable. If it cannot relinquish its privileges, it will not
+run.
+
=item B<PageName|ModuleName|ProgramName>
The item you want to look up. Nested modules (such as C<File::Basename>)
=head1 VERSION
-This is perldoc v2.0.
+This is perldoc v2.03.
=head1 AUTHOR
=cut
#
+# Version 2.03: Sun Apr 23 16:56:34 BST 2000
+# Hugo van der Sanden <hv@crypt0.demon.co.uk>
+# don't die when 'use blib' fails
+# Version 2.02: Mon Mar 13 18:03:04 MST 2000
+# Tom Christiansen <tchrist@perl.com>
+# Added -U insecurity option
+# Version 2.01: Sat Mar 11 15:22:33 MST 2000
+# Tom Christiansen <tchrist@perl.com>, querulously.
+# Security and correctness patches.
+# What a twisted bit of distasteful spaghetti code.
+# Version 2.0: ????
# Version 1.15: Tue Aug 24 01:50:20 EST 1999
# Charles Wilson <cwilson@ece.gatech.edu>
# changed /pod/ directory to /pods/ for cygwin