hlog.pl
branchfoerste
changeset 42 97c0f39be179
parent 40 99e8455f50dc
child 44 487165bdcf58
equal deleted inserted replaced
41:9465d503d498 42:97c0f39be179
    40 my $opt_ssl      = 1;
    40 my $opt_ssl      = 1;
    41 my $opt_ssl_cert = "crt.pem";
    41 my $opt_ssl_cert = "crt.pem";
    42 my $opt_ssl_key  = "key.pem";
    42 my $opt_ssl_key  = "key.pem";
    43 
    43 
    44 # these vars will be filled with the real dirs later
    44 # these vars will be filled with the real dirs later
    45 my $rundir = [ "/var/run/$ME", "$ENV{HOME}/.$ME" ];
    45 my $rundir = ["/var/run/$ME", "$ENV{HOME}/.$ME"];
    46 my $logdir = [ "/var/log/$ME", "$ENV{HOME}/.$ME" ];
    46 my $logdir = ["/var/log/$ME", "$ENV{HOME}/.$ME"];
    47 
    47 
    48 my $maxlogsize  = 1_000_000;    # ca 1 MByte
    48 my $maxlogsize  = 1_000_000;    # ca 1 MByte
    49 my $killtimeout = 3;
    49 my $killtimeout = 3;
    50 
    50 
    51 # these are refs to detect if they're converted already
    51 # these are refs to detect if they're converted already
    52 my $access  = \"%s/access.log";
    52 my $access  = \"%s/access.log";
    53 my $errors  = \"%s/error.log";
    53 my $errors  = \"%s/error.log";
    54 my $pidfile = \"%s/%s.%s.pid";     # %dir/%ip.%port
    54 my $pidfile = \"%s/%s.%s.pid";    # %dir/%ip.%port
    55 
    55 
    56 # remember the pid that is actually written to the pid file so we can ensure
    56 # remember the pid that is actually written to the pid file so we can ensure
    57 # that only the process with that pid is attempting to remove the pidfile at
    57 # that only the process with that pid is attempting to remove the pidfile at
    58 # exit
    58 # exit
    59 my $masterpid;
    59 my $masterpid;
    79 
    79 
    80 our (@ISA, $DEBUG);
    80 our (@ISA, $DEBUG);
    81 
    81 
    82 sub new {
    82 sub new {
    83 
    83 
    84   my $class = shift;
    84     my $class = shift;
    85   
    85 
    86   my %args = @_;
    86     my %args = @_;
    87   my $ssl = delete $args{SSL};
    87     my $ssl  = delete $args{SSL};
    88 
    88 
    89   if ($ssl) {
    89     if ($ssl) {
    90 
    90 
    91     @ISA = qw(IO::Socket::SSL);
    91         @ISA = qw(IO::Socket::SSL);
    92     $IO::Socket::SSL::DEBUG = $DEBUG ? 3 : 0;
    92         $IO::Socket::SSL::DEBUG = $DEBUG ? 3 : 0;
    93 
    93 
    94   } else {
    94     }
    95 
    95     else {
    96     %args = _delete_ssl_args(%args);
    96 
    97     @ISA = qw(IO::Socket::INET);
    97         %args = _delete_ssl_args(%args);
    98 
    98         @ISA  = qw(IO::Socket::INET);
    99   }
    99 
   100 
   100     }
   101   my $self;
   101 
   102   unless ($self = $class->SUPER::new(%args)) {
   102     my $self;
   103 
   103     unless ($self = $class->SUPER::new(%args)) {
   104     return;
   104 
   105 
   105         return;
   106   }
   106 
   107 
   107     }
   108   print "$class: $self created\n" if $DEBUG;
   108 
   109 
   109     print "$class: $self created\n" if $DEBUG;
   110   return $self;
   110 
       
   111     return $self;
   111 
   112 
   112 }
   113 }
   113 
   114 
   114 sub close {
   115 sub close {
   115 
   116 
   116   my $self = shift;
   117     my $self = shift;
   117   print "$self: closing\n" if $DEBUG;
   118     print "$self: closing\n" if $DEBUG;
   118 
   119 
   119   my %args = @_;
   120     my %args = @_;
   120 
   121 
   121   %args = _delete_ssl_args(%args) unless $self->_is_ssl;
   122     %args = _delete_ssl_args(%args) unless $self->_is_ssl;
   122 
   123 
   123   return $self->SUPER::close(%args);
   124     return $self->SUPER::close(%args);
   124 
   125 
   125 }
   126 }
   126 
   127 
   127 sub errstr {
   128 sub errstr {
   128 
   129 
   129   return IO::Socket::SSL::errstr if _is_ssl();
   130     return IO::Socket::SSL::errstr if _is_ssl();
   130 
   131 
   131   return $@;
   132     return $@;
   132 
   133 
   133 }
   134 }
   134 
   135 
   135 sub _delete_ssl_args {
   136 sub _delete_ssl_args {
   136 
   137 
   137   my %args = @_;
   138     my %args = @_;
   138   map { delete $args{$_} if /^SSL/; } keys %args;
   139     map { delete $args{$_} if /^SSL/; } keys %args;
   139   return %args;
   140     return %args;
   140 
   141 
   141 }
   142 }
   142 
   143 
   143 sub _is_ssl {
   144 sub _is_ssl {
   144 
   145 
   145   my $self = shift;
   146     my $self = shift;
   146   return $ISA[0] eq "IO::Socket::SSL";
   147     return $ISA[0] eq "IO::Socket::SSL";
   147 
   148 
   148 }
   149 }
   149 
   150 
   150 # authentication
   151 # authentication
   151 package Authen::hlog;
   152 package Authen::hlog;
   154 use Digest::SHA1 qw(sha1_base64);
   155 use Digest::SHA1 qw(sha1_base64);
   155 use MIME::Base64 qw(decode_base64);
   156 use MIME::Base64 qw(decode_base64);
   156 
   157 
   157 sub new {
   158 sub new {
   158 
   159 
   159   my $class = shift;
   160     my $class = shift;
   160   
   161 
   161   my $self = { @_ };
   162     my $self = {@_};
   162 
   163 
   163   die "At least one of 'filename' or 'authdata' parameters is required" unless $self->{filename} || $self->{authdata};
   164     die "At least one of 'filename' or 'authdata' parameters is required"
   164 
   165       unless $self->{filename} || $self->{authdata};
   165   bless $self, $class;
   166 
   166   $self->authdata if $self->{filename};
   167     bless $self, $class;
   167 
   168     $self->authdata if $self->{filename};
   168   return $self;
   169 
       
   170     return $self;
   169 
   171 
   170 }
   172 }
   171 
   173 
   172 sub verify_base64 {
   174 sub verify_base64 {
   173 
   175 
   174   my $self = shift;
   176     my $self = shift;
   175   return $self->verify(split /:/, decode_base64($_[0]));
   177     return $self->verify(split /:/, decode_base64($_[0]));
   176 
   178 
   177 }
   179 }
   178   
       
   179 
   180 
   180 sub verify {
   181 sub verify {
   181 
   182 
   182   my $self = shift;
   183     my $self = shift;
   183 
   184 
   184   my ($u, $p) = @_; 
   185     my ($u, $p) = @_;
   185 
   186 
   186   my $hp = $self->{authdata}->{$u};
   187     my $hp = $self->{authdata}->{$u};
   187 
   188 
   188   # crypt?
   189     # crypt?
   189   if (length $hp == 13) {
   190     if (length $hp == 13) {
   190     return crypt ($p, $hp) eq $hp;
   191         return crypt($p, $hp) eq $hp;
   191   # apache md5?
   192 
   192   } elsif (length $hp == 37 && $hp =~ /^\$apr/) {
   193         # apache md5?
   193     return apache_md5_crypt ($p, $hp) eq $hp;
   194     }
   194   } elsif ($hp =~ s/^\{SHA\}//) {
   195     elsif (length $hp == 37 && $hp =~ /^\$apr/) {
   195     # remove trailing equality signs because sha1_base64 refuses to add any
   196         return apache_md5_crypt($p, $hp) eq $hp;
   196     $hp =~ s/=*$//;
   197     }
   197     return sha1_base64($p) eq $hp;
   198     elsif ($hp =~ s/^\{SHA\}//) {
   198   } else {
   199 
   199     warn "unknown hash format: >>>$hp<<<";
   200         # remove trailing equality signs because sha1_base64 refuses to add any
   200   }
   201         $hp =~ s/=*$//;
   201 
   202         return sha1_base64($p) eq $hp;
   202   return;
   203     }
       
   204     else {
       
   205         warn "unknown hash format: >>>$hp<<<";
       
   206     }
       
   207 
       
   208     return;
   203 
   209 
   204 }
   210 }
   205 
   211 
   206 sub authdata {
   212 sub authdata {
   207 
   213 
   208   my $self = shift;
   214     my $self = shift;
   209 
   215 
   210   my ($htpasswd) = @_ || $self->{filename} || die "Missing filename";
   216     my ($htpasswd) = @_ || $self->{filename} || die "Missing filename";
   211 
   217 
   212   $self->{authdata} = {};
   218     $self->{authdata} = {};
   213 
   219 
   214   open H, "<$htpasswd" or die "Cant open '<$htpasswd': $!";
   220     open H, "<$htpasswd" or die "Cant open '<$htpasswd': $!";
   215   while (my $line = <H>) {
   221     while (my $line = <H>) {
   216 
   222 
   217     chomp $line;
   223         chomp $line;
   218     # htpasswd lines may have more than 2 fields
   224 
   219     my ($u, $p) = split /:/, $line, 3;
   225         # htpasswd lines may have more than 2 fields
   220 
   226         my ($u, $p) = split /:/, $line, 3;
   221     unless ($u && $p) {
   227 
   222       warn "invalid htpasswd line in '$htpasswd' at line $.";
   228         unless ($u && $p) {
   223       next;
   229             warn "invalid htpasswd line in '$htpasswd' at line $.";
   224     }
   230             next;
   225 
   231         }
   226     warn "duplicate user '$u' in '$htpasswd' at line $. - overriding previous record" if exists $self->{authdata}->{$u};
   232 
   227     $self->{authdata}->{$u} = $p;
   233         warn
   228 
   234 "duplicate user '$u' in '$htpasswd' at line $. - overriding previous record"
   229   }
   235           if exists $self->{authdata}->{$u};
   230 
   236         $self->{authdata}->{$u} = $p;
   231   close H or warn "Cant close '<$htpasswd': $!";
   237 
   232 
   238     }
   233   warn "no authentication data found" unless %{$self->{authdata}};
   239 
   234 
   240     close H or warn "Cant close '<$htpasswd': $!";
   235   return $self->{authdata};
   241 
   236   
   242     warn "no authentication data found" unless %{ $self->{authdata} };
       
   243 
       
   244     return $self->{authdata};
       
   245 
   237 }
   246 }
   238 
   247 
   239 # back to main package
   248 # back to main package
   240 package main;
   249 package main;
   241 
   250 
   242 END {
   251 END {
   243     unlink $pidfile
   252     unlink $pidfile
   244       if defined $pidfile and not ref $pidfile and defined $masterpid and $masterpid == $$;
   253       if defined $pidfile
       
   254           and not ref $pidfile
       
   255           and defined $masterpid
       
   256           and $masterpid == $$;
   245 }
   257 }
   246 
   258 
   247 sub find_writable_dir(@);
   259 sub find_writable_dir(@);
   248 
   260 
   249 sub log_open($);
   261 sub log_open($);
   276         "ssl-key=s"  => \$opt_ssl_key
   288         "ssl-key=s"  => \$opt_ssl_key
   277     ) or pod2usage();
   289     ) or pod2usage();
   278 
   290 
   279     $IO::Socket::hlog::DEBUG = $opt_debug;
   291     $IO::Socket::hlog::DEBUG = $opt_debug;
   280 
   292 
   281     $authdata = new Authen::hlog(filename => $opt_htpasswd, realm => $opt_realm) if $opt_auth;
   293     $authdata = new Authen::hlog(filename => $opt_htpasswd, realm => $opt_realm)
       
   294       if $opt_auth;
   282 
   295 
   283     if (defined($logdir = find_writable_dir(@$logdir))) {
   296     if (defined($logdir = find_writable_dir(@$logdir))) {
   284         $access = sprintf $$access, $logdir;
   297         $access = sprintf $$access, $logdir;
   285         $errors = sprintf $$errors, $logdir;
   298         $errors = sprintf $$errors, $logdir;
   286         log_open($access);
   299         log_open($access);
   335         LocalPort     => $opt_port,
   348         LocalPort     => $opt_port,
   336         Proto         => "tcp",
   349         Proto         => "tcp",
   337         Listen        => 1,
   350         Listen        => 1,
   338         ReuseAddr     => 1,
   351         ReuseAddr     => 1,
   339         SSL           => $opt_ssl,
   352         SSL           => $opt_ssl,
   340         SSL_key_file  => $opt_ssl_key, 
   353         SSL_key_file  => $opt_ssl_key,
   341         SSL_cert_file => $opt_ssl_cert,
   354         SSL_cert_file => $opt_ssl_cert,
   342         debug         => $opt_debug
   355         debug         => $opt_debug
   343     ) or die "Can't create listener socket: ", IO::Socket::hlog::errstr, "\n";
   356     ) or die "Can't create listener socket: ", IO::Socket::hlog::errstr, "\n";
   344 
       
   345 
   357 
   346     # go daemon
   358     # go daemon
   347     chdir("/") or die "Can't chdir to /: $!\n";
   359     chdir("/") or die "Can't chdir to /: $!\n";
   348 
   360 
   349     if ($opt_daemon) {
   361     if ($opt_daemon) {
   461     my $lines = (s/(\d+)$//    ? $1 : $opt_lines);
   473     my $lines = (s/(\d+)$//    ? $1 : $opt_lines);
   462     my $tag   = (s/^\/*(\w+)// ? $1 : "default");
   474     my $tag   = (s/^\/*(\w+)// ? $1 : "default");
   463 
   475 
   464     my $authorized;
   476     my $authorized;
   465     $authorized = 1 unless $opt_auth;
   477     $authorized = 1 unless $opt_auth;
   466     # read and verify (first) authentication header and discard any other headers
   478 
   467     while (<$client>) { 
   479    # read and verify (first) authentication header and discard any other headers
   468       
   480     while (<$client>) {
   469       if (!defined $authorized && /^Authorization: Basic ([[:alnum:]+\/=]+)\r?$/) {
   481 
   470         $authorized = $authdata->verify_base64($1);
   482         if (!defined $authorized
   471         log_write("authentication failure from " . $client->peerhost) unless $authorized;
   483             && /^Authorization: Basic ([[:alnum:]+\/=]+)\r?$/)
   472       }
   484         {
   473       last if /^\s*$/;
   485             $authorized = $authdata->verify_base64($1);
   474       
   486             log_write("authentication failure from " . $client->peerhost)
       
   487               unless $authorized;
       
   488         }
       
   489         last if /^\s*$/;
       
   490 
   475     }
   491     }
   476 
   492 
   477     unless ($authorized) {
   493     unless ($authorized) {
   478 
   494 
   479       $client->print(http {
   495         $client->print(
   480         code => "401 Unauthorized",
   496             http {
   481         headers => { "WWW-Authenticate" => "Basic realm=\"$authdata->{realm}\"" }
   497                 code    => "401 Unauthorized",
   482         }, "not authorized");
   498                 headers => {
   483       return;
   499                     "WWW-Authenticate" => "Basic realm=\"$authdata->{realm}\""
       
   500                 }
       
   501             },
       
   502             "not authorized"
       
   503         );
       
   504         return;
   484 
   505 
   485     }
   506     }
   486 
   507 
   487     if (not exists $FILE{$tag}) {
   508     if (not exists $FILE{$tag}) {
   488         $client->print(http "500 unknown file tag",
   509         $client->print(http "500 unknown file tag",
   549 
   570 
   550     my ($headers, $code, $date) = ('');
   571     my ($headers, $code, $date) = ('');
   551 
   572 
   552     if (ref $_[0] eq "HASH") {
   573     if (ref $_[0] eq "HASH") {
   553 
   574 
   554       my $h;
   575         my $h;
   555       ($code, $date, $h) = @{$_[0]}{'code', 'date', 'headers'};
   576         ($code, $date, $h) = @{ $_[0] }{ 'code', 'date', 'headers' };
   556       $headers = ( join "\n", map { "$_: $h->{$_}" } keys %{$h} ) . "\n" if defined $h;
   577         $headers = (join "\n", map { "$_: $h->{$_}" } keys %{$h}) . "\n"
   557       shift;
   578           if defined $h;
   558 
   579         shift;
   559     } else {
   580 
   560 
   581     }
   561       $code = shift;
   582     else {
   562 
   583 
   563     }
   584         $code = shift;
   564     
   585 
       
   586     }
       
   587 
   565     $date ||= date1123();
   588     $date ||= date1123();
   566 
   589 
   567     my $type = $_[0] =~ /^<!DOCTYPE HTML/ ? "text/html" : "text/plain";
   590     my $type = $_[0] =~ /^<!DOCTYPE HTML/ ? "text/html" : "text/plain";
   568 
   591 
   569     return <<__EOF, @_;
   592     return <<__EOF, @_;
   576 }
   599 }
   577 
   600 
   578 sub date1123(;$) {
   601 sub date1123(;$) {
   579     my @now = gmtime(@_ ? shift : time);
   602     my @now = gmtime(@_ ? shift : time);
   580     sprintf "%s, %2d %s %4d %02d:%02d:%02d GMT",
   603     sprintf "%s, %2d %s %4d %02d:%02d:%02d GMT",
   581       qw(Sun Mon Tue Wed Thu Fri Sat Sun) [ $now[6] ],
   604       qw(Sun Mon Tue Wed Thu Fri Sat Sun) [$now[6]],
   582       $now[3],
   605       $now[3],
   583       qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec) [ $now[4] ],
   606       qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec) [$now[4]],
   584       $now[5] + 1900, @now[ 2, 1, 0 ];
   607       $now[5] + 1900, @now[2, 1, 0];
   585 }
   608 }
   586 
   609 
   587 sub bad_request() {
   610 sub bad_request() {
   588     return <<'__EOF';
   611     return <<'__EOF';
   589 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
   612 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">