Hi, Ben (and modperl folken)

I've pieced together your config file from the bit's you've sent me and posted; I deleted the ip numbers, but here is what I think it basically looks like:

# Doesn't work
<Location /server-status>
   SetHandler server-status
   Order deny,allow
   Deny from all
   Allow from xx.xx.xx.xx
   Allow from 127.0.0.1
</Location>

# Doesn't work either
<Location /server-info>
   SetHandler server-info
   Order deny,allow
   Deny from all
   Allow from xx.xx.xx.xx
   Allow from 127.0.0.1
</Location>

# This one also doesn't work
<Location /perl-status>
       SetHandler perl-script
       PerlHandler Apache::Status
</Location>

# But this works fine
<Location />
       SetHandler perl-script
       PerlHandler Apache::putInJava
</Location>

# As does this
<Location /howdy>
       SetHandler perl-script
       PerlHandler Apache::HelloWorld
</Location>

# And this
<Location /secret>
       AuthName  " -- Use your regular name in the User Name field -- "
       AuthType Basic
       PerlAuthenHandler Apache::AuthPwd
       require valid-user
</Location>

-------------------------------

OK, the pattern there is pretty clear: everything start with <Location /> works

But what's perhaps confusing is that the access control in the first two Location stanzas is actually working, even though the SetHandler isn't.

So time for a little Apache-fu:

When Apache tries to figure out what applies to which URL, it does something called configuration merging. The basic algorithm is pretty well described in the documentation (http://httpd.apache.org/docs-2.0/sections.html), but the devil is in the details so it's worth looking at this in detail, using the above as an example.

For now, we'll leave out <Directory> sections, <Files> sections and all the other wonderful stuff; the principles are roughly the same (but see below). I'll just note that Location sections happen after all of that. Each of these things: <Foo > ... </Foo> is a "container".

The basic idea is that Apache starts with some configuration, and then goes through containers one at a time, in some order. For each container that applies (i.e. matches the current request), the configuration is merged with the configuration in the container, producing a new merged configuration. This continues until we get to the end of the containers, and the final merged configuration is the result.

In pseudo-code (sorry, my perl is rusty):

  config = default
  foreach container in Containers do
    if request matches container then
      config = merge(config, container)
    end
  end

Now, the important thing about the merge function is that it is implemented by modules, each
one acting independently of each other one, without any knowledge of each other's directives.


In the above example, we have four modules represented:

mod_mime: implements SetHandler
mod_perl: implements PerlHandler
mod_access: implements Allow, Deny and Order
mod_auth_basic: implements AuthType, AuthName and Require

So, let's consider what happens with the request /server-status.
This matches two containers: /server-status and /, in that order

mod_mime gets "SetHandler server-status" from /server-status, and then merges it with "SetHandler perl-script" from /. The merge in this case is "second one wins" (there is only one handler slot), so the end result is "SetHandler perl-script"

mod_perl only sees the / container, because it has no directives in /server-status. So it ends up with "PerlHandler Apache::putInJava"

mod_access only sees the /server-status container, because it has no directives in /. So it ends up with "Order deny,allow"; "Deny from all"; "Allow from xx.xx.xx.xx"; and "Allow from 127.0.0.1"

mod_auth_basic doesn't get involved at all. It has no directives in either of these containers.

So the final configuration for /server-status is:

SetHandler perl-script
PerlHandler Apache::putInJava
Order deny,allow
Deny from all
Allow from xx.xx.xx.xx
Allow from 127.0.0.1

As we might expect (having been through this whole exercise), the mod_access directives are checked, resulting in FORBIDDEN (403) if not accessed from one of the valid ip numbers; if that passes, then the request is given to the perl-script handler, which is part of mod_perl; it then checks the PerlHandler configuration, and sends the request to Apache::PutInJava. This handler wants a file, but DOCUMENTROOT/server-status doesn't exist. When it returns DECLINED, Apache is left with nothing to do other than to return it's own NOT-FOUND; after all, there is no file there.

If we go through this exercise again with the /perl-status container, it is easy to see that mod_mime will end up with "SetHandler perl-script"; after all, both /perl-status and / have the same directive; mod_perl will end up again with "PerlHandler Apache::PutInJava". And the same NOT-FOUND error will result, this time without any access check.

The /howdy section is after the / section, so the order of merging is different. Now, mod_mime is still merging two identical directives, but mod_perl sees "PerlHandler Apache::PutInJava" first and "PerlHandler Apache::HelloWorld" second. Again, last one wins, so this time we end up with:

SetHandler perl-script
PerlHandler Apache::HelloWorld

which was actually the desired outcome (maybe).

This e-mail is long enough without considering the other sections in that example, but it is worth trying each of them on paper to see what the results are. The most interesting ones are the first two, though, because it shows how the Apache configuration merging algorithm can take some directives from one container and some other directives from a different container.

Obviously, this requires some careful thought to get right, but a good rule of thumb is: put your Location sections in order in the file, from least-specific to most-specific. Then things will probably work out the way you would expect them too.

Interestingly, Apache does a shortest-to-longest sort on <Directory> sections (excluding regular expression DirectoryMatch or Directory ~ sections.) All other sections are merged in the order they show up in the configuration file, so it's your responsibility (or pleasure) to get it right.

Hope this treatise helps someone.

Rici

PS: I don't know what putInJava does, but I suppose it modifies the web page in some way. This strikes me as more of an output filter than a handler; I am led to believe that mod_perl implemented that even in mod_perl 1. With Apache 2.0, anyone can write output filters, thankfully. Implementing it as a filter rather than a handler would side-step the "only one handler" rule, which might help, particularly if you want it to apply to proxied content as well. (Probably everyone on this list is more qualified than I am to help you put together an output filter in mod_perl.)



On 6-Sep-04, at 5:31 PM, Ben Hopkins wrote:

William McKee wrote:

On Sat, Sep 04, 2004 at 12:56:26PM -0700, Ben Hopkins wrote:

I got FORBIDDEN errors until I put my IP addr (I'm working on a remote server). Then I got 404s.


The directives look similar to mine. Is there no message in the Apache error logs that gives you a hint of what's wrong with your system?

Here's the error log entries:
[Mon Sep 6 15:21:44 2004] [error] [client xx.xx.xx.xx] File does not exist: /usr/local/apache/htdocs/server-status
[Mon Sep 6 15:22:03 2004] [error] [client xx.xx.xx.xx] File does not exist: /usr/local/apache/htdocs/server-info


and the access log:
xx.xx.xx.xx - - [06/Sep/2004:15:21:44 -0700] "GET /server-status HTTP/1.1" 404 284
xx.xx.xx.xx - - [06/Sep/2004:15:22:03 -0700] "GET /server-info HTTP/1.1" 404 282


Notice that the client IP is the same as the one allowed in the httpd.conf snippet in the previous post.

I thought I had it solved when I realized that the PerlHandler for <Location /> was doing this:

unless (-e $f->finfo) {
   return NOT_FOUND
}

and I changed NOT_FOUND to DECLINED.

But that didn't work.


--
Report problems: http://perl.apache.org/bugs/
Mail list info: http://perl.apache.org/maillist/modperl.html
List etiquette: http://perl.apache.org/maillist/email-etiquette.html



Reply via email to