Net-Server
view release on metacpan or search on metacpan
lib/Net/Server.pm view on Meta::CPAN
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;
croak "Passed log_function $code was not a valid method of server, or was not a code object" 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
croak "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'})
|| croak "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 parse_info($port, $host, $proto, $ipv, $self);
}
lib/Net/Server.pm view on Meta::CPAN
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--) {
( run in 1.066 second using v1.01-cache-2.11-cpan-71847e10f99 )