Server IP : 85.214.239.14 / Your IP : 3.140.188.174 Web Server : Apache/2.4.62 (Debian) System : Linux h2886529.stratoserver.net 4.9.0 #1 SMP Tue Jan 9 19:45:01 MSK 2024 x86_64 User : www-data ( 33) PHP Version : 7.4.18 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare, MySQL : OFF | cURL : OFF | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : OFF Directory : /proc/3/cwd/proc/3/cwd/usr/share/perl5/Net/ |
Upload File : |
# -*- perl -*- # # Net::Server # ABSTRACT: Extensible Perl internet server # # Copyright (C) 2001-2022 # # Paul Seamons <paul@seamons.com> # # Rob Brown <bbb@cpan,org> # # This package may be distributed under the terms of either the # GNU General Public License # or the # Perl Artistic License # # All rights reserved. # ################################################################ package Net::Server; use strict; use Socket qw(AF_INET AF_UNIX SOCK_DGRAM SOCK_STREAM); use IO::Socket (); use IO::Select (); use POSIX (); use Net::Server::Proto (); use Net::Server::Daemonize qw(check_pid_file create_pid_file safe_fork get_uid get_gid set_uid set_gid); our $VERSION = '2.013'; sub new { my $class = shift || die "Missing class"; my $args = @_ == 1 ? shift : {@_}; return bless {server => {%$args}}, $class; } sub net_server_type { __PACKAGE__ } sub get_property { $_[0]->{'server'}->{$_[1]} } sub set_property { $_[0]->{'server'}->{$_[1]} = $_[2] } sub run { my $self = ref($_[0]) ? shift() : shift->new; # pass package or object $self->{'server'}->{'_run_args'} = [@_ == 1 ? %{$_[0]} : @_]; $self->_initialize; # configure all parameters $self->post_configure; # verification of passed parameters $self->post_configure_hook; # user customizable hook $self->pre_bind; # finalize ports to be bound $self->bind; # connect to port(s), setup selection handle for multi port $self->post_bind_hook; # user customizable hook $self->post_bind; # allow for chrooting, becoming a different user and group $self->pre_loop_hook; # user customizable hook $self->loop; # repeat accept/process cycle $self->server_close; # close the server and release the port } sub run_client_connection { my $self = shift; my $c = $self->{'server'}->{'client'}; $self->post_accept($c); # prepare client for processing $self->get_client_info($c); # determines information about peer and local $self->post_accept_hook($c); # user customizable hook my $ok = $self->allow_deny($c) && $self->allow_deny_hook($c); # do allow/deny check on client info if ($ok) { $self->process_request($c); # This is where the core functionality of a Net::Server should be. } else { $self->request_denied_hook($c); # user customizable hook } $self->post_process_request_hook($ok); # user customizable hook $self->post_process_request; # clean up client connection, etc $self->post_client_connection_hook; # one last hook } ###----------------------------------------------------------------### sub _initialize { my $self = shift; my $prop = $self->{'server'} ||= {}; $self->commandline($self->_get_commandline) if ! eval { local $SIG{__DIE__}; $self->commandline }; # save for a HUP $self->configure_hook; # user customizable hook $self->configure; # allow for reading of commandline, program, and configuration file parameters my @defaults = %{ $self->default_values || {} }; # allow yet another way to pass defaults $self->process_args(\@defaults) if @defaults; } sub commandline { my $self = shift; $self->{'server'}->{'commandline'} = ref($_[0]) ? shift : \@_ if @_; return $self->{'server'}->{'commandline'} || die "commandline was not set during initialization"; } sub _get_commandline { my $self = shift; my $script = $0; $script = $ENV{'PWD'} .'/'. $script if $script =~ m|^[^/]+/| && $ENV{'PWD'}; # add absolute to relative - avoid Cwd $script =~ /^(.+)$/; # untaint for later use in hup return [$1, @ARGV] } sub configure_hook {} sub configure { my $self = shift; my $prop = $self->{'server'}; my $template = ($_[0] && ref($_[0])) ? shift : undef; $self->process_args(\@ARGV, $template) if @ARGV; # command line $self->process_args($prop->{'_run_args'}, $template) if $prop->{'_run_args'}; # passed to run if ($prop->{'conf_file'}) { $self->process_args($self->_read_conf($prop->{'conf_file'}), $template); } else { my $def = $self->default_values || {}; $self->process_args($self->_read_conf($def->{'conf_file'}), $template) if $def->{'conf_file'}; } } sub default_values { {} } sub post_configure { my $self = shift; my $prop = $self->{'server'}; $prop->{'log_level'} = 2 if ! defined($prop->{'log_level'}) || $prop->{'log_level'} !~ /^\d+$/; $prop->{'log_level'} = 4 if $prop->{'log_level'} > 4; $self->initialize_logging; if ($prop->{'pid_file'}) { # see if a daemon is already running if (! eval{ check_pid_file($prop->{'pid_file'}) }) { warn $@ if !$ENV{'BOUND_SOCKETS'}; $self->fatal(my $e = $@); } } if (! $prop->{'_is_inet'}) { # completetely daemonize by closing STDIN, STDOUT (should be done before fork) if ($prop->{'setsid'} || length($prop->{'log_file'})) { open(STDIN, '<', '/dev/null') || die "Cannot read /dev/null [$!]"; open(STDOUT, '>', '/dev/null') || die "Cannot write /dev/null [$!]"; } } if (!$ENV{'BOUND_SOCKETS'}) { # don't need to redo this if hup'ing if ($prop->{'setsid'} || $prop->{'background'}) { my $pid = eval { safe_fork() }; $self->fatal(my $e = $@) if ! defined $pid; exit(0) if $pid; $self->log(2, "Process Backgrounded"); } POSIX::setsid() if $prop->{'setsid'}; # completely remove myself from parent process } if (length($prop->{'log_file'}) && !$prop->{'log_function'}) { open STDERR, '>&_SERVER_LOG' || die "Cannot open STDERR to _SERVER_LOG [$!]"; } elsif ($prop->{'setsid'}) { # completely daemonize by closing STDERR (should be done after fork) open STDERR, '>&STDOUT' || die "Cannot open STDERR to STDOUT [$!]"; } # allow for a pid file (must be done after backgrounding and chrooting) # Remove of this pid may fail after a chroot to another location... however it doesn't interfere either. if ($prop->{'pid_file'}) { if (eval { create_pid_file($prop->{'pid_file'}) }) { $prop->{'pid_file_unlink'} = 1; } else { $self->fatal(my $e = $@); } } # make sure that allow and deny look like array refs $prop->{$_} = [] for grep {! ref $prop->{$_}} qw(allow deny cidr_allow cidr_deny); $prop->{'reverse_lookups'} ||= 1 if $prop->{'double_reverse_lookups'}; $prop->{'double_reverse_lookups'} = $1 || $prop->{'double_reverse_lookups'} || 1 if $prop->{'reverse_lookups'} && $prop->{'reverse_lookups'} =~ /^(?:double|2)(.*)$/i; } sub initialize_logging { my $self = shift; my $prop = $self->{'server'}; if (! defined($prop->{'log_file'})) { $prop->{'log_file'} = ''; # log to STDERR return; } # pluggable logging if (my $code = $prop->{'log_function'}) { if (ref $code ne 'CODE') { require Scalar::Util; die "Passed log_function $code was not a valid method of server, or was not a code object\n" if ! $self->can($code); my $copy = $self; $prop->{'log_function'} = sub { $copy->$code(@_) }; Scalar::Util::weaken($copy); } } elsif ($prop->{'log_file'} =~ /^([a-zA-Z]\w*(?:::[a-zA-Z]\w*)*)$/) { my $pkg = "Net::Server::Log::$prop->{'log_file'}"; (my $file = "$pkg.pm") =~ s|::|/|g; if (eval { require $file }) { $prop->{'log_function'} = $pkg->initialize($self); $prop->{'log_class'} = $pkg; return; } elsif ($file =~ /::/ || grep {-e "$_/$file"} @INC) { $self->fatal("Unable to load log module $pkg from file $file: $@"); } } # regular file based logging die "Unsecure filename \"$prop->{'log_file'}\"" if $prop->{'log_file'} !~ m|^([\:\w\.\-/\\]+)$|; $prop->{'log_file'} = $1; # open a logging file open(_SERVER_LOG, ">>", $prop->{'log_file'}) || die "Couldn't open log file \"$prop->{'log_file'}\" [$!]."; _SERVER_LOG->autoflush(1); push @{ $prop->{'chown_files'} }, $prop->{'log_file'}; } sub post_configure_hook {} sub _server_type { ref($_[0]) } sub pre_bind { # make sure we have good port parameters my $self = shift; my $prop = $self->{'server'}; my $super = $self->net_server_type; my $type = $self->_server_type; if ($self->isa('Net::Server::MultiType')) { my $base = delete($prop->{'_recursive_multitype'}) || Net::Server::MultiType->net_server_type; $super = "$super -> MultiType -> $base"; } $type .= " (type $super)" if $type ne $super; $self->log(2, $self->log_time ." $type starting! pid($$)"); $prop->{'sock'} = [grep {$_} map { $self->proto_object($_) } @{ $self->prepared_ports }]; $self->fatal("No valid socket parameters found") if ! @{ $prop->{'sock'} }; } sub prepared_ports { my $self = shift; my $prop = $self->{'server'}; my ($ports, $hosts, $protos, $ipvs) = @$prop{qw(port host proto ipv)}; $ports ||= $prop->{'ports'}; if (!defined($ports) || (ref($ports) && !@$ports)) { $ports = $self->default_port; if (!defined($ports) || (ref($ports) && !@$ports)) { $ports = default_port(); $self->log(2, "Port Not Defined. Defaulting to '$ports'"); } } my %bound; my $bind = $prop->{'_bind'} = []; for my $_port (ref($ports) ? @$ports : $ports) { my $_host = ref($hosts) ? $hosts->[ @$bind >= @$hosts ? -1 : $#$bind + 1] : $hosts; # if ports are greater than hosts - augment with the last host my $_proto = ref($protos) ? $protos->[@$bind >= @$protos ? -1 : $#$bind + 1] : $protos; my $_ipv = ref($ipvs) ? $ipvs->[ @$bind >= @$ipvs ? -1 : $#$bind + 1] : $ipvs; foreach my $info ($self->port_info($_port, $_host, $_proto, $_ipv)) { my ($port, $host, $proto, $ipv) = @$info{qw(port host proto ipv)}; # use cleaned values if ($port ne "0" && $bound{"$host\e$port\e$proto\e$ipv"}++) { $self->log(2, "Duplicate configuration (\U$proto\E) on [$host]:$port with IPv$ipv) - skipping"); next; } push @$bind, $info; } } return $bind; } sub port_info { my ($self, $port, $host, $proto, $ipv) = @_; return Net::Server::Proto->parse_info($port, $host, $proto, $ipv, $self); } sub proto_object { my ($self, $info) = @_; return Net::Server::Proto->object($info, $self); } sub bind { # bind to the port (This should serve all but INET) my $self = shift; my $prop = $self->{'server'}; if (exists $ENV{'BOUND_SOCKETS'}) { $self->restart_open_hook; $self->log(2, "Binding open file descriptors"); my %map; foreach my $info (split /\s*;\s*/, $ENV{'BOUND_SOCKETS'}) { my ($fd, $host, $port, $proto, $ipv, $orig) = split /\|/, $info; $orig = $port if ! defined $orig; # allow for things like service ports or port 0 $fd = ($fd =~ /^(\d+)$/) ? $1 : $self->fatal("Bad file descriptor"); $map{"$host|$orig|$proto|$ipv"}->{$fd} = $port; } foreach my $sock (@{ $prop->{'sock'} }) { $sock->log_connect($self); if (my $ref = $map{$sock->hup_string}) { my ($fd, $port) = each %$ref; $sock->reconnect($fd, $self, $port); delete $ref->{$fd}; delete $map{$sock->hup_string} if ! keys %$ref; } else { $self->log(2, "Added new port configuration"); $sock->connect($self); } } foreach my $str (keys %map) { foreach my $fd (keys %{ $map{$str} }) { $self->log(2, "Closing un-mapped port ($str) on fd $fd"); POSIX::close($fd); } } delete $ENV{'BOUND_SOCKETS'}; $self->{'hup_waitpid'} = 1; } else { # connect to fresh ports foreach my $sock (@{ $prop->{'sock'} }) { $sock->log_connect($self); $sock->connect($self); } } if (@{ $prop->{'sock'} } > 1 || $prop->{'multi_port'}) { $prop->{'multi_port'} = 1; $prop->{'select'} = IO::Select->new; # if more than one socket we'll need to select on it $prop->{'select'}->add($_) for @{ $prop->{'sock'} }; } else { $prop->{'multi_port'} = undef; $prop->{'select'} = undef; } } sub post_bind_hook {} sub post_bind { # secure the process and background it my $self = shift; my $prop = $self->{'server'}; if (! defined $prop->{'group'}) { $self->log(1, "Group Not Defined. Defaulting to EGID '$)'"); $prop->{'group'} = $); } elsif ($prop->{'group'} =~ /^([\w.-]+(?:[ ,][\w.-]+)*)$/) { $prop->{'group'} = eval { get_gid($1) }; $self->fatal(my $e = $@) if $@; } else { $self->fatal("Invalid group \"$prop->{'group'}\""); } if (! defined $prop->{'user'}) { $self->log(1, "User Not Defined. Defaulting to EUID '$>'"); $prop->{'user'} = $>; } elsif ($prop->{'user'} =~ /^([\w.-]+)$/) { $prop->{'user'} = eval { get_uid($1) }; $self->fatal(my $e = $@) if $@; } else { $self->fatal("Invalid user \"$prop->{'user'}\""); } # chown any files or sockets that we need to if ($prop->{'group'} ne $) || $prop->{'user'} ne $>) { my @chown_files; push @chown_files, map {$_->NS_port} grep {$_->NS_proto =~ /^UNIX/} @{ $prop->{'sock'} }; push @chown_files, $prop->{'pid_file'} if $prop->{'pid_file_unlink'}; push @chown_files, $prop->{'lock_file'} if $prop->{'lock_file_unlink'}; push @chown_files, @{ $prop->{'chown_files'} || [] }; my $uid = $prop->{'user'}; my $gid = (split /\ /, $prop->{'group'})[0]; foreach my $file (@chown_files){ chown($uid, $gid, $file) || $self->fatal("Couldn't chown \"$file\" [$!]"); } } if ($prop->{'chroot'}) { $self->fatal("Specified chroot \"$prop->{'chroot'}\" doesn't exist.") if ! -d $prop->{'chroot'}; $self->log(2, "Chrooting to $prop->{'chroot'}"); chroot($prop->{'chroot'}) || $self->fatal("Couldn't chroot to \"$prop->{'chroot'}\": $!"); } # drop privileges eval { if ($prop->{'group'} ne $)) { $self->log(2, "Setting gid to \"$prop->{'group'}\""); set_gid($prop->{'group'} ); } if ($prop->{'user'} ne $>) { $self->log(2, "Setting uid to \"$prop->{'user'}\""); set_uid($prop->{'user'}); } }; if ($@) { if ($> == 0) { $self->fatal(my $e = $@); } elsif ($< == 0) { $self->log(2, "NOTICE: Effective UID changed, but Real UID is 0: $@"); } else { $self->log(2, my $e = $@); } } $prop->{'requests'} = 0; # record number of request $SIG{'INT'} = $SIG{'TERM'} = $SIG{'QUIT'} = sub { $self->server_close; }; $SIG{'PIPE'} = 'IGNORE'; # most cases, a closed pipe will take care of itself $SIG{'CHLD'} = \&sig_chld; # catch children (mainly for Fork and PreFork but works for any chld) $SIG{'HUP'} = sub { $self->sig_hup }; } sub sig_chld { 1 while waitpid(-1, POSIX::WNOHANG()) > 0; $SIG{'CHLD'} = \&sig_chld; } sub pre_loop_hook {} sub loop { my $self = shift; while ($self->accept) { $self->run_client_connection; last if $self->done; } } sub accept { my $self = shift; my $prop = $self->{'server'}; my $sock = undef; my $retries = 30; while ($retries--) { if ($prop->{'multi_port'}) { # with more than one port, use select to get the next one return 0 if $prop->{'_HUP'}; $sock = $self->accept_multi_port || next; # keep trying for the rest of retries return 0 if $prop->{'_HUP'}; if ($self->can_read_hook($sock)) { $retries++; next; } } else { $sock = $prop->{'sock'}->[0]; # single port is bound - just accept } $self->fatal("Received a bad sock!") if ! defined $sock; if (SOCK_DGRAM == $sock->getsockopt(Socket::SOL_SOCKET(), Socket::SO_TYPE())) { # receive a udp packet $prop->{'client'} = $sock; $prop->{'udp_true'} = 1; $prop->{'udp_peer'} = $sock->recv($prop->{'udp_data'}, $sock->NS_recv_len, $sock->NS_recv_flags); } else { # blocking accept per proto delete $prop->{'udp_true'}; $prop->{'client'} = $sock->accept(); } return 0 if $prop->{'_HUP'}; return 1 if $prop->{'client'}; $self->log(2,"Accept failed with $retries tries left: $!"); sleep(1); } $self->log(1,"Ran out of accept retries!"); return undef; } sub accept_multi_port { my @waiting = shift->{'server'}->{'select'}->can_read(); return undef if ! @waiting; return $waiting[rand @waiting]; } sub can_read_hook {} sub post_accept { my $self = shift; my $prop = $self->{'server'}; my $client = shift || $prop->{'client'}; $prop->{'requests'}++; return if $prop->{'udp_true'}; # no need to do STDIN/STDOUT in UDP if (!$client) { $self->log(1,"Client socket information could not be determined!"); return; } $client->post_accept() if $client->can("post_accept"); if (! $prop->{'no_client_stdout'}) { close STDIN; # duplicate some handles and flush them close STDOUT; if ($prop->{'tie_client_stdout'} || ($client->can('tie_stdout') && $client->tie_stdout)) { open STDIN, '<', '/dev/null' or die "Couldn't open STDIN to the client socket: $!"; open STDOUT, '>', '/dev/null' or die "Couldn't open STDOUT to the client socket: $!"; tie *STDOUT, 'Net::Server::TiedHandle', $client, $prop->{'tied_stdout_callback'} or die "Couldn't tie STDOUT: $!"; tie *STDIN, 'Net::Server::TiedHandle', $client, $prop->{'tied_stdin_callback'} or die "Couldn't tie STDIN: $!"; } elsif (defined(my $fileno = fileno $prop->{'client'})) { open STDIN, '<&', $fileno or die "Couldn't open STDIN to the client socket: $!"; open STDOUT, '>&', $fileno or die "Couldn't open STDOUT to the client socket: $!"; } else { *STDIN = \*{ $client }; *STDOUT = \*{ $client }; } STDIN->autoflush(1); STDOUT->autoflush(1); select STDOUT; } } sub get_client_info { my $self = shift; my $prop = $self->{'server'}; my $client = shift || $prop->{'client'}; if ($client->NS_proto =~ /^UNIX/) { delete @$prop{qw(sockaddr sockport peeraddr peerport peerhost peerhost_rev)}; $self->log(3, $self->log_time." CONNECT ".$client->NS_proto." Socket: \"".$client->NS_port."\"") if $prop->{'log_level'} && 3 <= $prop->{'log_level'}; return; } if (my $sockname = $client->sockname) { $prop->{'sockaddr'} = $client->sockhost; $prop->{'sockport'} = $client->sockport; } else { @{ $prop }{qw(sockaddr sockhost sockport)} = ($ENV{'REMOTE_HOST'} || '0.0.0.0', 'inet.test', 0); # commandline } my $addr; if ($prop->{'udp_true'}) { if ($client->sockdomain == AF_INET) { ($prop->{'peerport'}, $addr) = Socket::sockaddr_in($prop->{'udp_peer'}); $prop->{'peeraddr'} = Socket::inet_ntoa($addr); } else { ($prop->{'peerport'}, $addr) = Socket6::sockaddr_in6($prop->{'udp_peer'}); $prop->{'peeraddr'} = Socket6->can('inet_ntop') ? Socket6::inet_ntop($client->sockdomain, $addr) : Socket::inet_ntoa($addr); } } elsif ($prop->{'peername'} = $client->peername) { $addr = $client->peeraddr; $prop->{'peeraddr'} = $client->peerhost; $prop->{'peerport'} = $client->peerport; } else { @{ $prop }{qw(peeraddr peerhost peerport)} = ('0.0.0.0', 'inet.test', 0); # commandline } delete @$prop{qw(peerhost peerhost_rev)}; if ($addr && $prop->{'reverse_lookups'}) { if ($client->can('peerhostname')) { $prop->{'peerhost'} = $client->peerhostname; } elsif ($INC{'Socket6.pm'} && Socket6->can('getnameinfo')) { my @res = Socket6::getnameinfo($client->peername, 0); $prop->{'peerhost'} = $res[0] if @res > 1; } else { $prop->{'peerhost'} = gethostbyaddr($addr, AF_INET); } if ($prop->{'peerhost'} && $prop->{'double_reverse_lookups'}) { $prop->{'peerhost_rev'} = {map {$_->[0] => 1} Net::Server::Proto->get_addr_info($prop->{'peerhost'})}; } } $self->log(3, $self->log_time ." CONNECT ".$client->NS_proto ." Peer: \"[$prop->{'peeraddr'}]:$prop->{'peerport'}\"".($prop->{'peerhost'} ? " ($prop->{'peerhost'}) " : '') ." Local: \"[$prop->{'sockaddr'}]:$prop->{'sockport'}\"") if $prop->{'log_level'} && 3 <= $prop->{'log_level'}; } sub post_accept_hook {} sub allow_deny { my $self = shift; my $prop = $self->{'server'}; my $sock = shift || $prop->{'client'}; # unix sockets are immune to this check return 1 if $sock && $sock->NS_proto =~ /^UNIX/; # work around Net::CIDR::cidrlookup() croaking, # if first parameter is an IPv4 address in IPv6 notation. my $peeraddr = ($prop->{'peeraddr'} =~ /^\s*::ffff:(\d+(?:\.\d+){3})$/) ? $1 : $prop->{'peeraddr'}; if ($prop->{'double_reverse_lookups'}) { return 0 if ! $self->double_reverse_lookup($peeraddr, $prop->{'peerhost'}, $prop->{'peerhost_rev'}, $prop->{'peeraddr'}) } # if no allow or deny parameters are set, allow all return 1 if ! @{ $prop->{'allow'} } && ! @{ $prop->{'deny'} } && ! @{ $prop->{'cidr_allow'} } && ! @{ $prop->{'cidr_deny'} }; # if the addr or host matches a deny, reject it immediately foreach (@{ $prop->{'deny'} }) { return 0 if $prop->{'reverse_lookups'} && defined($prop->{'peerhost'}) && $prop->{'peerhost'} =~ /^$_$/; return 0 if $peeraddr =~ /^$_$/; } if (@{ $prop->{'cidr_deny'} }) { require Net::CIDR; return 0 if Net::CIDR::cidrlookup($peeraddr, @{ $prop->{'cidr_deny'} }); } # if the addr or host isn't blocked yet, allow it if it is allowed foreach (@{ $prop->{'allow'} }) { return 1 if $prop->{'reverse_lookups'} && defined($prop->{'peerhost'}) && $prop->{'peerhost'} =~ /^$_$/; return 1 if $peeraddr =~ /^$_$/; } if (@{ $prop->{'cidr_allow'} }) { require Net::CIDR; return 1 if Net::CIDR::cidrlookup($peeraddr, @{ $prop->{'cidr_allow'} }); } return 0; } sub double_reverse_lookup { my ($self, $addr, $host, $rev_addrs, $orig_addr) = @_; my $cfg = $self->{'server'}->{'double_reverse_lookups'} || ''; if (! $host) { $self->log(3, $self->log_time ." Double reverse missing host from addr $addr"); return 0; } elsif (! $rev_addrs) { $self->log(3, $self->log_time ." Double reverse missing reverse addrs from host $host ($addr)"); return 0; } my $extra = ($orig_addr && $orig_addr ne $addr) ? ", orig_addr: $orig_addr" : ''; if (! $rev_addrs->{$addr} && ! $rev_addrs->{$orig_addr}) { $self->log(3, $self->log_time ." Double reverse did not match: addr: $addr, host: $host" .($cfg =~ /detail/i ? ", addrs: (".join(' ', sort keys %$rev_addrs).")$extra" : '')); return 0; } elsif ($cfg =~ /autofail/i) { $self->log(3, $self->log_time ." Double reverse autofail: addr: $addr, host: $host, addrs: (".join(' ', sort keys %$rev_addrs).")$extra"); return 0; } elsif ($cfg =~ /debug/) { $self->log(3, $self->log_time ." Double reverse debug: addr: $addr, host: $host, addrs: (".join(' ', sort keys %$rev_addrs).")$extra"); } return 1; } sub allow_deny_hook { 1 } # false to deny request sub request_denied_hook {} sub process_request { # sample echo server - override for full functionality my $self = shift; my $prop = $self->{'server'}; if ($prop->{'udp_true'}) { # udp echo server my $client = shift || $prop->{'client'}; if ($prop->{'udp_data'} =~ /dump/) { require Data::Dumper; return $client->send(Data::Dumper::Dumper($self), 0); } return $client->send("You said \"$prop->{'udp_data'}\"", 0); } print "Welcome to \"".ref($self)."\" ($$)\015\012"; my $previous_alarm = alarm 30; eval { local $SIG{'ALRM'} = sub { die "Timed Out!\n" }; while (<STDIN>) { s/[\r\n]+$//; print ref($self),":$$: You said \"$_\"\015\012"; $self->log(5, $_); # very verbose log if (/get\s+(\w+)/) { print "$1: $self->{'server'}->{$1}\015\012" } elsif (/dump/) { require Data::Dumper; print Data::Dumper::Dumper($self) } elsif (/quit/) { last } elsif (/exit/) { $self->server_close } alarm 30; # another 30 } alarm($previous_alarm); }; alarm 0; print "Timed Out.\015\012" if $@ eq "Timed Out!\n"; } sub post_process_request_hook {} sub post_client_connection_hook {} sub post_process_request { my $self = shift; $self->close_client_stdout; } sub close_client_stdout { my $self = shift; my $prop = $self->{'server'}; return if $prop->{'udp_true'}; if (! $prop->{'no_client_stdout'}) { my $t = tied *STDOUT; if ($t) { undef $t; untie *STDOUT }; $t = tied *STDIN; if ($t) { undef $t; untie *STDIN }; open(STDIN, '<', '/dev/null') || die "Cannot read /dev/null [$!]"; open(STDOUT, '>', '/dev/null') || die "Cannot write /dev/null [$!]"; } $prop->{'client'}->close; } sub done { my $self = shift; $self->{'server'}->{'done'} = shift if @_; return $self->{'server'}->{'done'}; } sub pre_fork_hook {} sub register_child {} sub child_init_hook {} sub child_finish_hook {} sub run_dequeue { # fork off a child process to handle dequeuing my $self = shift; $self->pre_fork_hook('dequeue'); my $pid = fork; $self->fatal("Bad fork [$!]") if ! defined $pid; if (!$pid) { # child $SIG{'INT'} = $SIG{'TERM'} = $SIG{'QUIT'} = $SIG{'HUP'} = sub { $self->child_finish_hook('dequeue'); exit; }; $SIG{'PIPE'} = $SIG{'TTIN'} = $SIG{'TTOU'} = 'DEFAULT'; $self->child_init_hook('dequeue'); $self->dequeue(); $self->child_finish_hook('dequeue'); exit; } $self->log(4, "Running dequeue child $pid"); $self->{'server'}->{'children'}->{$pid}->{'status'} = 'dequeue' if $self->{'server'}->{'children'}; $self->register_child($pid, 'dequeue'); } sub default_port { 20203 } sub dequeue {} sub pre_server_close_hook {} sub server_close { my ($self, $exit_val) = @_; my $prop = $self->{'server'}; $SIG{'INT'} = 'DEFAULT'; ### if this is a child process, signal the parent and close ### normally the child shouldn't, but if they do... ### otherwise the parent continues with the shutdown ### this is safe for nonstandard forked child processes ### as they will not have server_close as a handler if (defined($prop->{'ppid'}) && $prop->{'ppid'} != $$ && ! defined($prop->{'no_close_by_child'})) { $self->close_parent; exit; } $self->pre_server_close_hook; $self->log(2, $self->log_time . " Server closing!"); if ($prop->{'kind_quit'} && $prop->{'children'}) { $self->log(3, "Attempting a slow shutdown"); $prop->{$_} = 0 for qw(min_servers max_servers); $self->hup_children; # send children signal to finish up while (1) { Net::Server::SIG::check_sigs(); $self->coordinate_children if $self->can('coordinate_children'); last if !keys %{$self->{'server'}->{'children'}}; sleep 1; } } if ($prop->{'_HUP'} && $prop->{'leave_children_open_on_hup'}) { $self->hup_children; } else { $self->close_children() if $prop->{'children'}; $self->post_child_cleanup_hook; } if (defined($prop->{'lock_file'}) && -e $prop->{'lock_file'} && defined($prop->{'lock_file_unlink'})) { unlink($prop->{'lock_file'}) || $self->log(1, "Couldn't unlink \"$prop->{'lock_file'}\" [$!]"); } if (defined($prop->{'pid_file'}) && -e $prop->{'pid_file'} && !$prop->{'_HUP'} && defined($prop->{'pid_file_unlink'})) { unlink($prop->{'pid_file'}) || $self->log(1, "Couldn't unlink \"$prop->{'pid_file'}\" [$!]"); } if (defined($prop->{'sem'})) { $prop->{'sem'}->remove(); } if ($prop->{'_HUP'}) { $self->restart_close_hook(); $self->hup_server; # execs at the end } $self->shutdown_sockets; return $self if $prop->{'no_exit_on_close'}; $self->server_exit($exit_val); } sub server_exit { my ($self, $exit_val) = @_; exit($exit_val || 0); } sub shutdown_sockets { my $self = shift; my $prop = $self->{'server'}; foreach my $sock (@{ $prop->{'sock'} }) { # unlink remaining socket files (if any) $sock->shutdown(2); unlink $sock->NS_port if $sock->NS_proto =~ /^UNIX/; } $prop->{'sock'} = []; # delete the sock objects return 1; } ### Allow children to send INT signal to parent (or use another method) ### This method is only used by forking servers sub close_parent { my $self = shift; my $prop = $self->{'server'}; die "Missing parent pid (ppid)" if ! $prop->{'ppid'}; kill 'INT', $prop->{'ppid'}; } ### SIG INT the children ### This method is only used by forking servers (ie Fork, PreFork) sub close_children { my $self = shift; my $prop = $self->{'server'}; return unless $prop->{'children'} && scalar keys %{ $prop->{'children'} }; foreach my $pid (keys %{ $prop->{'children'} }) { $self->log(4, "Kill TERM pid $pid"); if (kill('TERM', $pid) || ! kill(0, $pid)) { # if it is killable, kill it $self->delete_child($pid); } } 1 while waitpid(-1, POSIX::WNOHANG()) > 0; } sub is_prefork { 0 } sub hup_children { my $self = shift; my $prop = $self->{'server'}; return unless defined $prop->{'children'} && scalar keys %{ $prop->{'children'} }; return if ! $self->is_prefork; $self->log(2, "Sending children hup signal"); for my $pid (keys %{ $prop->{'children'} }) { $self->log(4, "Kill HUP pid $pid"); kill('HUP', $pid) or $self->log(2, "Failed to kill pid $pid: $!"); } } sub post_child_cleanup_hook {} ### handle sig hup ### this will prepare the server for a restart via exec sub sig_hup { my $self = shift; my $prop = $self->{'server'}; $self->log(2, "Received a SIG HUP"); my $i = 0; my @fd; $prop->{'_HUP'} = []; foreach my $sock (@{ $prop->{'sock'} }) { my $fd = POSIX::dup($sock->fileno) || $self->fatal("Cannot duplicate the socket [$!]"); # hold on to the socket copy until exec; # just temporary: any socket domain will do, # forked process will decide to use IO::Socket::IP or IO::Socket::INET6 if necessary $prop->{'_HUP'}->[$i] = IO::Socket::INET->new; $prop->{'_HUP'}->[$i]->fdopen($fd, 'w') || $self->fatal("Cannot open to file descriptor [$!]"); # turn off the FD_CLOEXEC bit to allow reuse on exec require Fcntl; $prop->{'_HUP'}->[$i]->fcntl(Fcntl::F_SETFD(), my $flags = ""); push @fd, $fd .'|'. $sock->hup_string; # save file-descriptor and host|port|proto|ipv $sock->close(); $i++; } delete $prop->{'select'}; # remove any blocking obstacle $ENV{'BOUND_SOCKETS'} = join "; ", @fd; if ($prop->{'leave_children_open_on_hup'} && scalar keys %{ $prop->{'children'} }) { $ENV{'HUP_CHILDREN'} = join "\n", map {"$_\t$prop->{'children'}->{$_}->{'status'}"} sort keys %{ $prop->{'children'} }; } } sub hup_server { my $self = shift; $self->log(0, $self->log_time()." Re-exec server during HUP"); delete @ENV{$self->hup_delete_env_keys}; exec @{ $self->commandline }; } sub hup_delete_env_keys { return qw(PATH) } sub restart_open_hook {} # this hook occurs if a server has been HUP'ed it occurs just before opening to the fileno's sub restart_close_hook {} # this hook occurs if a server has been HUP'ed it occurs just before exec'ing the server ###----------------------------------------------------------### sub fatal { my ($self, $error) = @_; my ($package, $file, $line) = caller; $self->fatal_hook($error, $package, $file, $line); $self->log(0, $self->log_time ." $error\n at line $line in file $file"); $self->server_close(1); } sub fatal_hook {} ###----------------------------------------------------------### sub log { my ($self, $level, $msg, @therest) = @_; my $prop = $self->{'server'}; return if ! $prop->{'log_level'}; return if $level =~ /^\d+$/ && $level > $prop->{'log_level'}; $msg = sprintf($msg, @therest) if @therest; # if multiple arguments are passed, assume that the first is a format string if ($prop->{'log_function'}) { return if eval { $prop->{'log_function'}->($level, $msg); 1 }; my $err = $@; if ($prop->{'log_class'} && $prop->{'log_class'}->can('handle_error')) { $prop->{'log_class'}->handle_log_error($self, $err, [$level, $msg]); } else { $self->handle_log_error($err, [$level, $msg]); } } return if $level !~ /^\d+$/; $self->write_to_log_hook($level, $msg); } sub handle_log_error { my ($self, $error) = @_; die $error } sub handle_syslog_error { &handle_log_error } sub write_to_log_hook { my ($self, $level, $msg) = @_; my $prop = $self->{'server'}; chomp $msg; $msg =~ s/([^\n\ -\~])/sprintf("%%%02X",ord($1))/eg; if ($prop->{'log_file'}) { print _SERVER_LOG $msg, "\n"; } elsif ($prop->{'setsid'}) { # do nothing ? } else { my $old = select STDERR; print $msg. "\n"; select $old; } } sub log_time { my ($sec,$min,$hour,$day,$mon,$year) = localtime; return sprintf "%04d/%02d/%02d-%02d:%02d:%02d", $year + 1900, $mon + 1, $day, $hour, $min, $sec; } ###----------------------------------------------------------### sub options { my $self = shift; my $ref = shift || {}; my $prop = $self->{'server'}; foreach (qw(port host proto ipv allow deny cidr_allow cidr_deny)) { if (! defined $prop->{$_}) { $prop->{$_} = []; } elsif (! ref $prop->{$_}) { $prop->{$_} = [$prop->{$_}]; # nicely turn us into an arrayref if we aren't one already } $ref->{$_} = $prop->{$_}; } foreach (qw(conf_file user group chroot log_level log_file log_function pid_file background setsid listen ipv6_package reverse_lookups double_reverse_lookups no_close_by_child no_client_stdout tie_client_stdout tied_stdout_callback tied_stdin_callback leave_children_open_on_hup )) { $ref->{$_} = \$prop->{$_}; } return $ref; } ### routine for parsing commandline, module, and conf file ### method has the benefit of leaving unused arguments in @ARGV sub process_args { my ($self, $args, $template) = @_; $self->options($template = {}) if ! $template || ! ref $template; if (!$_[2] && !scalar(keys %$template) && !$self->{'server'}->{'_no_options'}++) { warn "Configuration options were empty - skipping any commandline, config file, or run argument parsing.\n"; } # we want subsequent calls to not overwrite or add to previously set values so that command line arguments win my %previously_set; foreach (my $i = 0; $i < @$args; $i++) { if ($args->[$i] =~ /^(?:--)?(\w+)(?:[=\ ](\S+))?$/ && exists $template->{$1}) { my ($key, $val) = ($1, $2); splice @$args, $i, 1; if (! defined $val) { if ($i > $#$args || ($args->[$i] && $args->[$i] =~ /^--\w+/)) { $val = 1; # allow for options such as --setsid } else { $val = splice @$args, $i, 1; $val = $val->[0] if ref($val) eq 'ARRAY' && @$val == 1 && ref($template->{$key}) ne 'ARRAY'; } } $i--; $val =~ s/%([A-F0-9])/chr(hex $1)/eig if ! ref $val; if (ref $template->{$key} eq 'ARRAY') { if (! defined $previously_set{$key}) { $previously_set{$key} = scalar @{ $template->{$key} }; } next if $previously_set{$key}; push @{ $template->{$key} }, ref($val) eq 'ARRAY' ? @$val : $val; } else { if (! defined $previously_set{$key}) { $previously_set{$key} = defined(${ $template->{$key} }) ? 1 : 0; } next if $previously_set{$key}; die "Found multiple values on the configuration item \"$key\" which expects only one value" if ref($val) eq 'ARRAY'; ${ $template->{$key} } = $val; } } } } sub _read_conf { my ($self, $file) = @_; my @args; $file = ($file =~ m|^([\w\.\-\/\\\:]+)$|) ? $1 : $self->fatal("Unsecure filename \"$file\""); open my $fh, '<', $file or do { $self->fatal("Couldn't open conf \"$file\" [$!]") if $ENV{'BOUND_SOCKETS'}; warn "Couldn't open conf \"$file\" [$!]\n"; }; while (defined(my $line = <$fh>)) { $line = $1 if $line =~ /(.*?)(?<!\\)#/; # trim comments from line $line =~ s/\\#/#/g; push @args, $1, $2 if $line =~ m/^\s*((?:--)?\w+)(?:\s*[=:]\s*|\s+)(.+)/; } close $fh; return \@args; } ###----------------------------------------------------------------### sub other_child_died_hook {} sub delete_child_hook {} sub delete_child { my ($self, $pid) = @_; my $prop = $self->{'server'}; return $self->other_child_died_hook($pid) if ! exists $prop->{'children'}->{$pid}; # prefork server check to clear child communication if ($prop->{'child_communication'}) { if ($prop->{'children'}->{$pid}->{'sock'}) { $prop->{'child_select'}->remove($prop->{'children'}->{$pid}->{'sock'}); $prop->{'children'}->{$pid}->{'sock'}->close; } } $self->delete_child_hook($pid); # user customizable hook delete $prop->{'children'}->{$pid}; } # send signal to all children - used by forking servers sub sig_pass { my ($self, $sig) = @_; foreach my $chld (keys %{ $self->{'server'}->{'children'} }) { $self->log(4, "signaling $chld with $sig" ); kill($sig, $chld) || $self->log(1, "child $chld not signaled with $sig"); } } # register sigs to allow passthrough to children sub register_sig_pass { my $self = shift; my $ref = $self->{'server'}->{'sig_passthrough'} || []; $ref = [$ref] if ! ref $ref; $self->fatal('invalid sig_passthrough') if ref $ref ne 'ARRAY'; return if ! @$ref; $self->log(4, "sig_passthrough option found"); require Net::Server::SIG; foreach my $sig (map {split /\s*,\s*/, $_} @$ref) { my $code = Net::Server::SIG::sig_is_registered($sig); if ($code) { $self->log(2, "Installing passthrough for $sig even though it is already registered."); } else { $code = ref($SIG{$sig}) eq 'CODE' ? $SIG{$sig} : undef; } Net::Server::SIG::register_sig($sig => sub { $self->sig_pass($sig); $code->($sig) if $code; }); $self->log(2, "Installed passthrough for $sig"); } } ###----------------------------------------------------------------### package Net::Server::TiedHandle; sub TIEHANDLE { my $pkg = shift; return bless [@_], $pkg } sub READLINE { my $s = shift; $s->[1] ? $s->[1]->($s->[0], 'getline', @_) : $s->[0]->getline } sub SAY { my $s = shift; $s->[1] ? $s->[1]->($s->[0], 'say', @_) : $s->[0]->say(@_) } sub PRINT { my $s = shift; $s->[1] ? $s->[1]->($s->[0], 'print', @_) : $s->[0]->print(@_) } sub PRINTF { my $s = shift; $s->[1] ? $s->[1]->($s->[0], 'printf', @_) : $s->[0]->printf(@_) } sub READ { my $s = shift; $s->[1] ? $s->[1]->($s->[0], 'read', @_) : $s->[0]->read(@_) } sub WRITE { my $s = shift; $s->[1] ? $s->[1]->($s->[0], 'write', @_) : $s->[0]->write(@_) } sub SYSREAD { my $s = shift; $s->[1] ? $s->[1]->($s->[0], 'sysread', @_) : $s->[0]->sysread(@_) } sub SYSWRITE { my $s = shift; $s->[1] ? $s->[1]->($s->[0], 'syswrite', @_) : $s->[0]->syswrite(@_) } sub SEEK { my $s = shift; $s->[1] ? $s->[1]->($s->[0], 'seek', @_) : $s->[0]->seek(@_) } sub BINMODE {} sub FILENO {} sub CLOSE { my $s = shift; $s->[1] ? $s->[1]->($s->[0], 'close', @_) : $s->[0]->close(@_) } 1; ### The documentation is in Net/Server.pod