[svn:qpsmtpd] rev 407 - in branches/high_perf: . lib/Danga/DNS lib/Qpsmtpd

[email protected] 26 Apr 2005 02:46:45 -0000
Newsgroups perl.cvs.qpsmtpd
Message-ID <[email protected]>
Author: msergeant
Date: Mon Apr 25 19:46:45 2005
New Revision: 407

Added:
   branches/high_perf/lib/Qpsmtpd/ConfigServer.pm
Modified:
   branches/high_perf/lib/Danga/DNS/Resolver.pm
   branches/high_perf/qpsmtpd
Log:
Added rudimentary configuration server when running in non-forking poll mode


Modified: branches/high_perf/lib/Danga/DNS/Resolver.pm
==============================================================================
--- branches/high_perf/lib/Danga/DNS/Resolver.pm	(original)
+++ branches/high_perf/lib/Danga/DNS/Resolver.pm	Mon Apr 25 19:46:45 2005
@@ -50,6 +50,12 @@
     return $self;
 }
 
+sub pending {
+    my Danga::DNS::Resolver $self = shift;
+    
+    return keys(%{$self->{id_to_asker}});
+}
+
 sub _query {
     my Danga::DNS::Resolver $self = shift;
     my ($asker, $host, $type, $now) = @_;

Added: branches/high_perf/lib/Qpsmtpd/ConfigServer.pm
==============================================================================
--- (empty file)
+++ branches/high_perf/lib/Qpsmtpd/ConfigServer.pm	Mon Apr 25 19:46:45 2005
@@ -0,0 +1,138 @@
+# $Id$
+
+package Qpsmtpd::ConfigServer;
+
+use base ('Danga::Client');
+
+use fields qw(
+    commands
+    _auth
+    _commands
+    _config_cache
+    _connection
+    _transaction
+    _test_mode
+    _extras
+);
+
+sub new {
+    my Qpsmtpd::ConfigServer $self = shift;
+    
+    $self = fields::new($self) unless ref $self;
+    $self->SUPER::new( @_ );
+    $self->{commands} = { help => 1, status => 1, };
+    $self->write("Enter command:\n");
+    return $self;
+}
+
+sub process_line {
+    my $self = shift;
+    my $line = shift || return;
+    if ($::DEBUG > 1) { print "$$:".($self+0)."C($self->{mode}): $line"; }
+    local $SIG{ALRM} = sub {
+        my ($pkg, $file, $line) = caller();
+        die "ALARM: $pkg, $file, $line";
+    };
+    my $prev = alarm(2); # must process a command in < 2 seconds
+    my $resp = eval { $self->_process_line($line) };
+    alarm($prev);
+    if ($@) {
+        print STDERR "Error: $@\n";
+    }
+    return $resp || '';
+}
+
+sub respond {
+    my $self = shift;
+    my (@messages) = @_;
+    while (my $msg = shift @messages) {
+        $self->write("$msg\r\n");
+    }
+    return;
+}
+
+sub fault {
+  my $self = shift;
+  my ($msg) = shift || "program fault - command not performed";
+  print STDERR "$0 [$$]: $msg ($!)\n";
+  return $self->respond("Error - " . $msg, "Enter command:");
+}
+
+sub _process_line {
+    my $self = shift;
+    my $line = shift;
+
+    $line =~ s/\r?\n//;
+    my ($cmd, @params) = split(/ +/, $line);
+    my $meth = lc($cmd);
+    if (my $lookup = $self->{commands}->{$meth} && $self->can($meth)) {
+        my $resp = eval {
+            $lookup->($self, @params);
+        };
+        if ($@) {
+            my $error = $@;
+            chomp($error);
+            $self->log(LOGERROR, "Command Error: $error");
+            return $self->fault("command '$cmd' failed unexpectedly");
+        }
+        return $resp . "\nEnter command:\n";
+    }
+    else {
+        # No such method - i.e. unrecognized command
+        return $self->fault("command '$cmd' unrecognised");
+    }
+}
+
+my %helptext = (
+    all => "Available Commands:\n\nSTATUS\nHELP [CMD]",
+    status => "STATUS - Returns status information about current connections",
+    );
+
+sub help {
+    my $self = shift;
+    my ($subcmd) = @_;
+    
+    $subcmd ||= 'all';
+    $subcmd = lc($subcmd);
+    
+    my $txt = $helptext{$subcmd} || "Unrecognised help option. Try 'help all'";
+    warn "help returning: $txt\n";
+    return $txt . "\n";
+}
+
+sub status {
+    my $self = shift;
+    
+    my $descriptors = Danga::Socket->DescriptorMap;
+    
+    my $current_connections = 0;
+    my $current_dns = 0;
+    foreach my $fd (keys %$descriptors) {
+        my $pob = $descriptors->{$fd};
+        if ($pob->isa("Qpsmtpd::PollServer")) {
+            $current_connections++;
+        }
+        elsif ($pob->isa("Danga::DNS::Resolver")) {
+            $current_dns = $pob->pending;
+        }
+    }
+    
+    return
+"  Current Connections: $current_connections
+  Current DNS Queries: $current_dns";
+}
+
+1;
+__END__
+
+=head1 NAME
+
+Qpsmtpd::ConfigServer - a configuration server for qpsmtpd
+
+=head1 DESCRIPTION
+
+When qpsmtpd runs in multiplex mode it also provides a config server that you
+can connect to. This allows you to view current connection statistics and other
+gumph that you probably don't care about.
+
+=cut
\ No newline at end of file

Modified: branches/high_perf/qpsmtpd
==============================================================================
--- branches/high_perf/qpsmtpd	(original)
+++ branches/high_perf/qpsmtpd	Mon Apr 25 19:46:45 2005
@@ -15,6 +15,7 @@
 use Danga::Socket;
 use Danga::Client;
 use Qpsmtpd::PollServer;
+use Qpsmtpd::ConfigServer;
 use Qpsmtpd::Constants;
 use IO::Socket;
 use Carp;
@@ -31,6 +32,10 @@
 $SIG{'PIPE'} = "IGNORE";  # handled manually
 
 $DEBUG          = 0;
+
+my $CONFIG_PORT      = 20025;
+my $CONFIG_LOCALADDR = '127.0.0.1';
+
 my $PORT        = 2525;
 my $LOCALADDR   = '0.0.0.0';
 my $LineMode    = 0;
@@ -93,6 +98,7 @@
                     $Danga::Socket::HaveKQueue ? "kqueue()" : "poll()");
 
 my $server;
+my $config_server;
 
 # Code for inetd/tcpserver mode
 if ($ENV{REMOTE_HOST} or $ENV{TCPREMOTEHOST}) {
@@ -200,10 +206,22 @@
                                     Blocking  => 0,
                                     Reuse     => 1,
                                     Listen    => 10 )
-                                           or die "Error creating server $LOCALADDR:$PORT : $@\n";
+               or die "Error creating server $LOCALADDR:$PORT : $@\n";
 
     IO::Handle::blocking($server, 0);
     binmode($server, ':raw');
+    
+    $config_server = IO::Socket::INET->new(LocalPort => $CONFIG_PORT,
+                                            LocalAddr => $CONFIG_LOCALADDR,
+                                            Type      => SOCK_STREAM,
+                                            Proto     => IPPROTO_TCP,
+                                            Blocking  => 0,
+                                            Reuse     => 1,
+                                            Listen    => 1 )
+               or die "Error creating server $CONFIG_LOCALADDR:$CONFIG_PORT : $@\n";
+    
+    IO::Handle::blocking($config_server, 0);
+    binmode($config_server, ':raw');
 
     # Drop priviledges
     my (undef, undef, $quid, $qgid) = getpwnam $USER or
@@ -240,7 +258,9 @@
         }
         ::log(LOGDEBUG, "Listening on $PORT with single process $POLL" .
             ($LineMode ? " (forking server)" : ""));
-        Qpsmtpd::PollServer->OtherFds(fileno($server) => \&accept_handler);
+        Qpsmtpd::PollServer->OtherFds(fileno($server) => \&accept_handler,
+                                      fileno($config_server) => \&config_handler,
+                                      );
         while (1) {
             Qpsmtpd::PollServer->EventLoop();
         }
@@ -249,6 +269,24 @@
 
 }
 
+sub config_handler {
+    my $csock = $config_server->accept();
+    if (!$csock) {
+        warn("accept failed on config server: $!");
+        return;
+    }
+    binmode($csock, ':raw');
+    
+    printf("Config server connection\n") if $DEBUG;
+    
+    IO::Handle::blocking($csock, 0);
+    setsockopt($csock, IPPROTO_TCP, TCP_NODELAY, pack("l", 1)) or die;
+    
+    my $client = Qpsmtpd::ConfigServer->new($csock);
+    $client->watch_read(1);
+    return;
+}
+
 # Accept a new connection
 sub accept_handler {
     my $running = scalar keys %childstatus;
@@ -260,8 +298,8 @@
     my $csock = $server->accept();
     if (!$csock) {
         # warn("accept() failed: $!");
+        return;
     }
-    return unless $csock;
     binmode($csock, ':raw');
 
     printf("Listen child making a Qpsmtpd::PollServer for %d.\n", fileno($csock))