Файловый менеджер - Редактировать - /usr/sbin/p0f-analyzer
Назад
#!/usr/bin/perl -T #------------------------------------------------------------------------------ # This is p0f-analyzer.pl, a program to continuously read log reports from p0f # utility, keep results in cache for a couple of minutes, and answer queries # over UDP from some program (like amavisd-new) about collected data. # # Author: Mark Martinec <Mark.Martinec@ijs.si> # # Copyright (c) 2006,2012-2014, Mark Martinec # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # # The views and conclusions contained in the software and documentation are # those of the authors and should not be interpreted as representing official # policies, either expressed or implied, of the Jozef Stefan Institute. # (the above license is the 2-clause BSD license, also known as # a "Simplified BSD License", and pertains to this program only) # # Patches and problem reports are welcome. # The latest version of this program is available at: # http://www.ijs.si/software/amavisd/ #------------------------------------------------------------------------------ use strict; use re 'taint'; use Errno qw(EAGAIN EINTR ENOENT EACCES); use POSIX (); use Socket; use IO::File qw(O_RDONLY); use vars qw($VERSION); $VERSION = '1.502'; # Example usage with p0f v3: # p0f -i eth0 'tcp and dst host mail.example.org' 2>&1 | p0f-analyzer.pl 2345 # # Example usage with old p0f v2: # p0f -l -i eth0 'tcp and dst host mail.example.org' 2>&1 | p0f-analyzer.pl 2345 # # In the p0f filter expression above specify an IP address of the host where # your MTA is listening for incoming mail (in place of host.example.com above). # Match the UDP port number (like 2345 above) with the port number to which a # client will be sending queries ($os_fingerprint_method in amavisd.conf). use vars qw($io_socket_module_name $have_inet4 $have_inet6); BEGIN { # prefer using module IO::Socket::IP if available, # otherwise fall back to IO::Socket::INET6 or to IO::Socket::INET # if (eval { require IO::Socket::IP }) { $io_socket_module_name = 'IO::Socket::IP'; } elsif (eval { require IO::Socket::INET6 }) { $io_socket_module_name = 'IO::Socket::INET6'; } elsif (eval { require IO::Socket::INET }) { $io_socket_module_name = 'IO::Socket::INET'; } $have_inet4 = # can we create a PF_INET socket? defined $io_socket_module_name && eval { my $sock = $io_socket_module_name->new(LocalAddr => '0.0.0.0', Proto => 'tcp'); $sock->close or die "error closing socket: $!" if $sock; $sock ? 1 : undef; }; $have_inet6 = # can we create a PF_INET6 socket? defined $io_socket_module_name && $io_socket_module_name ne 'IO::Socket::INET' && eval { my $sock = $io_socket_module_name->new(LocalAddr => '::', Proto => 'tcp'); $sock->close or die "error closing socket: $!" if $sock; $sock ? 1 : undef; }; } # argument should be a free UDP port where queries will be accepted on @ARGV or die <<'EOD'; Usage: p0f-analyzer.pl socket-spec ... where socket-spec is an UDP port number optionally preceded by an IP address (or a host name) and a colon. An IPv6 address must be enclosed in square brackets so that the port-delimiting colon is unambiguous. To listen on all interfaces specify an asterisk in place of an IP address, e.g. '*:2345'. A host name 'localhost' implies binding to a loopback interface on any available protocol family (IPv4 or IPv6) and is a default when only a port number is specified. Example usage, all three examples are equivalent: p0f -i eth0 'tcp dst port 25' 2>&1 | p0f-analyzer.pl 2345 p0f -i eth0 'tcp dst port 25' | p0f-analyzer.pl localhost:2345 p0f -i eth0 'tcp dst port 25' | p0f-analyzer.pl [::1]:2345 127.0.0.1:2345 EOD my(@listen_sockets, @inet_acl, $retention_time, $log_level, %src); @listen_sockets = map(untaint($_), @ARGV); # list of IP addresses from which queries will be accepted, others ignored @inet_acl = ('::1', '127.0.0.1'); # time in seconds to keep collected information in cache $retention_time = 10*60; $log_level = 0; # Return untainted copy of a string (argument can be a string or a string ref) sub untaint($) { return undef if !defined $_[0]; # must return undef even in a list context! no re 'taint'; local $1; # avoids Perl taint bug: tainted global $1 propagates taintedness (ref($_[0]) ? ${$_[0]} : $_[0]) =~ /^(.*)\z/s; $1; } sub ll($) { my($level) = @_; $level <= $log_level; } # write log entry sub do_log($$;@) { my($level,$errmsg,@args) = @_; if ($level <= $log_level) { $errmsg = sprintf($errmsg,@args) if @args; print STDERR $errmsg,"\n"; } 1; } # ip_to_vec() takes an IPv6 or IPv4 address with optional prefix length # (or an IPv4 mask), parses and validates it, and returns it as a 128-bit # vector string that can be used as operand to Perl bitwise string operators. # Syntax and other errors in the argument throw exception (die). # If the second argument $allow_mask is 0, the prefix length or mask # specification is not allowed as part of the IP address. # # The IPv6 syntax parsing and validation adheres to RFC 4291 (ex RFC 3513). # All the following IPv6 address forms are supported: # x:x:x:x:x:x:x:x preferred form # x:x:x:x:x:x:d.d.d.d alternative form # ...::... zero-compressed form # addr/prefix-length prefix length may be specified (defaults to 128) # Optionally an "IPv6:" prefix may be prepended to an IPv6 address # as specified by RFC 5321 (ex RFC 2821). Brackets enclosing the address # are optional, e.g. [::1]/128 . # # The following IPv4 forms are allowed: # d.d.d.d # d.d.d.d/prefix-length CIDR mask length is allowed (defaults to 32) # d.d.d.d/m.m.m.m network mask (gets converted to prefix-length) # If prefix-length or a mask is specified with an IPv4 address, the address # may be shortened to d.d.d/n or d.d/n or d/n. Such truncation is allowed # for compatibility with earlier version, but is deprecated and is not # allowed for IPv6 addresses. # # IPv4 addresses and masks are converted to IPv4-mapped IPv6 addresses # of the form ::FFFF:d.d.d.d, The CIDR mask length (0..32) is converted # to an IPv6 prefix-length (96..128). The returned vector strings resulting # from IPv4 and IPv6 forms are indistinguishable. # # NOTE: # d.d.d.d is equivalent to ::FFFF:d.d.d.d (IPv4-mapped IPv6 address) # which is not the same as ::d.d.d.d (IPv4-compatible IPv6 address) # # A quadruple is returned: # - an IP address represented as a 128-bit vector (a string) # - network mask derived from prefix length, a 128-bit vector (string) # - prefix length as an integer (0..128) # - interface scope (for link-local addresses), undef if non-scoped # sub ip_to_vec($;$) { my($ip,$allow_mask) = @_; my($ip_len, @ip_fields, $scope); local($1,$2,$3,$4,$5,$6); $ip =~ s/^[ \t]+//; $ip =~ s/[ \t\r\n]+\z//s; # trim my $ipa = $ip; ($ipa,$ip_len) = ($1,$2) if $allow_mask && $ip =~ m{^ ([^/]*) / (.*) \z}xs; $ipa = $1 if $ipa =~ m{^ \[ (.*) \] \z}xs; # discard optional brackets my $have_ipv6; if ($ipa =~ s/^IPv6://i) { $have_ipv6 = 1 } elsif ($ipa =~ /:.*:/s) { $have_ipv6 = 1 } $scope = $1 if $ipa =~ s/ ( % [A-Z0-9:._-]+ ) \z//xsi; # scoped address if ($have_ipv6 && $ipa =~ m{^(.*:) (\d{1,3}) \. (\d{1,3}) \. (\d{1,3}) \. (\d{1,3})\z}xsi){ # IPv6 alternative form x:x:x:x:x:x:d.d.d.d my(@d) = ($2,$3,$4,$5); !grep($_ > 255, @d) or die "Invalid decimal field value in IPv6 address: [$ip]\n"; $ipa = $2 . sprintf('%02x%02x:%02x%02x', @d); } elsif (!$have_ipv6 && $ipa =~ m{^ \d{1,3} (?: \. \d{1,3}){0,3} \z}xs) { # IPv4 my(@d) = split(/\./,$ipa,-1); !grep($_ > 255, @d) or die "Invalid field value in IPv4 address: [$ip]\n"; defined($ip_len) || @d==4 or die "IPv4 address [$ip] contains fewer than 4 fields\n"; $ipa = '::ffff:' . sprintf('%02x%02x:%02x%02x', @d); # IPv4-mapped IPv6 if (!defined($ip_len)) { $ip_len = 32; # no length, defaults to /32 } elsif ($ip_len =~ /^\d{1,9}\z/) { # /n, IPv4 CIDR notation } elsif ($ip_len =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\z/) { my(@d) = ($1,$2,$3,$4); !grep($_ > 255, @d) or die "Illegal field value in IPv4 mask: [$ip]\n"; my $mask1 = pack('C4', @d); # /m.m.m.m my $len = unpack('%b*', $mask1); # count ones my $mask2 = pack('B32', '1' x $len); # reconstruct mask from count $mask1 eq $mask2 or die "IPv4 mask not representing a valid CIDR mask: [$ip]\n"; $ip_len = $len; } else { die "Invalid IPv4 network mask or CIDR prefix length: [$ip]\n"; } $ip_len<=32 or die "IPv4 network prefix length greater than 32: [$ip]\n"; $ip_len += 128-32; # convert IPv4 net mask length to IPv6 prefix length } # now we presumably have an IPv6 compressed or preferred form x:x:x:x:x:x:x:x if ($ipa !~ /^(.*?)::(.*)\z/s) { # zero-compressing form used? @ip_fields = split(/:/,$ipa,-1); # no, have preferred form } else { # expand zero-compressing form my($before,$after) = ($1,$2); my(@bfr) = split(/:/,$before,-1); my(@aft) = split(/:/,$after,-1); my $missing_cnt = 8-(@bfr+@aft); $missing_cnt = 1 if $missing_cnt<1; @ip_fields = (@bfr, ('0') x $missing_cnt, @aft); } @ip_fields >= 8 or die "IPv6 address [$ip] contains fewer than 8 fields\n"; @ip_fields <= 8 or die "IPv6 address [$ip] contains more than 8 fields\n"; !grep(!/^[0-9a-zA-Z]{1,4}\z/, @ip_fields) # this is quite slow or die "Invalid syntax of IPv6 address: [$ip]\n"; my $vec = pack('n8', map(hex($_),@ip_fields)); if (!defined($ip_len)) { $ip_len = 128; } elsif ($ip_len !~ /^\d{1,3}\z/) { die "Invalid prefix length syntax in IP address: [$ip]\n"; } elsif ($ip_len > 128) { die "IPv6 network prefix length greater than 128: [$ip]\n"; } my $mask = pack('B128', '1' x $ip_len); # do_log(5, "ip_to_vec: %s => %s/%d\n", # unpack('B*',$vec) # $ip, join(':',unpack('(H4)*',$vec)), $ip_len); ($vec, $mask, $ip_len, $scope); } sub add_entry($$$$;$) { my($now, $src_ip, $src_port, $descr, $attr_ref) = @_; if ($src_ip =~ /:.*:/) { # normalize an IPv6 address to a preferred form my($vec, $mask, $ip_len, $scope) = ip_to_vec($src_ip); $src_ip = lc join(':',unpack('(H4)*',$vec)); # full preferred form $src_ip =~ s/\b 0{1,3}//xsg; # suppress leading zeroes in each field } my $key = "[$src_ip]:$src_port"; my $entry = $src{$key}; $entry = {} if !$entry; $entry->{t} = $now; $entry->{d} = $descr; do_log(2, "%s [%s]:%d %s", exists($src{$key}) ? 'added:' : 'new: ', $src_ip, $src_port, !$attr_ref ? '' : join('; ', keys %$attr_ref)) if ll(2); if ($attr_ref && %$attr_ref) { # replace attributes while keeping existing ones for my $attr_name (keys %$attr_ref) { $entry->{a}{$attr_name} = $attr_ref->{$attr_name}; } } $src{$key} = $entry; } # main program starts here $SIG{INT} = sub { die "\n" }; # do the END code block when interrupted $SIG{TERM} = sub { die "\n" }; # do the END code block when killed umask(0027); # set our preferred umask my(%fileno_to_socket, @unix_socket_paths_to_be_removed, $rout, $rin); $rin = ''; for (@listen_sockets) { my $sock_spec = $_; if (m{^/.+\z}s) { # looks like a Unix socket absolute path specification $sock_spec = $_; die "Unix datagram sockets are currently not supported\n"; # # test for a stale Unix socket # my(@stat_list) = stat($sock_spec); my $errn = @stat_list ? 0 : 0+$!; # if ($errn == ENOENT) { # no such socket # # good, Unix socket does not exist yet # } elsif ($errn) { # some other error # die "File $sock_spec is inaccessible: $!\n"; # } elsif (!-S _) { # die "File $sock_spec exists but is not a socket\n"; # } elsif (IO::Socket::UNIX->new( # try binding to it # Peer => $sock_spec, Type => &SOCK_STREAM)) { # die "Socket $sock_spec is already in use\n"; # } else { # do_log(1, "Removing stale socket %s", $sock_spec); # unlink $sock_spec # or do_log(-1, "Error unlinking socket %s: %s", $sock_spec, $!); # } # # # create a new Unix socket # # umask(0007); # affects protection of a Unix socket # my $sock = IO::Socket::UNIX->new( # Type => &SOCK_DGRAM, Listen => &SOMAXCONN, # Local => $sock_spec); # $sock or die "Binding to $_ failed: $!"; # # umask(0027); # restore our preferred umask # push(@unix_socket_paths_to_be_removed, $sock_spec); # # my $fileno = $sock->fileno; # vec($rin,$fileno,1) = 1; # $fileno_to_socket{$fileno} = $sock; # do_log(0, "Listening for queries on %s, fn %d", $sock_spec, $fileno); } else { # assume an INET or INET6 socket my(@host, $port); if (m{^ \d+ \z}xs) { # port specification only, assume a loopback interface @host = 'localhost'; $port = $_; } elsif (m{^ \[ ( [^\]]* ) \] (?: : (\d+) )? \z}xs || m{^ ( [^/:]* ) (?: : (\d+) )? \z}xs) { # explicit host & port specified @host = $1; $port = $2; } else { die "Invalid socket specification: $_\n"; } $port or die "Invalid socket specs, a port number is required: $_\n"; # map hostnames 'localhost' and '*' to their equivalents if (@host == 1) { if (lc($host[0]) eq 'localhost') { @host = ('::1', '127.0.0.1') } elsif ($host[0] eq '*') { @host = ('::', '0.0.0.0') } } # filter IP addresses according to available protocol families @host = grep { /^\d+\.\d+\.\d+\.\d+\z/s ? $have_inet4 : /:.*:/s ? $have_inet6 : 1 } @host; for my $h (@host) { my %sockopt = ( LocalAddr => $h, LocalPort => $port, Type => &SOCK_DGRAM, Proto => 'udp', ReuseAddr => 1, ); $sockopt{V6Only} = 1 if $io_socket_module_name eq 'IO::Socket::IP' && IO::Socket::IP->VERSION >= 0.09; my $sock = $io_socket_module_name->new(%sockopt); $sock or die "Binding to socket [$h]:$port failed ". "(using $io_socket_module_name): $!"; my $fileno = $sock->fileno; vec($rin,$fileno,1) = 1; $fileno_to_socket{$fileno} = $sock; do_log(0, "Listening for queries on [%s]:%s, fn %d", $h, $port, $fileno); } } } binmode(STDIN) or die "Can't set binmode on STDIN: $!"; my $fn_input = fileno(STDIN); vec($rin,$fn_input,1) = 1; do_log(0, "p0f-analyzer version %s starting.", $VERSION); my $p0f_version; my $cnt_since_cleanup = 0; my $p0f_buff = ''; my($src_ip, $src_port, $src_t, $src_d, %attr); for (;;) { my($nfound,$timeleft) = select($rout=$rin, undef, undef, undef); defined $nfound && $nfound >= 0 or die "Select failed: $!"; next if !$nfound; my $now = time; for my $fileno (keys %fileno_to_socket) { next if !vec($rout,$fileno,1); # accept a query my $sock = $fileno_to_socket{$fileno}; $sock or die "panic: no socket, fileno=$fileno"; my($query_source, $inbuf); my $paddr = $sock->recv($inbuf, 64, 0); if (!defined($paddr)) { if ($!==EAGAIN || $!==EINTR) { # false alarm, nothing can be read } else { die "recv: $!"; } } else { my $clientaddr = $sock->peerhost; my $clientport = $sock->peerport; if (!defined($clientaddr)) { do_log(1, "query from unknown client"); } elsif (!grep($_ eq $clientaddr, @inet_acl)) { do_log(1, "query from non-approved client: %s:%s", $clientaddr, $clientport); } elsif ($clientport < 1024 || $clientport == 2049 || $clientport > 65535) { do_log(1, "query from questionable port: %s:%s", $clientaddr, $clientport); } elsif ($inbuf !~ /^([^ ]+) (.*)$/s) { do_log(1, "invalid query syntax from %s", $query_source); } else { $query_source = "[$clientaddr]:$clientport"; my($query, $nonce) = ($1, $2); my($src_ip, $src_port); if ($query =~ /^ \[ ([^\]]*) \] (?: : (\d{1,5}) )? \z/xs) { $src_ip = $1; $src_port = $2; if ($src_ip =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/) { # IPv4 } elsif ($src_ip =~ /^ (?: (?: IPv6: )? 0{0,4} (?: : 0{0,4} ){1,4} : ffff : )? ( \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} )\z/xsi) { $src_ip = $1; # IPv4-mapped IPv6 address, alternative form } elsif ($src_ip =~ /^ (?: IPv6: )? [0-9a-f]{0,4} (?: : [0-9a-f]{0,4} ){2,7} \z/xsi) { $src_ip =~ s/^IPv6://i; } elsif ($src_ip =~ /^ (?: IPv6: )? [0-9a-f]{0,4} (?: : [0-9a-f]{0,4} ){1,5} : \d{1,3} (?: \. \d{1,3} ){3} \z/xsi) { $src_ip =~ s/^IPv6://i; } else { undef $src_ip } } $src_port = 0 if !defined $src_port; if (length($nonce) > 1024) { do_log(1, "invalid query from %s, nonce too long: %d chrs", $query_source, length($nonce)); } elsif ($nonce !~ /^([\040-\177]*)\z/s) { do_log(1, "invalid query from %s, forbidden char in nonce", $query_source); } elsif (!defined($src_ip) || $src_port > 65535) { do_log(1, "invalid query from %s, bad IP address or port: %s", $query_source, $query); } else { if ($src_ip =~ /:.*:/) { # normalize an IPv6 address in a query my($vec, $mask, $ip_len, $scope) = ip_to_vec($src_ip); $src_ip = lc join(':',unpack('(H4)*',$vec)); # preferred form $src_ip =~ s/\b 0{1,3}//xsg; # suppress leading zeroes } do_log(2, "query from %s: %s", $query_source, $inbuf); my $resp = ''; if ($src_port > 0 && exists $src{"[$src_ip]:$src_port"}) { my $attr_ref = $src{"[$src_ip]:$src_port"}{a}; if ($attr_ref) { my %tmp_attr = %$attr_ref; # partial compatibility with v2 format: place OS first my $os = delete $tmp_attr{os}; $resp = join('; ', $os, map("$_: $tmp_attr{$_}", sort keys %tmp_attr)); } else { # old p0f (v2) $resp = $src{"[$src_ip]:$src_port"}{d}; } } $resp = $query.' '.$nonce.' '.$resp; do_log(1, "response to %s: %s", $query_source, $resp); defined $sock->send($resp."\015\012", 0, $paddr) or die "send failed: $!"; } } } } if (vec($rout,$fn_input,1)) { # accept more input from p0f $cnt_since_cleanup++; $! = 0; my $nbytes = sysread(STDIN, $p0f_buff, 8192, length $p0f_buff); if (!defined($nbytes)) { if ($!==EAGAIN || $!==EINTR) { # false alarm, nothing can be read } else { die "Read: $!"; } } elsif ($nbytes < 1) { # sysread returns 0 at eof last; # eof } else { while (index($p0f_buff,"\012") >= 0) { local($1,$2,$3,$4,$5,$6); my($dst_ip,$dst_port); if ((!defined $p0f_version || $p0f_version < 3) && $p0f_buff =~ s/^ (\d+\.\d+\.\d+\.\d+) : (\d+) [ -]* (.*) \ ->\ (\d+\.\d+\.\d+\.\d+) : (\d+) \s* (.*) \015? \012//x) { # looks like a old version (v2) of p0f $p0f_version = 2 if !defined $p0f_version; ($src_ip,$src_port,$src_t,$dst_ip,$dst_port,$src_d) = ($1,$2,$3,$4,$5,$6); add_entry($now, $src_ip, $src_port, "$src_t, $src_d"); } elsif ($p0f_buff =~ s/^ \|? \s* \015? \012//x) { # empty } elsif ($p0f_buff =~ s/^ --- .*? \015? \012//x) { # info } elsif ($p0f_buff =~ s/^ \[ [+!] \] .*? \015? \012//x) { # info } elsif ($p0f_buff =~ s/^ \.-\[ \s* (.*?) \s* \] - \015? \012//x) { # new entry %attr = (); ($src_ip, $src_port, $src_t, $src_d) = (undef) x 4; } elsif ($p0f_buff =~ s/^ \| \s* (.*?) \015? \012//x) { my($attr_name, $attr_val) = split(/\s*=\s*/, $1, 2); if (!defined $attr_val) { # ignore } elsif ($attr_name eq 'client' || $attr_name eq 'server') { ($src_ip, $src_port) = split(m{/}, $attr_val, 2); } else { $attr{$attr_name} = $attr_val; } } elsif ($p0f_buff =~ s/^ \` -+ \015? \012//x) { add_entry($now, $src_ip, $src_port, '', \%attr); $p0f_version = 3 if !defined $p0f_version && %attr; %attr = (); ($src_ip, $src_port, $src_t, $src_d) = (undef) x 4; } elsif ($p0f_buff =~ s/^ (.*?) \015? \012//x) { do_log(1, "UNRECOGNIZED <%s>", $1); } else { do_log(0, "SHOULDN'T HAPPEN <%s>", $p0f_buff); $p0f_buff = ''; } } } if ($cnt_since_cleanup > 50) { for my $k (keys %src) { if (ref $src{$k} ne 'ARRAY') { if ($src{$k}{t} + $retention_time < $now) { do_log(2, "EXPIRED: %s, age = %d s", $k, $now - $src{$k}{t}); delete $src{$k}; } } else { my @kept = grep($_->{t} + $retention_time >= $now, @{$src{$k}}); if (!@kept) { do_log(2, "EXPIRED: %s, age = %d s", $k, $now - $src{$k}[0]{t}); delete $src{$k}; } elsif (@kept != @{$src{$k}}) { do_log(2, "SHRUNK: %s, %d -> %d", $k, scalar(@{$src{$k}}), scalar(@kept)); @{$src{$k}} = @kept; } } } $cnt_since_cleanup = 0; } } } do_log(1, "normal termination"); END { # remove Unix sockets we created if (@unix_socket_paths_to_be_removed) { do_log(1, 'Removing socket %s', join(', ', @unix_socket_paths_to_be_removed)); unlink $_ for @unix_socket_paths_to_be_removed; # ignoring errors } }
| ver. 1.4 |
Github
|
.
| PHP 7.4.3-4ubuntu2.24 | Генерация страницы: 0 |
proxy
|
phpinfo
|
Настройка