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