stas        2004/07/04 01:35:23

  Modified:    src/docs/2.0/user/handlers protocols.pod
  Log:
  backport the original Command Server protocol example written by doug
  
  Revision  Changes    Path
  1.26      +274 -0    modperl-docs/src/docs/2.0/user/handlers/protocols.pod
  
  Index: protocols.pod
  ===================================================================
  RCS file: /home/cvs/modperl-docs/src/docs/2.0/user/handlers/protocols.pod,v
  retrieving revision 1.25
  retrieving revision 1.26
  diff -u -u -r1.25 -r1.26
  --- protocols.pod     2 Jul 2004 23:18:30 -0000       1.25
  +++ protocols.pod     4 Jul 2004 08:35:23 -0000       1.26
  @@ -546,6 +546,280 @@
   
   
   
  +=head1 Examples
  +
  +Following are some practical examples.
  +
  +META: If you have written an interesting, but not too complicated
  +module, which others can learn from, please submit a pod to the
  +L<mailing list|maillist::modperl> so we can include it here.
  +
  +
  +
  +
  +
  +=head2 Command Server
  +
  +The C<MyApache::CommandServer> example is based on the example in the
  +"TCP Servers with IO::Socket" section of the I<perlipc> manpage.  Of
  +course, we don't need C<IO::Socket> since Apache takes care of those
  +details for us.  The rest of that example can still be used to
  +illustrate implementing a simple text protocol.  In this case, one
  +where a command is sent by the client to be executed on the server
  +side, with results sent back to the client.
  +
  +The C<MyApache::CommandServer> handler will support four commands:
  +C<motd>, C<date>, C<who> and C<quit>.  These are probably not commands
  +which can be exploited, but should we add such commands, we'll want to
  +limit access based on ip address/hostname, authentication and
  +authorization.  Protocol handlers need to take care of these tasks
  +themselves, since we bypass the HTTP protocol handler.
  +
  +Here is the whole module:
  +
  +  package MyApache::CommandServer;
  +  
  +  use strict;
  +  use warnings FATAL => 'all';
  +  
  +  use Apache::Connection ();
  +  use Apache::RequestUtil ();
  +  use Apache::HookRun ();
  +  use Apache::Access ();
  +  use APR::Socket ();
  +  
  +  use Apache::Const -compile => qw(OK DONE DECLINED);
  +  
  +  my @cmds = qw(motd date who quit);
  +  my %commands = map { $_, \&{$_} } @cmds;
  +  
  +  sub handler {
  +      my $c = shift;
  +      my $socket = $c->client_socket;
  +  
  +      if ((my $rc = login($c)) != Apache::OK) {
  +          $socket->send("Access Denied\n");
  +          return $rc;
  +      }
  +  
  +      $socket->send("Welcome to " . __PACKAGE__ .
  +                    "\nAvailable commands: @cmds\n");
  +  
  +      while (1) {
  +          my $cmd;
  +          next unless $cmd = getline($socket);
  +  
  +          if (my $sub = $commands{$cmd}) {
  +              last unless $sub->($socket) == Apache::OK;
  +          }
  +          else {
  +              $socket->send("Commands: @cmds\n");
  +          }
  +      }
  +  
  +      return Apache::OK;
  +  }
  +  
  +  sub login {
  +      my $c = shift;
  +  
  +      my $r = Apache::RequestRec->new($c);
  +      $r->location_merge(__PACKAGE__);
  +  
  +      for my $method (qw(run_access_checker run_check_user_id
  +                         run_auth_checker)) {
  +          my $rc = $r->$method();
  +  
  +          if ($rc != Apache::OK and $rc != Apache::DECLINED) {
  +              return $rc;
  +          }
  +  
  +          last unless $r->some_auth_required;
  +  
  +          unless ($r->user) {
  +              my $socket = $c->client_socket;
  +              my $username = prompt($socket, "Login");
  +              my $password = prompt($socket, "Password");
  +  
  +              $r->set_basic_credentials($username, $password);
  +          }
  +      }
  +  
  +      return Apache::OK;
  +  }
  +  
  +  sub getline {
  +      my $socket = shift;
  +  
  +      my $line;
  +      $socket->recv($line, 1024);
  +      return unless $line;
  +      $line =~ s/[\r\n]*$//;
  +  
  +      return $line;
  +  }
  +  
  +  sub prompt {
  +      my($socket, $msg) = @_;
  +  
  +      $socket->send("$msg: ");
  +      getline($socket);
  +  }
  +  
  +  sub motd {
  +      my $socket = shift;
  +  
  +      open my $fh, '/etc/motd' or return;
  +      local $/;
  +      $socket->send(scalar <$fh>);
  +      close $fh;
  +  
  +      return Apache::OK;
  +  }
  +  
  +  sub date {
  +      my $socket = shift;
  +  
  +      $socket->send(scalar(localtime) . "\n");
  +  
  +      return Apache::OK;
  +  }
  +  
  +  sub who {
  +      my $socket = shift;
  +  
  +      # make -T happy
  +      local $ENV{PATH} = "/bin:/usr/bin";
  +  
  +      $socket->send(scalar `who`);
  +  
  +      return Apache::OK;
  +  }
  +  
  +  sub quit { Apache::DONE }
  +  
  +  1;
  +  __END__
  +
  +
  +Next, let's explain what this module does in details.
  +
  +As with all C<PerlProcessConnectionHandlers>, we are passed an
  +C<Apache::Connection> object as the first argument.  Again, we will be
  +directly accessing the client socket via the I<client_socket> method.
  +The I<login> subroutine is called to check if access by this client
  +should be allowed.  This routine makes up for what we lost with the
  +core HTTP protocol handler bypassed.  First we call the
  +C<Apache::RequestRec> I<new> method, which returns a I<request_rec>
  +object, just like that which is passed at request time to L<HTTP
  +protocol|docs::2.0::user::handlers::http> C<Perl*Handlers> and
  +returned by the subrequest API methods, I<lookup_uri> and
  +I<lookup_file>.  However, this "fake request" does not run handlers
  +for any of the phases, it simply returns an object which we can use to
  +do that ourselves.  The C<location_merge()> method is passed the
  +C<location> for this request, it will look up the
  +C<E<lt>LocationE<gt>> section that matches the given name and merge it
  +with the default server configuration.  For example, should we only
  +wish to allow access to this server from certain locations:
  +
  +  <Location MyApache::CommandServer>
  +      deny from all
  +      allow from 10.*
  +  </Location>
  +
  +The C<location_merge()> method only looks up and merges the
  +configuration, we still need to apply it.  This is done in I<for>
  +loop, iterating over three methods: C<run_access_checker()>,
  +C<run_check_user_id()> and C<run_auth_checker()>.  These methods will
  +call directly into the Apache functions that invoke module handlers
  +for these phases and will return an integer status code, such as
  +C<Apache::OK>, C<Apache::DECLINED> or C<Apache::FORBIDDEN>.  If
  +I<run_access_check> returns something other than C<Apache::OK> or
  +C<Apache::DECLINED>, that status will be propagated up to the handler
  +routine and then back up to Apache.  Otherwise, the access check
  +passed and the loop will break unless C<some_auth_required()> returns
  +true.  This would be false given the previous configuration example,
  +but would be true in the presence of a C<require> directive, such as:
  +
  +  <Location MyApache::CommandServer>
  +      deny from all
  +      allow from 10.*
  +      require user dougm
  +  </Location>
  +
  +Given this configuration, C<some_auth_required()> will return true.
  +The C<user()> method is then called, which will return false if we
  +have not yet authenticated.  A C<prompt()> utility is called to read
  +the username and password, which are then injected into the
  +C<headers_in()> table using the C<set_basic_credentials()> method.
  +The I<Authenticate> field in this table is set to a I<base64> encoded
  +value of the username:password pair, exactly the same format a browser
  +would send for I<Basic authentication>.  Next time through the loop
  +I<run_check_user_id> is called, which will in turn invoke any
  +authentication handlers, such as I<mod_auth>.  When I<mod_auth> calls
  +the C<ap_get_basic_auth_pw()> API function (as all C<Basic> auth
  +modules do), it will get back the username and password we injected.
  +If we fail authentication a C<401> status code is returned which we
  +propagate up.  Otherwise, authorization handlers are run via
  +C<run_auth_checker()>.  Authorization handlers normally need the
  +I<user> field of the C<request_rec> for its checks and that field was
  +filled in when I<mod_auth> called C<ap_get_basic_auth_pw()>.
  +
  +Provided login is a success, a welcome message is printed and main
  +request loop entered.  Inside the loop the C<getline()> function
  +returns just one line of data, with newline characters stripped.  If
  +the string sent by the client is in our command table, the command is
  +then invoked, otherwise a usage message is sent.  If the command does
  +not return C<Apache::OK>, we break out of the loop.
  +
  +Let's use this configuration:
  +
  +  Listen 8085
  +  <VirtualHost _default_:8085>
  +      PerlProcessConnectionHandler MyApache::CommandServer
  +  
  +      <Location MyApache::CommandServer>
  +          allow from 127.0.0.1
  +          require user dougm
  +          satisfy any
  +          AuthUserFile /tmp/basic-auth
  +      </Location>
  +  </VirtualHost>
  +
  +The auth file can be created with the help of C<htpasswd> utility
  +coming bundled with the Apache server. For example to create a file
  +F</tmp/basic-auth> and add a password entry for user I<dougm> with
  +password I<foobar> we do:
  +
  +  % htpasswd -bc /tmp/basic-auth dougm foobar
  +
  +Now we are ready to try the command server:
  +
  +  % telnet localhost 8085
  +  Trying 127.0.0.1...
  +  Connected to localhost (127.0.0.1).
  +  Escape character is '^]'.
  +  Login: dougm
  +  Password: foobar
  +  Welcome to MyApache::CommandServer
  +  Available commands: motd date who quit
  +  motd
  +  Have a lot of fun...
  +  date
  +  Mon Mar 12 19:20:10 PST 2001
  +  who
  +  dougm    tty1     Mar 12 00:49
  +  dougm    pts/0    Mar 12 11:23
  +  dougm    pts/1    Mar 12 14:08
  +  dougm    pts/2    Mar 12 17:09
  +  quit
  +  Connection closed by foreign host.
  +
  +
  +
  +
  +
  +
   
   
   
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to