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]