close $hash{ $fno }; delete $hash{ $fno }; #### #!/usr/bin/perl -w use strict; use warnings; use IO::Socket; use threads; use Thread::Queue; use POSIX qw(setsid); use Proc::PID::File; use Log::Dispatch; use Log::Dispatch::File; use Date::Format; use File::Spec; use FindBin qw($Bin); sub dienice ($); ### ### Change default configuration here if needed ### # Our server port (Any number between 1024 and 65535) my $port = 9999; my $timeout = 60; # Number of listener threads to spawn # (2 or 3 threads are sufficient to handle 100 concurrent connections since our duty cycle is a few milliseconds) my $listeners = 3; # Want to log connections? 1 = yes, 0 = no my $log_connections = 1; # Want to log if script is executed when it's already running? # (You might want to set it to 0 if you run periodically from cron - too redundant) my $log_is_running = 1; # # Do not change the following unless you know what you're doing!!! # my $content = ''; my $NULLBYTE = pack( 'c', 0 ); ### ### Detect our base directory where we create the log and pid files ### our $BASE_DIR = $Bin; # Get script name, chop off any preceding path it was called with and chop off its extension (ex: 'some/dir/script.pl' becomes 'script') our $ME = $0; $ME =~ s|.*/||; $ME =~ s|\..*||; our $LOG_FILE = "$ME.log"; ### ### Setup a logging agent ### my $log = new Log::Dispatch( callbacks => sub { my %h=@_; return Date::Format::time2str('%B %e %T', time)." $ME\[$$]: ".$h{message}."\n"; } ); $log->add( Log::Dispatch::File->new( name => 'file1', min_level => 'warning', mode => 'append', filename => File::Spec->catfile($BASE_DIR, $LOG_FILE), ) ); ### ### Fork and background daemon process ### startDaemon(); $log->warning("Logging Started"); ### ### Setup signal handlers to give us time to cleanup (and log) before shutting down ### my $running = 1; $SIG{HUP} = sub { $log->warning("Caught SIGHUP: exiting gracefully"); $running = 0; }; $SIG{INT} = sub { $log->warning("Caught SIGINT: exiting gracefully"); $running = 0; }; $SIG{QUIT} = sub { $log->warning("Caught SIGQUIT: exiting gracefully"); $running = 0; }; $SIG{TERM} = sub { $log->warning("Caught SIGTERM: exiting gracefully"); $running = 0; }; my $Q = Thread::Queue->new; my $Qclean = Thread::Queue->new; ### ### As long as the daemon is running, listen for and handle received connections ### while ($running) { ### ### Spawn our listener threads and detach them since we don't want return values and don't to wait for them to finish ### "detach" also allows automatic cleanup of the thread and recycles its memory ### for (1..$listeners) { threads->create(\&handleConnection)->detach; } ### ### BEGIN LISTENING ### my $sock = new IO::Socket::INET( LocalPort => $port, Proto => 'tcp', Listen => SOMAXCONN, ReuseAddr => 1); $sock or dienice("Socket error :$!"); $log->warning("Listening on port $port"); ### ### Handle connections ### my %sockets = (); while ( my ($new_sock, $clientAddr) = $sock->accept() ) { my ( $clientPort, $clientIp ) = sockaddr_in( $clientAddr ); # put the connection on the queue for a reader thread my $fno = fileno($new_sock); $sockets{ $fno } = $new_sock; $Q->enqueue( "$fno\0$clientIp" ); while( $Qclean->pending ) { my $fileno = $Qclean->dequeue(); close $sockets{ $fileno }; delete $sockets{ $fileno }; } } } ### ### Mark a clean exit in the log ### $log->warning("Logging Stopped"); ### ### startDaemon ### sub startDaemon { # fork a child process and have the parent process exit to disassociate the process from controlling terminal or login shell defined(my $pid = fork) or dienice("Can't fork: $!"); exit if $pid; # setsid turns the process into a session and group leader to ensure our process doesn't have a controlling terminal POSIX::setsid() or dienice("Can't start a new session: $!"); # Get a PID file - or exit without error in case we're running periodically from cron # if ( Proc::PID::File->running(dir => "$BASE_DIR", name => "$ME", verify => "1") ) # { # $log->warning("Daemon Already Running!") if ($log_is_running); # exit(0); # } } ### ### handleConnection ### sub handleConnection { my $tid = threads->tid(); $log->warning("Thread ($tid) started"); while ( my $work = $Q->dequeue() ) { my ( $fno, $clientIp ) = split ( chr(0), $work ); my $clientIpStr = inet_ntoa( $clientIp ); local $/ = $NULLBYTE; open my $socket, '+<&='.$fno or dienice("Thread ($tid) could not reopen socket: $!"); my $request; if ( defined ( $request = <$socket> ) ) { chomp $request; if ( $request eq '' ) { $log->warning("($tid) : XML Policy file request from: $clientIpStr") if ($log_connections); print $socket $content.$NULLBYTE; } elsif ( $request =~ /getmyip<\/request>/ ) { $log->warning("($tid) : XML IP request from: $clientIpStr") if ($log_connections); print $socket "$clientIpStr".$NULLBYTE; } else { $log->warning("($tid) : Ignoring unrecognized request from: $clientIpStr") if ($log_connections); } } else { $log->warning("($tid) : Ignoring NULL connection from: $clientIpStr") if ($log_connections); } shutdown $socket, 2; close $socket; $Qclean->enqueue( $fno ); } return 1; } ### ### dienice ### sub dienice ($) { my ($package, $filename, $line) = caller; # write die messages to the log before die'ing $log->critical("$_[0] at line $line in $filename"); die $_[0]; }