views:

913

answers:

13

Recently there have been a few attackers trying malicious things on my server so I've decided to somewhat "track" them even though I know they won't get very far.

Now, I have an entire directory containing the server logs and I need a way to search through every file in the directory, and return a filename if a string is found. So I thought to myself, what better of a language to use for text & file operations than Perl? So my friend is helping me with a script to scan all files for a certain IP, and return the filenames that contain the IP so I don't have to search for the attacker through every log manually. (I have hundreds)

#!/usr/bin/perl

$dir = ".";

opendir(DIR, "$dir");
@files = grep(/\.*$/,readdir(DIR));
closedir(DIR);

foreach $file(@files) {
    open FILE, "$file" or die "Unable to open files";

    while(<FILE>) {
        print if /12.211.23.200/;
    }

}

although it is giving me directory read errors. Any assistance is greatly appreciated.

EDIT: Code edited, still saying permission denied cannot open directory on line 10. I am just going to run the script from within the logs directory if you are questioning the directory change to "."

Mike.

+2  A: 

This will get the file names you are looking for in perl, and probably do it much faster than running and doing a perl regex.

@files = `find ~/ServerLogs -name "*.log" | xargs grep -l "<ip address>"`'

Although, this will require a *nix compliant system, or Cygwin on Windows.

ng
Without perl the following will get the file names in a log.find ~/ServerLogs -name "*.log" | xargs grep -l 127.0.0.1 > match.log
ng
How about just `grep -l 127.0.0.1 *.log` instead of the find | xargs? Plus: it will work if there are spaces in filenames. Minus: it won't work in huge directories.
A. Rex
It will work in very large directories, all find will do is collect the matching files hand it to xargs and pipe it as input to grep. Using pipes will also allow you to sed /<match>/d any specific files or subdirectories you don't want to give to grep.
ng
I'm saying xargs won't work if the filenames contain spaces. My solution might not work in a directory with many files, because *.log will exceed the command-line argument limit. Neither issue comes up often, though spaces in filenames aren't exactly _uncommon_.
A. Rex
+1  A: 

Firstly get a list of files within your source directory:

opendir(DIR, "$dir");
@files = grep(/\.log$/,readdir(DIR));
closedir(DIR);

And then loop through those files

foreach $file(@files)
{
  // file processing code
}
benPearce
+13  A: 

Can you use grep instead?

A. Rex
for what is being done, writing a script does look like overkill
Kent Fredric
The Unix "tool chain" is very powerful. =)
A. Rex
+1: grep is even available on Windows using the unxutils package on sourceforge.
romandas
+1: the best code is code you don't have to write.
jrockway
+3  A: 

You have to concatenate $dirname with $filname when using files found through readdir, remember you haven't chdir'ed into the directory where those files resides.

open FH, "<", "$dirname/$filname" or die "Cannot open $filname:$!";

Incidentally, why not just use grep -r to recursively search all subdirectories under your log dir for your string?

EDIT: I see your edits, and two things. First, this line:

@files = grep(/\.*$/,readdir(DIR));

Is not effective, because you are searching for zero or more . characters at the end of the string. Since it's zero or more, it'll match everything in the directory. If you're trying to exclude files ending in ., try this:

@files = grep(!/\.$/,readdir(DIR));

Note the ! sign for negation if you're trying to exclude those files. Otherwise (if you only want those files and I'm misunderstanding your intent), leave the ! out.

In any case, if you're getting your die message on line 10, most likely you're hitting a file that has permissions such that you can't read it. Try putting the filename in the die output so you can see which file it's failing on:

open FILE, "$file" or die "Unable to open file: $file";

But as with other answers, and to reiterate: Why not use grep? The unix command, not the Perl function.

Adam Bellaire
also, ~ doesn't autoexpand, he needs glob.
Kent Fredric
+1  A: 

My first suggest would be to use grep instead. The right tool for the job, they say...

But to answer your question:

readdir just returns the filenames from the directory. You'll need to concatenate the directory name and filename together.

$path = "$dirname/$filname";
open FH, $path or die ...

Then you should ignore files that are actually directories, such as "." and "..". After getting the $path, check to see if it's a file.

if (-f $path) {
    open FH, $path or die ...
    while (<FH>)
Barry Brown
A: 

~ doesn't auto-expand in Perl.

opendir my $fh,  '~/' or die("Doin It Wrong");  # Doing It Wrong. 

opendir my $fh, glob('~/') and die( "Thats right!" );
Kent Fredric
A: 

Also, if you must use readdir(), make sure you guard the expression thus:

while (defined(my $filename = readdir(DH))) {
    ...
}

If you don't do the defined() test, the loop will terminate if it finds a file called '0'.

Alnitak
A: 

Have you looked on CPAN for log parsers? I searched with 'log parse' and it yielded over 200 hits. Some (probably many) won't be relevant - some may be. It depends, in part, on which web server you are using.

Jonathan Leffler
A: 

Am I reading this right? Your line 10 that gives you the error is

open FILE, "$file" or die "Unable to open files";

And the $file you are trying to read, according to line 6,

@files = grep(/\.*$/,readdir(DIR));

is a file that ends with zero or more dot. Is this what you really wanted? This basically matches every file in the directory, including "." and "..". Maybe you don't have enough permission to open the parent directory for reading?

EDIT: if you only want to read all files (including hidden ones), you might want to use something like the following:

opendir(DIR, ".");
@files = readdir(DIR);
closedir(DIR);

foreach $file (@files) {
  if ($file ne "." and $file ne "..") {
    open FILE, "$file" or die "cannot open $file\n";
    # do stuff with FILE
  }
}

Note that this doesn't take care of sub directories.

PolyThinker
no i just want to open the files in it, how should i change that?
John T
A: 

First, use grep.

But if you don't want to, here are two small improvements you can make that I haven't seen mentioned yet:

1) Change:

@files = grep(/\.*$/,readdir(DIR));

to

@files = grep({ !-d "$dir/$_" } readdir(DIR));

This way you will exclude not just "." and ".." but also any other subdirectories that may exist in the server log directory (which the open downstream would otherwise choke on).

2) Change:

print if /12.211.23.200/;

to

print if /12\.211\.23\.200/;

"." is a regex wildcard meaning "any character". Changing it to "\." will reduce the number of false positives (unlikely to change your results in practice but it's more correct anyway).

j_random_hacker
+4  A: 

To get all the lines with the IP, I would directly use grep, no need to show a list of files, it's a simple command:

grep 12\.211\.23\.200 *

I like to pipe it to another file and then open that file in an editor...

If you insist on wanting the filenames, it's also easy

grep -l 12\.211\.23\.200 *

grep is available on all Unix//Linux with the GNU tools, or on windows using one of the many implementations (unxutils, cygwin, ...etc.)

Osama ALASSIRY
ack-grep also goes through subdirectories and shows really nice output
Kalmi
+1  A: 

BTW, I thought I would throw in a mention for File::Next. To iterate over all files in a directory (recursively):

use Path::Class; # always useful.
use File::Next;

my $files = File::Next::files( dir(qw/path to files/) ); # look in path/to/files
while( defined ( my $file = $files->() ) ){
    $file = file( $file );
    say "Examining $file";
    say "found foo" if $file->slurp =~ /foo/;
}

File::Next is taint-safe.

jrockway
A: 

I know I am way late to this discussion (ran across it while searching for grep related posts) but I am going to answer anyway:

It isn't specified clearly if these are web server logs (Apache, IIS, W3SVC, etc.) but the best tool for mining those for data is the LogParser tool from Microsoft. See logparser.com for more info.

LogParser will allow you to write SQL-like statements against the log files. It is very flexible and very fast.

mattsmith321