Responding to my own post... I found the problem by using tethereal to capture the exchange between my server and the Active Directory server.
I was under the mistaken impression that because the web2py LDAP authentication does not require binding information that the AD server had been configured to accept anonymous binds. This is not the case! The web2py LDAP module does something rather clever: Apparently if there is no bind information in the configuration, the module will use the user-entered credentials to first bind to the server before performing a query (using the dn fields to create an email address for the binding). So from a configuration standpoint, it looks like no bind user/password information is needed, when in fact it is created on the fly. Apache lacks such cleverness, and I had to specify a user and password before it would authenticate against the AD server. Hopefully this will be useful information should someone else get as confused as I was! Nick