1#
2#  ----------------------------------------------------
3#  httpry - HTTP logging and information retrieval tool
4#  ----------------------------------------------------
5#
6#  Copyright (c) 2005-2014 Jason Bittel <jason.bittel@gmail.com>
7#
8
9package common_log;
10
11use POSIX qw(strftime mktime);
12
13# -----------------------------------------------------------------------------
14# GLOBAL VARIABLES
15# -----------------------------------------------------------------------------
16my %requests = ();
17my $fh;
18
19my @months = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
20
21# -----------------------------------------------------------------------------
22# Plugin core
23# -----------------------------------------------------------------------------
24
25main::register_plugin();
26
27sub new {
28        return bless {};
29}
30
31sub init {
32        my $self = shift;
33        my $cfg_dir = shift;
34
35        _load_config($cfg_dir);
36
37        open OUTFILE, ">$output_file" or die "Cannot open $output_file: $!\n";
38        $fh = *OUTFILE;
39
40        return;
41}
42
43sub list {
44        return qw(direction source-ip dest-ip);
45}
46
47sub main {
48        my $self = shift;
49        my $record = shift;
50        my $line = "";
51        my $line_suffix;
52        my ($sec, $min, $hour, $mday, $mon, $year);
53        my $tz_offset;
54
55        if ($record->{'direction'} eq '>') {
56                return unless exists $record->{'timestamp'};
57                return unless exists $record->{'method'};
58                return unless exists $record->{'request-uri'};
59                return unless exists $record->{'http-version'};
60
61                # Build the output line: begin with client (remote host) address
62                $line .= $record->{'source-ip'};
63
64                # Append ident and authuser fields
65                # NOTE: we use the ident field to display the
66                # hostname/ip of the destination site
67                if (exists $record->{'host'}) {
68                        $line .= " $record->{'host'} - ";
69                } else {
70                        $line .= " $record->{'dest-ip'} - ";
71                }
72
73                # Append date field
74                $record->{'timestamp'} =~ /(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/;
75                ($sec, $min, $hour, $mday, $mon, $year) = ($6, $5, $4, $3, $2-1, $1-1900);
76                # NOTE: We assume the current timezone here; that may not always be accurate, but
77                # timezone data is not stored in the httpry log files
78                $tz_offset = strftime("%z", localtime(mktime($sec, $min, $hour, $mday, $mon, $year)));
79                $line .= sprintf("[%02d/%3s/%04d:%02d:%02d:%02d %5s]", $mday, $months[$mon], $year+1900, $hour, $min, $sec, $tz_offset);
80
81                # Append request fields
82                $line .= " \"$record->{'method'} $record->{'request-uri'} $record->{'http-version'}\"";
83
84                if ($combined_format) {
85                        # Append referer
86                        if (exists $record->{'referer'}) {
87                                $line .= "\t \"$record->{'referer'}\"";
88                        } else {
89                                $line .= "\t \"-\"";
90                        }
91
92                        # Append user agent string
93                        if (exists $record->{'user-agent'}) {
94                                $line .= " \"$record->{'user-agent'}\"";
95                        } else {
96                                $line .= " \"-\"";
97                        }
98                }
99
100                if ($ignore_response) {
101                        print $fh "$line - -\n";
102                } else {
103                        push(@{ $requests{"$record->{'source-ip'}$record->{'dest-ip'}"} }, $line);
104                }
105        } elsif ($record->{'direction'} eq '<') {
106                # NOTE: This is a bit naive, but functional. Basically we match a request with the
107                # next response from that IP pair in the log file. This means that under busy
108                # conditions the response could be matched to the wrong request but currently there
109                # isn't a more accurate way to tie them together.
110                if (exists $requests{"$record->{'dest-ip'}$record->{'source-ip'}"}) {
111                        $line = shift(@{ $requests{"$record->{'dest-ip'}$record->{'source-ip'}"} });
112                        return unless $line;
113
114                        if (! @{ $requests{"$record->{'dest-ip'}$record->{'source-ip'}"} }) {
115                                delete $requests{"$record->{'dest-ip'}$record->{'source-ip'}"};
116                        }
117                } else {
118                        return;
119                }
120
121                ($line, $line_suffix) = split /\t/, $line, 2 if $combined_format;
122
123                # Append status code
124                if (exists $record->{'status-code'}) {
125                        $line .= " $record->{'status-code'}";
126                } else {
127                        $line .= " -";
128                }
129
130                # Append byte count
131                if (exists $record->{'content-length'}) {
132                        $line .= " $record->{'content-length'}";
133                } else {
134                        $line .= " -";
135                }
136
137                $line .= $line_suffix if $combined_format;
138
139                print $fh "$line\n";
140        }
141
142        return;
143}
144
145sub end {
146        # TODO: Print lines that don't have a matching response?
147
148        close $fh or die "Cannot close $fh: $!\n";
149
150        return;
151}
152
153# -----------------------------------------------------------------------------
154# Load config file and check for required options
155# -----------------------------------------------------------------------------
156sub _load_config {
157        my $cfg_dir = shift;
158
159        # Load config file; by default in same directory as plugin
160        if (-e "$cfg_dir/" . __PACKAGE__ . ".cfg") {
161                require "$cfg_dir/" . __PACKAGE__ . ".cfg";
162        } else {
163                die "No config file found\n";
164        }
165
166        # Check for required options and combinations
167        if (!$output_file) {
168                die "No output file provided\n";
169        }
170
171        $output_dir = "." if (!$output_dir);
172        $output_dir =~ s/\/$//; # Remove trailing slash
173
174        return;
175}
176
1771;
178