muleherd has asked for the wisdom of the Perl Monks concerning the following question:

I need to write a simple cgi script that takes a string from the user and searches for it using grep. If I grep only one file, it works fine. But if I make the script grep -r, the cgi script does not display the results. However, running the script w/ grep -r from the command line, I get the results.
if (open (GREP, "-|")) { print <GREP>; } else { exec ("grep", "-r", $string, ".") || die "Error exec'ing command", + "\n"; } close (GREP);
Thanks

Replies are listed 'Best First'.
Re: CGI Script Calling Grep
by mandog (Curate) on Sep 26, 2001 at 00:20 UTC
    I copied and pasted your code into a file on my debian box & it seemed to work ok. Perhaps the problem is someplace else... Perhaps the environment your CGI is executing in is different (PATH???) than the environment that your script is executing in when run from command line.

    As an aside, If you are passing input from the user to the shell, you need to enable taint mode (see perldoc perlsec for details)

    As it is a hostlle user might enter an arbitrary command that your script will run. Perhaps something like: .* -l | rm -fr . (This might not work w/ exec but...)

    I've copied what appear to the relevant bits from from perlsec below, However since I've never actually written a CGI that passes user input to the system, you might want to look at the docs yourself or await guidance from a wiser monk.

    update: fixed silly grammer error

    #!/usr/local/bin/perl -wT use strict; my $string="test"; # actually comes from CGI $string=~s/[^\w]//; # remove non word chars #clean up environment delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; $ENV{'PATH'} = '/bin:/usr/bin'; if (open (GREP, "-|")) { print <GREP>; } else { exec ("grep", "-r", $string, ".") || die "Error exec'ing command", "\n"; } close (GREP);


    --mandog
Re: CGI Script Calling Grep
by scain (Curate) on Sep 26, 2001 at 00:01 UTC
    I am not sure of exactly what you are trying to do with your code, but it seems to me it should be much easier:
    my @grep_result = `grep -r $string .`; foreach my $result (@grep_result) { print; }
    HTH
    Scott

    update: Ack! yes of course; I don't write CGIs much, but I should still know better. Use -T and untaint $string, by all means.

      Well, yes, in one way (this should solve the technical problem), and in another, emphatic no. Never pass user input directly to a subshell -- all sorts of nasties could result. Suppose $string is "foo *; cat /etc/passwd; rm -rf " for example. DoS, cracker info, and evil file removal (potential) all in one go.

      So you could use this sort of thing, but *not* without taint checking, and, if you're going to untaint, be sure you know what you're doing. For more, see perldoc perlsec on your system, or perlsec hereabouts.

      perl -e 'print "How sweet does a rose smell? "; chomp ($n = <STDIN>); +$rose = "smells sweet to degree $n"; *other_name = *rose; print "$oth +er_name\n"'
      Not recommended for certain values of $string... (scain probably knew that, but some people might not.)

      Your exec didn't do what you want because... well, that's not what exec does. It doesn't capture output. Read the docs on exec, system, and backticks (``) for a better understanding. It would be safer and faster to do this using File::Find and Perl's built-in pattern matching.

        Your exec didn't do what you want because... well, that's not what exec does. It doesn't capture output. Read the docs on exec, system, and backticks (``) for a better understanding.

        Actually, he's running grep in a reasonable way. The open() call sets up a pipe, forks, and hooks the pipe to the child process's stdout; he then execs grep in the subshell. The way he's exec'ing grep, he also avoids the question of shell metacharacters in the $string variable.

        Scain: The problem may be the script's current directory. The script is running grep (recursively) on ".". When you run the script by hand you could be starting it in the directory you want grep to search. The web server probably starts the script with a different directory as its current directory, so your script would have to chdir() to the right spot, or else give the full /path/to/the/directory instead of using ".".

Re: CGI Script Calling Grep
by derby (Abbot) on Sep 26, 2001 at 16:00 UTC
    muleherd,

    What does it display? Do you get an internal server error? (probably not) Do you get a "page cannot be displayed?" My only concern with this code is that you could timeout before the recursive grep starts to output any data. If you have access to your servers conf file, check out the "Timeout" value. Combine that with mandog's (Re: CGI Script Calling Grep) suggestion that '.' may not be where you think it is (and with all the other security tips) and you should have a solution.

    -derby

      derby,
      The page loads, but it doesn't have the output of grep. Everything before and after where the grep command should be IS there. '.' does in fact seem to be referring to the correct directory. I was able to successfully grep another script in the same directory by altering my exec command. Is it still necessary to untaint the user string when passing the parameters to exec as a list, avoiding the shell?
        Try this theory on for size:

        You're running this on an OS like Solaris, where the standard version of grep doesn't provide a -r option. Your system administrator has installed GNU grep (which does have this option) in /usr/local/bin or some such place.

        When you run the script yourself, your PATH contains /usr/local/bin so the script uses GNU grep. When the web server runs your script, it gets a different path and ends up running /usr/bin/grep. This version just complains and exits, but you don't see the error message.