# -*- perl -*-
#
#  Net::Server - Extensible Perl internet server
#
#  $Id: Server.pm,v 1.105 2007/02/03 07:54:48 rhandom Exp $
#
#  Copyright (C) 2001-2007
#
#    Paul Seamons
#    paul@seamons.com
#    http://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 vars qw($VERSION);
use Socket qw(inet_aton inet_ntoa AF_INET AF_UNIX SOCK_DGRAM SOCK_STREAM);
use IO::Socket ();
use IO::Select ();
use POSIX ();
use Fcntl ();
use Net::Server::Proto ();
use Net::Server::Daemonize qw(check_pid_file create_pid_file
                              get_uid get_gid set_uid set_gid
                              safe_fork
                              );

$VERSION = '0.95';

###----------------------------------------------------------------###

sub new {
  my $class = shift || die "Missing class";
  my $args  = @_ == 1 ? shift : {@_};
  my $self  = bless {server => { %$args }}, $class;
  return $self;
}

sub _initialize {
  my $self = shift;

  ### need a place to store properties
  $self->{server} = {} unless defined($self->{server}) && ref($self->{server});

  ### save for a HUP
  $self->commandline($self->_get_commandline)
      if ! eval { $self->commandline };

  ### prepare to cache configuration parameters
  $self->{server}->{conf_file_args} = undef;
  $self->{server}->{configure_args} = undef;

  $self->configure_hook;      # user customizable hook

  $self->configure(@_);       # allow for reading of commandline,
                              # program, and configuration file parameters

  ### allow yet another way to pass defaults
  my $defaults = $self->default_values || {};
  foreach my $key (keys %$defaults) {
    next if ! exists $self->{server}->{$key};
    if (ref $self->{server}->{$key} eq 'ARRAY') {
      if (! @{ $self->{server}->{$key} }) { # was empty
        my $val = $defaults->{$key};
        $self->{server}->{$key} = ref($val) ? $val : [$val];
      }
    } elsif (! defined $self->{server}->{$key}) {
      $self->{server}->{$key} = $defaults->{$key};
    }
  }

  ### get rid of cached config parameters
  delete $self->{server}->{conf_file_args};
  delete $self->{server}->{configure_args};

}

###----------------------------------------------------------------###

### program flow
sub run {

  ### pass package or object
  my $self = ref($_[0]) ? shift() : shift->new;

  $self->_initialize(@_ == 1 ? %{$_[0]} : @_);     # 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

  ### routines inside a standard $self->loop
  # $self->accept             # wait for client connection
  # $self->run_client_connection # process client
  # $self->done               # indicate if connection is done

  $self->server_close;        # close the server and release the port
                              # this will run pre_server_close_hook
                              #               close_children
                              #               post_child_cleanup_hook
                              #               shutdown_sockets
                              # and either exit or run restart_close_hook
}

### standard connection flow
sub run_client_connection {
  my $self = shift;

  $self->post_accept;         # prepare client for processing

  $self->get_client_info;     # determines information about peer and local

  $self->post_accept_hook;    # user customizable hook

  if( $self->allow_deny             # do allow/deny check on client info
      && $self->allow_deny_hook ){  # user customizable hook

    $self->process_request;   # This is where the core functionality
                              # of a Net::Server should be.  This is the
                              # only method necessary to override.
  }else{

    $self->request_denied_hook;     # user customizable hook

  }

  $self->post_process_request_hook; # user customizable hook

  $self->post_process_request;      # clean up client connection, etc

}

###----------------------------------------------------------------###

sub _get_commandline {
  my $self = shift;
  my $prop = $self->{server};

  ### see if we can find the full command line
  if (open _CMDLINE, "/proc/$$/cmdline") { # unix specific
    my $line = do { local $/ = undef; <_CMDLINE> };
    close _CMDLINE;
    if ($line =~ /^(.+)$/) { # need to untaint to allow for later hup
      return [split /\0/, $1];
    }
  }

  my $script = $0;
  $script = $ENV{'PWD'} .'/'. $script if $script =~ m|^[^/]+/| && $ENV{'PWD'}; # add absolute to relative
  $script =~ /^(.+)$/; # untaint for later use in hup
  return [ $1, @ARGV ]
}

sub commandline {
    my $self = shift;
    if (@_) { # allow for set
      $self->{server}->{commandline} = ref($_[0]) ? shift : \@_;
    }
    return $self->{server}->{commandline} || die "commandline was not set during initialization";
}

###----------------------------------------------------------------###

### any values to set if no configuration could be found
sub default_values { {} }

### any pre-initialization stuff
sub configure_hook {}


### set up the object a little bit better
sub configure {
  my $self = shift;
  my $prop = $self->{server};
  my $template = undef;
  local @_ = @_; # fix some issues under old perls on alpha systems

  ### allow for a template to be passed
  if( $_[0] && ref($_[0]) ){
    $template = shift;
  }

  ### do command line
  $self->process_args( \@ARGV, $template ) if defined @ARGV;

  ### do startup file args
  ### cache a reference for multiple calls later
  my $args = undef;
  if( $prop->{configure_args} && ref($prop->{configure_args}) ){
    $args = $prop->{configure_args};
  }else{
    $args = $prop->{configure_args} = \@_;
  }
  $self->process_args( $args, $template ) if defined $args;

  ### do a config file
  if( defined $prop->{conf_file} ){
    $self->process_conf( $prop->{conf_file}, $template );
  }

}


### make sure it has been configured properly
sub post_configure {
  my $self = shift;
  my $prop = $self->{server};

  ### set the log level
  if( !defined $prop->{log_level} || $prop->{log_level} !~ /^\d+$/ ){
    $prop->{log_level} = 2;
  }
  $prop->{log_level} = 4 if $prop->{log_level} > 4;


  ### log to STDERR
  if( ! defined($prop->{log_file}) ){
    $prop->{log_file} = '';

  ### log to syslog
  }elsif( $prop->{log_file} eq 'Sys::Syslog' ){

    my $logsock = defined($prop->{syslog_logsock})
      ? $prop->{syslog_logsock} : 'unix';
    $prop->{syslog_logsock} = ($logsock =~ /^(unix|inet|stream)$/)
      ? $1 : 'unix';

    my $ident = defined($prop->{syslog_ident})
      ? $prop->{syslog_ident} : 'net_server';
    $prop->{syslog_ident} = ($ident =~ /^([\ -~]+)$/)
      ? $1 : 'net_server';

    require Sys::Syslog;

    my $opt = defined($prop->{syslog_logopt})
      ? $prop->{syslog_logopt} : $Sys::Syslog::VERSION ge '0.15' ? 'pid,nofatal' : 'pid';
    $prop->{syslog_logopt} = ($opt =~ /^((cons|ndelay|nowait|pid|nofatal)($|[,|]))*/)
      ? $1 : 'pid';

    my $fac = defined($prop->{syslog_facility})
      ? $prop->{syslog_facility} : 'daemon';
    $prop->{syslog_facility} = ($fac =~ /^((\w+)($|\|))*/)
      ? $1 : 'daemon';

    Sys::Syslog::setlogsock($prop->{syslog_logsock}) || die "Syslog err [$!]";
    if( ! Sys::Syslog::openlog($prop->{syslog_ident},
                               $prop->{syslog_logopt},
                               $prop->{syslog_facility}) ){
      die "Couldn't open syslog [$!]" if $prop->{syslog_logopt} ne 'ndelay';
    }

  ### open a logging file
  }elsif( $prop->{log_file} && $prop->{log_file} ne 'Sys::Syslog' ){

    die "Unsecure filename \"$prop->{log_file}\""
      unless $prop->{log_file} =~ m|^([\w\.\-/\\]+)$|;
    $prop->{log_file} = $1;
    open(_SERVER_LOG, ">>$prop->{log_file}")
      or die "Couldn't open log file \"$prop->{log_file}\" [$!].";
    _SERVER_LOG->autoflush(1);
    $prop->{chown_log_file} = 1;

  }

  ### see if a daemon is already running
  if( defined $prop->{pid_file} ){
    if( ! eval{ check_pid_file( $prop->{pid_file} ) } ){
      if (! $ENV{BOUND_SOCKETS}) {
        warn $@;
      }
      $self->fatal( $@ );
    }
  }

  ### completetly daemonize by closing STDIN, STDOUT (should be done before fork)
  if( ! $prop->{_is_inet} ){
    if( $prop->{setsid} || length($prop->{log_file}) ){
      open(STDIN,  '</dev/null') || die "Can't read /dev/null  [$!]";
      open(STDOUT, '>/dev/null') || die "Can't write /dev/null [$!]";
    }
  }

  if (! $ENV{BOUND_SOCKETS}) {
    ### background the process - unless we are hup'ing
    if( $prop->{setsid} || defined($prop->{background}) ){
      my $pid = eval{ safe_fork() };
      if( not defined $pid ){ $self->fatal( $@ ); }
      exit(0) if $pid;
      $self->log(2,"Process Backgrounded");
    }

    ### completely remove myself from parent process - unless we are hup'ing
    if( $prop->{setsid} ){
      &POSIX::setsid();
    }
  }

  ### completetly daemonize by closing STDERR (should be done after fork)
  if( length($prop->{log_file}) && $prop->{log_file} ne 'Sys::Syslog' ){
    open STDERR, '>&_SERVER_LOG' || die "Can't open STDERR to _SERVER_LOG [$!]";
  }elsif( $prop->{setsid} ){
    open STDERR, '>&STDOUT' || die "Can't 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( defined $prop->{pid_file} ){
    if( eval{ create_pid_file( $prop->{pid_file} ) } ){
      $prop->{pid_file_unlink} = 1;
    }else{
      $self->fatal( $@ );
    }
  }

  ### make sure that allow and deny look like array refs
  $prop->{allow} = [] unless defined($prop->{allow}) && ref($prop->{allow});
  $prop->{deny}  = [] unless defined($prop->{deny})  && ref($prop->{deny} );
  $prop->{cidr_allow} = [] unless defined($prop->{cidr_allow}) && ref($prop->{cidr_allow});
  $prop->{cidr_deny}  = [] unless defined($prop->{cidr_deny})  && ref($prop->{cidr_deny} );

}


### user customizable hook
sub post_configure_hook {}


### make sure we have good port parameters
sub pre_bind {
  my $self = shift;
  my $prop = $self->{server};

  my $ref   = ref($self);
  no strict 'refs';
  my $super = ${"${ref}::ISA"}[0];
  use strict 'refs';
  my $ns_type = (! $super || $ref eq $super) ? '' : " (type $super)";
  $self->log(2,$self->log_time ." ". ref($self) .$ns_type. " starting! pid($$)");

  ### set a default port, host, and proto
  $prop->{port} = [$prop->{port}] if defined($prop->{port}) && ! ref($prop->{port});
  if (! defined($prop->{port}) || ! @{ $prop->{port} }) {
    $self->log(2,"Port Not Defined.  Defaulting to '20203'\n");
    $prop->{port}  = [ 20203 ];
  }

  $prop->{host} = []              if ! defined $prop->{host};
  $prop->{host} = [$prop->{host}] if ! ref     $prop->{host};
  push @{ $prop->{host} }, (($prop->{host}->[-1]) x (@{ $prop->{port} } - @{ $prop->{host}})); # augment hosts with as many as port
  foreach my $host (@{ $prop->{host} }) {
    $host = '*' if ! defined $host || ! length $host;;
    $host = ($host =~ /^([\w\.\-\*\/]+)$/) ? $1 : $self->fatal("Unsecure host \"$host\"");
  }

  $prop->{proto} = []               if ! defined $prop->{proto};
  $prop->{proto} = [$prop->{proto}] if ! ref     $prop->{proto};
  push @{ $prop->{proto} }, (($prop->{proto}->[-1]) x (@{ $prop->{port} } - @{ $prop->{proto}})); # augment hosts with as many as port
  foreach my $proto (@{ $prop->{proto} }) {
      $proto ||= 'tcp';
      $proto = ($proto =~ /^(\w+)$/) ? $1 : $self->fatal("Unsecure proto \"$proto\"");
  }

  ### loop through the passed ports
  ### set up parallel arrays of hosts, ports, and protos
  ### port can be any of many types (tcp,udp,unix, etc)
  ### see perldoc Net::Server::Proto for more information
  my %bound;
  foreach (my $i = 0 ; $i < @{ $prop->{port} } ; $i++) {
    my $port  = $prop->{port}->[$i];
    my $host  = $prop->{host}->[$i];
    my $proto = $prop->{proto}->[$i];
    if ($bound{"$host/$port/$proto"}++) {
      $self->log(2, "Duplicate configuration (".(uc $proto)." port $port on host $host - skipping");
      next;
    }
    my $obj = $self->proto_object($host, $port, $proto) || next;
    push @{ $prop->{sock} }, $obj;
  }
  if (! @{ $prop->{sock} }) {
    $self->fatal("No valid socket parameters found");
  }

  $prop->{listen} = Socket::SOMAXCONN()
    unless defined($prop->{listen}) && $prop->{listen} =~ /^\d{1,3}$/;

}

### method for invoking procol specific bindings
sub proto_object {
  my $self = shift;
  my ($host,$port,$proto) = @_;
  return Net::Server::Proto->object($host,$port,$proto,$self);
}

### bind to the port (This should serve all but INET)
sub bind {
  my $self = shift;
  my $prop = $self->{server};

  ### connect to previously bound ports
  if( exists $ENV{BOUND_SOCKETS} ){

    $self->restart_open_hook();

    $self->log(2, "Binding open file descriptors");

    ### loop through the past information and match things up
    foreach my $info (split /\n/, $ENV{BOUND_SOCKETS}) {
      my ($fd, $hup_string) = split /\|/, $info, 2;
      $fd = ($fd =~ /^(\d+)$/) ? $1 : $self->fatal("Bad file descriptor");
      foreach my $sock ( @{ $prop->{sock} } ){
        if ($hup_string eq $sock->hup_string) {
          $sock->log_connect($self);
          $sock->reconnect($fd, $self);
          last;
        }
      }
    }
    delete $ENV{BOUND_SOCKETS};

  ### connect to fresh ports
  }else{

    foreach my $sock ( @{ $prop->{sock} } ){
      $sock->log_connect($self);
      $sock->connect( $self );
    }

  }

  ### if more than one port we'll need to select on it
  if( @{ $prop->{port} } > 1 || $prop->{multi_port} ){
    $prop->{multi_port} = 1;
    $prop->{select} = IO::Select->new();
    foreach ( @{ $prop->{sock} } ){
      $prop->{select}->add( $_ );
    }
  }else{
    $prop->{multi_port} = undef;
    $prop->{select}     = undef;
  }

}


### user customizable hook
sub post_bind_hook {}


### secure the process and background it
sub post_bind {
  my $self = shift;
  my $prop = $self->{server};


  ### figure out the group(s) to run as
  if( ! defined $prop->{group} ){
    $self->log(1,"Group Not Defined.  Defaulting to EGID '$)'\n");
    $prop->{group}  = $);
  }else{
    if( $prop->{group} =~ /^([\w-]+( [\w-]+)*)$/ ){
      $prop->{group} = eval{ get_gid( $1 ) };
      $self->fatal( $@ ) if $@;
    }else{
      $self->fatal("Invalid group \"$prop->{group}\"");
    }
  }


  ### figure out the user to run as
  if( ! defined $prop->{user} ){
    $self->log(1,"User Not Defined.  Defaulting to EUID '$>'\n");
    $prop->{user}  = $>;
  }else{
    if( $prop->{user} =~ /^([\w-]+)$/ ){
      $prop->{user} = eval{ get_uid( $1 ) };
      $self->fatal( $@ ) 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 = ();
    foreach my $sock ( @{ $prop->{sock} } ){
      push @chown_files, $sock->NS_unix_path
        if $sock->NS_proto eq 'UNIX';
    }
    if( $prop->{pid_file_unlink} ){
      push @chown_files, $prop->{pid_file};
    }
    if( $prop->{lock_file_unlink} ){
      push @chown_files, $prop->{lock_file};
    }
    if( $prop->{chown_log_file} ){
      delete $prop->{chown_log_file};
      push @chown_files, $prop->{log_file};
    }
    my $uid = $prop->{user};
    my $gid = (split(/\ /,$prop->{group}))[0];
    foreach my $file (@chown_files){
      chown($uid,$gid,$file)
        or $self->fatal("Couldn't chown \"$file\" [$!]\n");
    }
  }


  ### perform the chroot operation
  if( defined $prop->{chroot} ){
    if( ! -d $prop->{chroot} ){
      $self->fatal("Specified chroot \"$prop->{chroot}\" doesn't exist.\n");
    }else{
      $self->log(2,"Chrooting to $prop->{chroot}\n");
      chroot( $prop->{chroot} )
        or $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( $@ );
    } elsif( $< == 0){
      $self->log(2,"NOTICE: Effective UID changed, but Real UID is 0: $@");
    }else{
      $self->log(2,$@);
    }
  }

  ### record number of request
  $prop->{requests} = 0;

  ### set some sigs
  $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = sub { $self->server_close; };

  ### most cases, a closed pipe will take care of itself
  $SIG{PIPE} = 'IGNORE';

  ### catch children (mainly for Fork and PreFork but works for any chld)
  $SIG{CHLD} = \&sig_chld;

  ### catch sighup
  $SIG{HUP} = sub { $self->sig_hup; }

}

### routine to avoid zombie children
sub sig_chld {
  1 while (waitpid(-1, POSIX::WNOHANG()) > 0);
  $SIG{CHLD} = \&sig_chld;
}


### user customizable hook
sub pre_loop_hook {}


### receive requests
sub loop {
  my $self = shift;

  while( $self->accept ){

    $self->run_client_connection;

    last if $self->done;

  }
}


### wait for the connection
sub accept {
  my $self = shift;
  my $prop = $self->{server};
  my $sock = undef;
  my $retries = 30;

  ### try awhile to get a defined client handle
  ### normally a good handle should occur every time
  while( $retries-- ){

    ### with more than one port, use select to get the next one
    if( defined $prop->{multi_port} ){

      return 0 if defined $prop->{_HUP};

      ### anything server type specific
      $sock = $self->accept_multi_port;
      next unless $sock; # keep trying for the rest of retries

      return 0 if defined $prop->{_HUP};

      if ($self->can_read_hook($sock)) {
        $retries ++;
        next;
      }

    ### single port is bound - just accept
    }else{

      $sock = $prop->{sock}->[0];

    }

    ### make sure we got a good sock
    if( not defined $sock ){
      $self->fatal("Received a bad sock!");
    }

    ### receive a udp packet
    if( SOCK_DGRAM == $sock->getsockopt(Socket::SOL_SOCKET(),Socket::SO_TYPE()) ){
      $prop->{client}   = $sock;
      $prop->{udp_true} = 1;
      $prop->{udp_peer} = $sock->recv($prop->{udp_data},
                                      $sock->NS_recv_len,
                                      $sock->NS_recv_flags,
                                      );

    ### blocking accept per proto
    }else{
      delete $prop->{udp_true};
      $prop->{client} = $sock->accept();

    }

    ### last one if HUPed
    return 0 if defined $prop->{_HUP};

    ### success
    return 1 if defined $prop->{client};

    $self->log(2,"Accept failed with $retries tries left: $!");

    ### try again in a second
    sleep(1);

  }
  $self->log(1,"Ran out of accept retries!");

  return undef;
}


### server specific hook for multi port applications
### this actually applies to all but INET
sub accept_multi_port {
  my $self = shift;
  my $prop = $self->{server};

  if( not exists $prop->{select} ){
    $self->fatal("No select property during multi_port execution.");
  }

  ### this will block until a client arrives
  my @waiting = $prop->{select}->can_read();

  ### if no sockets, return failure
  return undef unless @waiting;

  ### choose a socket
  return $waiting[ rand(@waiting) ];

}

### this occurs after a socket becomes readible on an accept_multi_port call.
### It is passed $self and the $sock that is readible.  A return value
### of true indicates to not pass the handle on to the process_request method and
### to return to accepting
sub can_read_hook {}


### this occurs after the request has been processed
### this is server type specific (actually applies to all by INET)
sub post_accept {
  my $self = shift;
  my $prop = $self->{server};

  ### keep track of the requests
  $prop->{requests} ++;

  return if $prop->{udp_true}; # no need to do STDIN/STDOUT in UDP

  ### duplicate some handles and flush them
  ### maybe we should save these somewhere - maybe not
  if( defined $prop->{client} ){
    if( ! $prop->{no_client_stdout} ){
      my $fileno= fileno $prop->{client};
      close STDIN;
      close STDOUT;
      if( defined $fileno ){
          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= \*{ $prop->{client} };
          *STDOUT= \*{ $prop->{client} } if ! $prop->{client}->isa('IO::Socket::SSL');
      }
      STDIN->autoflush(1);
      STDOUT->autoflush(1);
      select(STDOUT);
    }
  }else{
    $self->log(1,"Client socket information could not be determined!");
  }

}

### read information about the client connection
sub get_client_info {
  my $self = shift;
  my $prop = $self->{server};
  my $sock = $prop->{client};

  ### handle unix style connections
  if( UNIVERSAL::can($sock,'NS_proto') && $sock->NS_proto eq 'UNIX' ){
    my $path = $sock->NS_unix_path;
    $self->log(3,$self->log_time
               ." CONNECT UNIX Socket: \"$path\"\n");

    return;
  }

  ### read information about this connection
  my $sockname = getsockname( $sock );
  if( $sockname ){
    ($prop->{sockport}, $prop->{sockaddr})
      = Socket::unpack_sockaddr_in( $sockname );
    $prop->{sockaddr} = inet_ntoa( $prop->{sockaddr} );

  }else{
    ### does this only happen from command line?
    $prop->{sockaddr} = '0.0.0.0';
    $prop->{sockhost} = 'inet.test';
    $prop->{sockport} = 0;
  }

  ### try to get some info about the remote host
  my $proto_type = 'TCP';
  if( $prop->{udp_true} ){
    $proto_type = 'UDP';
    ($prop->{peerport} ,$prop->{peeraddr})
      = Socket::sockaddr_in( $prop->{udp_peer} );
  }elsif( $prop->{peername} = getpeername( $sock ) ){
    ($prop->{peerport}, $prop->{peeraddr})
      = Socket::unpack_sockaddr_in( $prop->{peername} );
  }

  if( $prop->{peername} || $prop->{udp_true} ){
    $prop->{peeraddr} = inet_ntoa( $prop->{peeraddr} );

    if( defined $prop->{reverse_lookups} ){
      $prop->{peerhost} = gethostbyaddr( inet_aton($prop->{peeraddr}), AF_INET );
    }
    $prop->{peerhost} = '' unless defined $prop->{peerhost};

  }else{
    ### does this only happen from command line?
    $prop->{peeraddr} = '0.0.0.0';
    $prop->{peerhost} = 'inet.test';
    $prop->{peerport} = 0;
  }

  $self->log(3,$self->log_time
             ." CONNECT $proto_type Peer: \"$prop->{peeraddr}:$prop->{peerport}\""
             ." Local: \"$prop->{sockaddr}:$prop->{sockport}\"\n");

}

### user customizable hook
sub post_accept_hook {}


### perform basic allow/deny service
sub allow_deny {
  my $self = shift;
  my $prop = $self->{server};
  my $sock = $prop->{client};

  ### unix sockets are immune to this check
  if( UNIVERSAL::can($sock,'NS_proto') && $sock->NS_proto eq 'UNIX' ){
    return 1;
  }

  ### if no allow or deny parameters are set, allow all
  return 1 if
       $#{ $prop->{allow} } == -1
    && $#{ $prop->{deny} }  == -1
    && $#{ $prop->{cidr_allow} } == -1
    && $#{ $prop->{cidr_deny} }  == -1;

  ### if the addr or host matches a deny, reject it immediately
  foreach ( @{ $prop->{deny} } ){
    return 0 if $prop->{peerhost} =~ /^$_$/ && defined($prop->{reverse_lookups});
    return 0 if $prop->{peeraddr} =~ /^$_$/;
  }
  if ($#{ $prop->{cidr_deny} } != -1) {
    require Net::CIDR;
    return 0 if Net::CIDR::cidrlookup($prop->{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->{peerhost} =~ /^$_$/ && defined($prop->{reverse_lookups});
    return 1 if $prop->{peeraddr} =~ /^$_$/;
  }
  if ($#{ $prop->{cidr_allow} } != -1) {
    require Net::CIDR;
    return 1 if Net::CIDR::cidrlookup($prop->{peeraddr}, @{ $prop->{cidr_allow} });
  }

  return 0;
}


### user customizable hook
### if this hook returns 1 the request is processed
### if this hook returns 0 the request is denied
sub allow_deny_hook { 1 }


### user customizable hook
sub request_denied_hook {}


### this is the main method to override
### this is where most of the work will occur
### A sample server is shown below.
sub process_request {
  my $self = shift;
  my $prop = $self->{server};

  ### handle udp packets (udp echo server)
  if( $prop->{udp_true} ){
    if( $prop->{udp_data} =~ /dump/ ){
      require Data::Dumper;
      $prop->{client}->send( Data::Dumper::Dumper( $self ) , 0);
    }else{
      $prop->{client}->send("You said \"$prop->{udp_data}\"", 0 );
    }
    return;
  }


  ### handle tcp connections (tcp echo server)
  print "Welcome to \"".ref($self)."\" ($$)\r\n";

  ### eval block needed to prevent DoS by using timeout
  my $timeout = 30; # give the user 30 seconds to type a line
  my $previous_alarm = alarm($timeout);
  eval {

    local $SIG{ALRM} = sub { die "Timed Out!\n" };

    while( <STDIN> ){

      s/\r?\n$//;

      print ref($self),":$$: You said \"$_\"\r\n";
      $self->log(5,$_); # very verbose log

      if( /get (\w+)/ ){
        print "$1: $self->{server}->{$1}\r\n";
      }

      if( /dump/ ){
        require Data::Dumper;
        print Data::Dumper::Dumper( $self );
      }

      if( /quit/ ){ last }

      if( /exit/ ){ $self->server_close }

      alarm($timeout);
    }

  };
  alarm($previous_alarm);


  if ($@ eq "Timed Out!\n") {
    print STDOUT "Timed Out.\r\n";
    return;
  }

}


### user customizable hook
sub post_process_request_hook {}


### this is server type specific functions after the process
sub post_process_request {
  my $self = shift;
  my $prop = $self->{server};

  ### don't do anything for udp
  return if $prop->{udp_true};

  ### close the client socket handle
  if( ! $prop->{no_client_stdout} ){
    # close handles - but leave fd's around to prevent spurious messages (Rob Mueller)
    #close STDIN;
    #close STDOUT;
    open(STDIN,  '</dev/null') || die "Can't read /dev/null  [$!]";
    open(STDOUT, '>/dev/null') || die "Can't write /dev/null [$!]";
  }
  close($prop->{client});

}


### determine if I am done with a request
### in the base type, we are never done until a SIG occurs
sub done {
  my $self = shift;
  $self->{server}->{done} = shift if @_;
  return $self->{server}->{done};
}


### fork off a child process to handle dequeuing
sub run_dequeue {
  my $self = shift;
  my $pid  = fork;

  ### trouble
  if( not defined $pid ){
    $self->fatal("Bad fork [$!]");

  ### parent
  }elsif( $pid ){
    $self->{server}->{children}->{$pid}->{status} = 'dequeue';

  ### child
  }else{
    $self->dequeue();
    exit;
  }
}

### sub process which could be implemented to
### perform tasks such as clearing a mail queue.
### currently only supported in PreFork
sub dequeue {}


### user customizable hook
sub pre_server_close_hook {}

### this happens when the server reaches the end
sub server_close{
  my $self = shift;
  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 non standard 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;
  }

  ### allow for customizable closing
  $self->pre_server_close_hook;

  $self->log(2,$self->log_time . " Server closing!");

  if (defined $prop->{_HUP} && $prop->{leave_children_open_on_hup}) {
      $self->hup_children;

  } else {
      ### shut down children if any
      if( defined $prop->{children} ){
          $self->close_children();
      }

      ### allow for additional cleanup phase
      $self->post_child_cleanup_hook();
  }

  ### remove files
  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}
      && defined $prop->{pid_file_unlink} ){
    unlink($prop->{pid_file}) || $self->log(1, "Couldn't unlink \"$prop->{pid_file}\" [$!]");
  }

  ### HUP process
  if( defined $prop->{_HUP} ){

    $self->restart_close_hook();

    $self->hup_server; # execs at the end
  }

  ### we don't need the ports - close everything down
  $self->shutdown_sockets;

  ### all done - exit
  $self->server_exit;
}

### called at end once the server has exited
sub server_exit { exit }

### allow for fully shutting down the bound sockets
sub shutdown_sockets {
  my $self = shift;
  my $prop = $self->{server};

  ### unlink remaining socket files (if any)
  foreach my $sock ( @{ $prop->{sock} } ){
    $sock->shutdown(2); # close sockets - nobody should be reading/writing still

    unlink $sock->NS_unix_path
      if $sock->NS_proto eq 'UNIX';
  }

  ### delete the sock objects
  $prop->{sock} = [];

  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 2, $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 defined $prop->{children} && scalar keys %{ $prop->{children} };

  foreach my $pid (keys %{ $prop->{children} }) {
    ### if it is killable, kill it
    if( ! defined($pid) || kill(15,$pid) || ! kill(0,$pid) ){
      $self->delete_child( $pid );
    }

  }

  ### need to wait off the children
  ### eventually this should probably use &check_sigs
  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 during HUP on prefork server\n");

  foreach my $pid (keys %{ $prop->{children} }) {
      kill(1,$pid); # try to hup it
  }
}

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};

  ### prepare for exec
  my $i  = 0;
  my @fd = ();
  $prop->{_HUP} = [];
  foreach my $sock ( @{ $prop->{sock} } ){

    ### duplicate the sock
    my $fd = POSIX::dup($sock->fileno)
      or $self->fatal("Can't dup socket [$!]");

    ### hold on to the socket copy until exec
    $prop->{_HUP}->[$i] = IO::Socket::INET->new;
    $prop->{_HUP}->[$i]->fdopen($fd, 'w')
      or $self->fatal("Can't open to file descriptor [$!]");

    ### turn off the FD_CLOEXEC bit to allow reuse on exec
    $prop->{_HUP}->[$i]->fcntl( Fcntl::F_SETFD(), my $flags = "" );

    ### save host,port,proto, and file descriptor
    push @fd, $fd .'|'. $sock->hup_string;

    ### remove anything that may be blocking
    $sock->close();

    $i++;
  }

  ### remove any blocking obstacle
  if( defined $prop->{select} ){
    delete $prop->{select};
  }

  $ENV{BOUND_SOCKETS} = join("\n", @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} });
  }
}

### restart the server using prebound sockets
sub hup_server {
  my $self = shift;

  $self->log(0,$self->log_time()." HUP'ing server");

  delete $ENV{$_} for $self->hup_delete_env_keys;

  exec @{ $self->commandline };
}

sub hup_delete_env_keys { return qw(PATH) }

### this hook occurs if a server has been HUP'ed
### it occurs just before opening to the fileno's
sub restart_open_hook {}

### this hook occurs if a server has been HUP'ed
### it occurs just before exec'ing the server
sub restart_close_hook {}

###----------------------------------------------------------###

### what to do when all else fails
sub fatal {
  my $self = shift;
  my $error = shift;
  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;
}


### user customizable hook
sub fatal_hook {}

###----------------------------------------------------------###

### how internal levels map to syslog levels
$Net::Server::syslog_map = {0 => 'err',
                            1 => 'warning',
                            2 => 'notice',
                            3 => 'info',
                            4 => 'debug'};

### record output
sub log {
  my ($self, $level, $msg, @therest) = @_;
  my $prop = $self->{server};

  return if ! $prop->{log_level};

  ### log only to syslog if setup to do syslog
  if (defined($prop->{log_file}) && $prop->{log_file} eq 'Sys::Syslog') {
    if ($level =~ /^\d+$/) {
        return if $level > $prop->{log_level};
        $level = $Net::Server::syslog_map->{$level} || $level;
    }

    if (@therest) { # if more parameters are passed, we must assume that the first is a format string
      Sys::Syslog::syslog($level, $msg, @therest);
    } else {
      Sys::Syslog::syslog($level, '%s', $msg);
    }
    return;
  } else {
    return if $level !~ /^\d+$/ || $level > $prop->{log_level};
  }

  $self->write_to_log_hook($level, $msg);
}


### standard log routine, this could very easily be
### overridden with a syslog call
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);
  }

}


### default time format
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);
}

###----------------------------------------------------------###

### set up default structure
sub options {
  my $self = shift;
  my $prop = $self->{server};
  my $ref  = shift;

  foreach ( qw(port host proto 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 pid_file background setsid
               listen reverse_lookups
               syslog_logsock syslog_ident
               syslog_logopt syslog_facility
               no_close_by_child
               no_client_stdout
               leave_children_open_on_hup
               ) ){
    $ref->{$_} = \$prop->{$_};
  }

}


### routine for parsing commandline, module, and conf file
### possibly should use Getopt::Long but this
### method has the benefit of leaving unused arguments in @ARGV
sub process_args {
  my $self = shift;
  my $ref  = shift;
  my $template = shift; # allow for custom passed in template

  ### if no template is passed, obtain our own
  if (! $template || ! ref($template)) {
    $template = {};
    $self->options( $template );
  }

  ### 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 < @$ref ; $i++) {

    if ($ref->[$i] =~ /^(?:--)?(\w+)([=\ ](\S+))?$/
        && exists $template->{$1}) {
      my ($key,$val) = ($1,$3);
      splice( @$ref, $i, 1 );
      if (not defined($val)) {
        if ($i > $#$ref
            || ($ref->[$i] && $ref->[$i] =~ /^--\w+/)) {
          $val = 1; # allow for options such as --setsid
        } else {
          $val = splice( @$ref, $i, 1 );
          if (ref $val) {
            die "Found an invalid configuration value for \"$key\" ($val)" if ref($val) ne 'ARRAY';
            $val = $val->[0] if @$val == 1;
          }
        }
      }
      $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) ? @$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;
        ${ $template->{$key} } = $val;
      }
    }

  }

}


### routine for loading conf file parameters
### cache the args temporarily to handle multiple calls
sub process_conf {
  my $self = shift;
  my $file = shift;
  my $template = shift;
  $template = undef if ! $template || ! ref($template);
  my @args = ();

  if( ! $self->{server}->{conf_file_args} ){
    $file = ($file =~ m|^([\w\.\-\/\\\:]+)$|)
      ? $1 : $self->fatal("Unsecure filename \"$file\"");

    if( not open(_CONF,"<$file") ){
      if (! $ENV{BOUND_SOCKETS}) {
        warn "Couldn't open conf \"$file\" [$!]\n";
      }
      $self->fatal("Couldn't open conf \"$file\" [$!]");
    }

    while(<_CONF>){
      push( @args, "$1=$2") if m/^\s*((?:--)?\w+)(?:\s*[=:]\s*|\s+)(\S+)/;
    }

    close(_CONF);

    $self->{server}->{conf_file_args} = \@args;
  }

  $self->process_args( $self->{server}->{conf_file_args}, $template );
}

### remove a child from the children hash. Not to be called by user.
### if UNIX sockets are in use the socket is removed from the select object.
sub delete_child {
  my $self = shift;
  my $pid  = shift;
  my $prop = $self->{server};

  ### don't remove children that don't belong to me (Christian Mock, Luca Filipozzi)
  return unless 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;
    }
  }

  delete $prop->{children}->{$pid};
}

###----------------------------------------------------------###
sub get_property {
  my $self = shift;
  my $key  = shift;
  $self->{server} = {} unless defined $self->{server};
  return $self->{server}->{$key} if exists $self->{server}->{$key};
  return undef;
}

sub set_property {
  my $self = shift;
  my $key  = shift;
  $self->{server} = {} unless defined $self->{server};
  $self->{server}->{$key}  = shift;
}

###----------------------------------------------------------------###

1;

### The documentation is in Net/Server.pod
