19 # Heiko Schlittermann <hs@schlittermann.de> |
19 # Heiko Schlittermann <hs@schlittermann.de> |
20 |
20 |
21 use strict; |
21 use strict; |
22 use warnings; |
22 use warnings; |
23 use Getopt::Long; |
23 use Getopt::Long; |
24 use IO::Socket::INET; |
|
25 use Pod::Usage; |
24 use Pod::Usage; |
26 use File::Basename; |
25 use File::Basename; |
27 use POSIX qw(:sys_wait_h setsid); |
26 use POSIX qw(:sys_wait_h setsid); |
28 use Cwd; |
27 use Cwd; |
29 |
28 |
30 my $opt_addr = "0.0.0.0"; |
29 my $opt_addr = "0.0.0.0"; |
31 my $opt_port = 8080; |
30 my $opt_port = 8080; |
32 my $opt_lines = 10; |
31 my $opt_lines = 10; |
33 my $opt_daemon = 1; |
32 my $opt_daemon = 1; |
34 my $opt_kill = 0; |
33 my $opt_kill = 0; |
|
34 my $opt_debug = 0; |
|
35 my $opt_ssl = 1; |
|
36 my $opt_ssl_cert = "crt.pem"; |
|
37 my $opt_ssl_key = "key.pem"; |
35 |
38 |
36 my $ME = basename $0; |
39 my $ME = basename $0; |
37 |
40 |
38 # these vars will be filled with the real dirs later |
41 # these vars will be filled with the real dirs later |
39 my $rundir = [ "/var/run/$ME", "$ENV{HOME}/.$ME" ]; |
42 my $rundir = [ "/var/run/$ME", "$ENV{HOME}/.$ME" ]; |
40 my $logdir = [ "/var/log/$ME", "$ENV{HOME}/.$ME" ]; |
43 my $logdir = [ "/var/log/$ME", "$ENV{HOME}/.$ME" ]; |
41 |
44 |
42 my $maxlogsize = 1000_000_000; # ca 1 MByte |
45 my $maxlogsize = 1_000_000; # ca 1 MByte |
43 my $killtimeout = 3; |
46 my $killtimeout = 3; |
44 |
47 |
45 # these are refs to detect if they're converted already |
48 # these are refs to detect if they're converted already |
46 my $access = \"%s/access.log"; |
49 my $access = \"%s/access.log"; |
47 my $errors = \"%s/error.log"; |
50 my $errors = \"%s/error.log"; |
48 my $pidfile = \"%s/%s.%s.pid"; # %dir/%ip.%port |
51 my $pidfile = \"%s/%s.%s.pid"; # %dir/%ip.%port |
49 |
52 |
|
53 # remember the pid that is actually written to the pid file so we can ensure |
|
54 # that only the process with that pid is attempting to remove the pidfile at |
|
55 # exit |
|
56 my $masterpid; |
|
57 |
|
58 |
|
59 # attempt to generalize some interface differences between |
|
60 # IO::Socket::{INET,SSL}; currently we need to pass an SSL specific argument |
|
61 # when closing an SSL Socket to avoid affecting the socket in |
|
62 # parent(s)/children; passing unknown arguments to the 'close' method of non |
|
63 # SSL Sockets would result in an runtime error; error reporting is also done |
|
64 # differently; note that you cant mix non/SSL Sockets since currently we |
|
65 # achieve it by modifying @ISA which is a class variable in the constructor; |
|
66 # the right thing to do would probably be to use IO::Socket::SSL for non SSL |
|
67 # Sockets too -> TODO: how? ;) |
|
68 package IO::Socket::hlog; |
|
69 |
|
70 use IO::Socket::INET; |
|
71 use IO::Socket::SSL; |
|
72 |
|
73 our (@ISA, $DEBUG); |
|
74 |
|
75 sub new { |
|
76 |
|
77 my $class = shift; |
|
78 |
|
79 my %args = @_; |
|
80 my $ssl = delete $args{SSL}; |
|
81 |
|
82 if ($ssl) { |
|
83 |
|
84 @ISA = qw(IO::Socket::SSL); |
|
85 $IO::Socket::SSL::DEBUG = $DEBUG ? 3 : 0; |
|
86 |
|
87 } else { |
|
88 |
|
89 %args = _delete_ssl_args(%args); |
|
90 @ISA = qw(IO::Socket::INET); |
|
91 |
|
92 } |
|
93 |
|
94 my $self; |
|
95 unless ($self = $class->SUPER::new(%args)) { |
|
96 |
|
97 return; |
|
98 |
|
99 } |
|
100 |
|
101 print "$class: $self created\n" if $DEBUG; |
|
102 |
|
103 return $self; |
|
104 |
|
105 } |
|
106 |
|
107 sub close { |
|
108 |
|
109 my $self = shift; |
|
110 print "$self: closing\n" if $DEBUG; |
|
111 |
|
112 my %args = @_; |
|
113 |
|
114 %args = _delete_ssl_args(%args) unless $self->_is_ssl; |
|
115 |
|
116 return $self->SUPER::close(%args); |
|
117 |
|
118 } |
|
119 |
|
120 sub errstr { |
|
121 |
|
122 return IO::Socket::SSL::errstr if _is_ssl(); |
|
123 |
|
124 return $@; |
|
125 |
|
126 } |
|
127 |
|
128 sub _delete_ssl_args { |
|
129 |
|
130 my %args = @_; |
|
131 map { delete $args{$_} if /^SSL/; } keys %args; |
|
132 return %args; |
|
133 |
|
134 } |
|
135 |
|
136 sub _is_ssl { |
|
137 |
|
138 my $self = shift; |
|
139 return $ISA[0] eq "IO::Socket::SSL"; |
|
140 |
|
141 } |
|
142 |
|
143 package main; |
|
144 |
50 END { |
145 END { |
51 unlink $pidfile |
146 unlink $pidfile |
52 if defined $pidfile and not ref $pidfile; |
147 if defined $pidfile and not ref $pidfile and defined $masterpid and $masterpid == $$; |
53 } |
148 } |
54 |
149 |
55 sub find_writable_dir(@); |
150 sub find_writable_dir(@); |
56 |
151 |
57 sub log_open($); |
152 sub log_open($); |
66 my %FILE; |
161 my %FILE; |
67 |
162 |
68 MAIN: { |
163 MAIN: { |
69 |
164 |
70 GetOptions( |
165 GetOptions( |
71 "addr=s" => \$opt_addr, |
166 "addr=s" => \$opt_addr, |
72 "port=i" => \$opt_port, |
167 "port=i" => \$opt_port, |
73 "lines=i" => \$opt_lines, |
168 "lines=i" => \$opt_lines, |
74 "daemon!" => \$opt_daemon, |
169 "daemon!" => \$opt_daemon, |
75 "kill" => \$opt_kill, |
170 "debug!" => \$opt_debug, |
76 "help" => sub { pod2usage(-verbose => 1, -exitval => 0) }, |
171 "kill" => \$opt_kill, |
77 "man" => sub { pod2usage(-verbose => 2, -exitval => 0) }, |
172 "help" => sub { pod2usage(-verbose => 1, -exitval => 0) }, |
|
173 "man" => sub { pod2usage(-verbose => 2, -exitval => 0) }, |
|
174 "ssl!" => \$opt_ssl, |
|
175 "ssl-cert=s" => \$opt_ssl_cert, |
|
176 "ssl-key=s" => \$opt_ssl_key |
78 ) or pod2usage(); |
177 ) or pod2usage(); |
|
178 |
|
179 $IO::Socket::hlog::DEBUG = $opt_debug; |
79 |
180 |
80 if (defined($logdir = find_writable_dir(@$logdir))) { |
181 if (defined($logdir = find_writable_dir(@$logdir))) { |
81 $access = sprintf $$access, $logdir; |
182 $access = sprintf $$access, $logdir; |
82 $errors = sprintf $$errors, $logdir; |
183 $errors = sprintf $$errors, $logdir; |
83 log_open($access); |
184 log_open($access); |
125 $file = getcwd() . "/$file" if $file !~ /^\//; |
226 $file = getcwd() . "/$file" if $file !~ /^\//; |
126 $FILE{$tag} = $file; |
227 $FILE{$tag} = $file; |
127 } |
228 } |
128 |
229 |
129 # start the listener |
230 # start the listener |
130 my $listener = new IO::Socket::INET( |
231 my $listener = new IO::Socket::hlog( |
131 LocalAddr => $opt_addr, |
232 LocalAddr => $opt_addr, |
132 LocalPort => $opt_port, |
233 LocalPort => $opt_port, |
133 Proto => "tcp", |
234 Proto => "tcp", |
134 Listen => 1, |
235 Listen => 1, |
135 ReuseAddr => 1, |
236 ReuseAddr => 1, |
136 ) or die "Can't create listener socket: $!\n"; |
237 SSL => $opt_ssl, |
|
238 SSL_key_file => $opt_ssl_key, |
|
239 SSL_cert_file => $opt_ssl_cert, |
|
240 debug => $opt_debug |
|
241 ) or die "Can't create listener socket: ", IO::Socket::hlog::errstr, "\n"; |
|
242 |
137 |
243 |
138 # go daemon |
244 # go daemon |
139 chdir("/") or die "Can't chdir to /: $!\n"; |
245 chdir("/") or die "Can't chdir to /: $!\n"; |
140 |
246 |
141 if ($opt_daemon) { |
247 if ($opt_daemon) { |
188 |
294 |
189 my $pid = fork(); |
295 my $pid = fork(); |
190 die "Can't fork: $!\n" if not defined $pid; |
296 die "Can't fork: $!\n" if not defined $pid; |
191 if ($pid == 0) { |
297 if ($pid == 0) { |
192 $SIG{CHLD} = "DEFAULT"; |
298 $SIG{CHLD} = "DEFAULT"; |
193 $listener->close; |
299 print("listener $listener\n") if $opt_debug; |
|
300 $listener->close(SSL_no_shutdown => 1); |
194 handle_request($client); |
301 handle_request($client); |
195 exit 0; |
302 exit 0; |
196 } |
303 } |
197 $client->close; |
304 print("client $client\n") if $opt_debug; |
|
305 $client->close(SSL_no_shutdown => 1); |
198 |
306 |
199 # maintenance of logfiles |
307 # maintenance of logfiles |
200 if (-s $access > $maxlogsize) { |
308 if (-s $access > $maxlogsize) { |
201 rename $access, "$access.1"; |
309 rename $access, "$access.1"; |
202 log_open($access); |
310 log_open($access); |
234 |
342 |
235 sub handle_request($) { |
343 sub handle_request($) { |
236 my $client = shift; |
344 my $client = shift; |
237 local $_ = <$client>; |
345 local $_ = <$client>; |
238 |
346 |
239 # should be HTTP/x.x |
347 # should be HTTP(S)/x.x |
240 if (not s/\s+HTTP\/\S+\s*$//) { |
348 if (not s/\s+HTTPS?\/\S+\s*$//) { |
|
349 log_write("Bad Request: $_") if $opt_debug; |
241 $client->print(bad_request); |
350 $client->print(bad_request); |
242 return; |
351 return; |
243 } |
352 } |
244 |
353 |
245 # should be a GET request |
354 # should be a GET request |
246 if (not s/GET\s+//) { |
355 if (not s/GET\s+//) { |
|
356 log_write("Bad Request: $_") if $opt_debug; |
247 $client->print(http "400 Bad Request" => bad_request); |
357 $client->print(http "400 Bad Request" => bad_request); |
248 } |
358 } |
249 |
359 |
250 # number of lines and tag to show |
360 # number of lines and tag to show |
251 my $lines = (s/(\d+)$// ? $1 : $opt_lines); |
361 my $lines = (s/(\d+)$// ? $1 : $opt_lines); |
359 |
469 |
360 hlog - simple http server providing access to some logfile |
470 hlog - simple http server providing access to some logfile |
361 |
471 |
362 =head1 SYNOPSIS |
472 =head1 SYNOPSIS |
363 |
473 |
364 hlog [--[no]daemon] |
474 hlog [--[no]daemon] |
|
475 [--[no]debug] |
365 [-k|--kill] |
476 [-k|--kill] |
366 [-a|--address address] [-p|--port port] |
477 [-a|--address address] [-p|--port port] |
367 [--lines n] |
478 [--lines n] |
|
479 [--[no]ssl] |
|
480 [--ssl-cert path] |
|
481 [--ssl-key path] |
368 {file|tag=file ...} |
482 {file|tag=file ...} |
369 |
483 |
370 hlog [-h|--help] [-m|--man] |
484 hlog [-h|--help] [-m|--man] |
371 |
485 |
372 =head1 DESCRIPTION |
486 =head1 DESCRIPTION |
373 |
487 |
374 This script should run as a server providing access to |
488 This script should run as a server providing access to |
375 the last lines of a logfile. It should understand basic HTTP/1.x. |
489 the last lines of a logfile. It should understand basic HTTP(S)/1.x. |
376 |
490 |
377 See the L<FILES> section for more information on files. |
491 See the L<FILES> section for more information on files. |
378 |
492 |
379 =head1 OPTIONS |
493 =head1 OPTIONS |
380 |
494 |