| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Apache2::AuthZLDAP; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 1 |  |  | 1 |  | 24694 | use warnings; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 81 |  | 
| 4 | 1 |  |  | 1 |  | 7 | use strict; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 36 |  | 
| 5 | 1 |  |  | 1 |  | 478 | use mod_perl2; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 6 |  |  |  |  |  |  | BEGIN { | 
| 7 |  |  |  |  |  |  | require Apache2::Const; | 
| 8 |  |  |  |  |  |  | require Apache2::Access; | 
| 9 |  |  |  |  |  |  | require Apache2::SubRequest; | 
| 10 |  |  |  |  |  |  | require Apache2::RequestRec; | 
| 11 |  |  |  |  |  |  | require Apache2::RequestUtil; | 
| 12 |  |  |  |  |  |  | require Apache2::Response; | 
| 13 |  |  |  |  |  |  | require APR::Table; | 
| 14 |  |  |  |  |  |  | Apache2::Const->import(-compile => 'HTTP_UNAUTHORIZED','OK', 'HTTP_INTERNAL_SERVER_ERROR'); | 
| 15 |  |  |  |  |  |  | require Apache2::Log; | 
| 16 |  |  |  |  |  |  | require Apache2::Directive; | 
| 17 |  |  |  |  |  |  | require Net::LDAP; | 
| 18 |  |  |  |  |  |  | } | 
| 19 |  |  |  |  |  |  | =head1 NAME | 
| 20 |  |  |  |  |  |  |  | 
| 21 |  |  |  |  |  |  | Apache2::AuthZLDAP - Authorization module based on LDAP filters or LDAP groups | 
| 22 |  |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | =head1 VERSION | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | Version 0.02 | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | =cut | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  | our $VERSION = '0.02'; | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | This module is an authorization handler for Apache 2. Its authorization method relies on openLDAP filters. | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  | =head1 CONFIGURATION | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | This module can work with all authentification module that provides a valid REMOTE_USER env var. For example : | 
| 38 |  |  |  |  |  |  |  | 
| 39 |  |  |  |  |  |  | =over | 
| 40 |  |  |  |  |  |  |  | 
| 41 |  |  |  |  |  |  | =item * | 
| 42 |  |  |  |  |  |  | Basic Apache auth | 
| 43 |  |  |  |  |  |  |  | 
| 44 |  |  |  |  |  |  | =item * | 
| 45 |  |  |  |  |  |  | CAS authentication (mod_cas, Apache2::AuthCAS) | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | =back | 
| 48 |  |  |  |  |  |  |  | 
| 49 |  |  |  |  |  |  | Example with CAS authentication : | 
| 50 |  |  |  |  |  |  |  | 
| 51 |  |  |  |  |  |  |  | 
| 52 |  |  |  |  |  |  | ## these vars can be initialized outside of directory | 
| 53 |  |  |  |  |  |  | PerlSetVar LDAPURI             ldap://myldaphost/ | 
| 54 |  |  |  |  |  |  | PerlSetVar LDAPbaseDN          ou=groups,dc=organization,dc=domain | 
| 55 |  |  |  |  |  |  |  | 
| 56 |  |  |  |  |  |  |  | 
| 57 |  |  |  |  |  |  |  | 
| 58 |  |  |  |  |  |  | AuthName CAS | 
| 59 |  |  |  |  |  |  | AuthType CAS | 
| 60 |  |  |  |  |  |  | ## define a filter. [uid] will be replaced by user value on runtime | 
| 61 |  |  |  |  |  |  | PerlSetVar LDAPfilter          &(member=uid=[uid],ou=people,dc=organization,dc=domain)(cn=admins) | 
| 62 |  |  |  |  |  |  | ## charging of the module for authZ | 
| 63 |  |  |  |  |  |  | PerlAuthzHandler Apache2::AuthZLDAP | 
| 64 |  |  |  |  |  |  | require valid-user | 
| 65 |  |  |  |  |  |  |  | 
| 66 |  |  |  |  |  |  |  | 
| 67 |  |  |  |  |  |  |  | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  | =head2 Configuration Options | 
| 70 |  |  |  |  |  |  |  | 
| 71 |  |  |  |  |  |  | # Set to the LDAP URI | 
| 72 |  |  |  |  |  |  | # Multiple URIs can be set for failover LDAP servers | 
| 73 |  |  |  |  |  |  | # Note: ldaps Defaults to port 636 | 
| 74 |  |  |  |  |  |  | PerlSetVar LDAPURI          ldap://ldaphost1 | 
| 75 |  |  |  |  |  |  | PerlSetVar LDAPURI          ldaps://ldaphost2 | 
| 76 |  |  |  |  |  |  | PerlSetVar LDAPURI          ldap://ldaphost3:1001 | 
| 77 |  |  |  |  |  |  |  | 
| 78 |  |  |  |  |  |  | # How to handle the certificate verification for ldaps:// URIs | 
| 79 |  |  |  |  |  |  | # See start_tls in Net::LDAP for more information | 
| 80 |  |  |  |  |  |  | # If you set any of the LDAPSSL* variables, be sure to include only | 
| 81 |  |  |  |  |  |  | # ldaps:// URIs. Otherwise the connection will fail. | 
| 82 |  |  |  |  |  |  | # (none|optional|require) | 
| 83 |  |  |  |  |  |  | PerlSetVar LDAPSSLverify    none | 
| 84 |  |  |  |  |  |  |  | 
| 85 |  |  |  |  |  |  | # Set to a directory that contains the CA certs | 
| 86 |  |  |  |  |  |  | PerlSetVar LDAPSSLcapath    /path/to/cadir | 
| 87 |  |  |  |  |  |  |  | 
| 88 |  |  |  |  |  |  | # Set to a file that contains the CA cert | 
| 89 |  |  |  |  |  |  | PerlSetVar LDAPSSLcafile    /path/to/cafile.pem | 
| 90 |  |  |  |  |  |  |  | 
| 91 |  |  |  |  |  |  | # Turn on TLS to encrypt a connection | 
| 92 |  |  |  |  |  |  | # Note: This is different from ldaps:// connections. ldaps:// specifies | 
| 93 |  |  |  |  |  |  | # an LDAP connection totally encapsulated by SSL usually running on a | 
| 94 |  |  |  |  |  |  | # different port. TLS tells the LDAP server to encrypt a cleartext ldap:// | 
| 95 |  |  |  |  |  |  | # connection from the time the start_tls command is issued. | 
| 96 |  |  |  |  |  |  | # (yes|no) | 
| 97 |  |  |  |  |  |  | PerlSetVar LDAPTLS          yes | 
| 98 |  |  |  |  |  |  |  | 
| 99 |  |  |  |  |  |  | # How to handle the certificate verification | 
| 100 |  |  |  |  |  |  | # See start_tls in Net::LDAP for more information | 
| 101 |  |  |  |  |  |  | # (none|optional|require) | 
| 102 |  |  |  |  |  |  | PerlSetVar LDAPTLSverify    none | 
| 103 |  |  |  |  |  |  |  | 
| 104 |  |  |  |  |  |  | # Set to a directory that contains the CA certs | 
| 105 |  |  |  |  |  |  | PerlSetVar LDAPTLScapath    /path/to/cadir | 
| 106 |  |  |  |  |  |  |  | 
| 107 |  |  |  |  |  |  | # Set to a file that contains the CA cert | 
| 108 |  |  |  |  |  |  | PerlSetVar LDAPTLScafile    /path/to/cafile.pem | 
| 109 |  |  |  |  |  |  |  | 
| 110 |  |  |  |  |  |  | # Specifies a user/password to use for the bind | 
| 111 |  |  |  |  |  |  | # If LDAPuser is not specified, AuthZLDAP will attempt an anonymous bind | 
| 112 |  |  |  |  |  |  | PerlSetVar LDAPuser         cn=user,o=org | 
| 113 |  |  |  |  |  |  | PerlSetVar LDAPpassword     secret | 
| 114 |  |  |  |  |  |  |  | 
| 115 |  |  |  |  |  |  | # Sets the LDAP search scope | 
| 116 |  |  |  |  |  |  | # (base|one|sub) | 
| 117 |  |  |  |  |  |  | # Defaults to sub | 
| 118 |  |  |  |  |  |  | PerlSetVar LDAPscope        sub | 
| 119 |  |  |  |  |  |  |  | 
| 120 |  |  |  |  |  |  | # Defines the search filter | 
| 121 |  |  |  |  |  |  | # [uid] will be replaced by the username passed in to AuthZLDAP | 
| 122 |  |  |  |  |  |  | PerlSetVar LDAPfilter       &(member=uid=[uid],ou=people,dc=organization,dc=domain)(cn=admins) | 
| 123 |  |  |  |  |  |  |  | 
| 124 |  |  |  |  |  |  | =cut | 
| 125 |  |  |  |  |  |  |  | 
| 126 |  |  |  |  |  |  | sub handler{ | 
| 127 |  |  |  |  |  |  | my $r= shift; | 
| 128 |  |  |  |  |  |  | return Apache2::Const::OK unless $r->is_initial_req; | 
| 129 |  |  |  |  |  |  |  | 
| 130 |  |  |  |  |  |  | ## Location Variables to connect to the good server | 
| 131 |  |  |  |  |  |  | my @LDAPURI = $r->dir_config->get('LDAPURI'); | 
| 132 |  |  |  |  |  |  |  | 
| 133 |  |  |  |  |  |  | my $LDAPSSLverify = lc($r->dir_config('LDAPSSLverify')); | 
| 134 |  |  |  |  |  |  | my $LDAPSSLcapath = $r->dir_config('LDAPSSLcapath'); | 
| 135 |  |  |  |  |  |  | my $LDAPSSLcafile = $r->dir_config('LDAPSSLcafile'); | 
| 136 |  |  |  |  |  |  |  | 
| 137 |  |  |  |  |  |  | my $LDAPTLS =  lc($r->dir_config('LDAPTLS')) || "no"; | 
| 138 |  |  |  |  |  |  | my $LDAPTLSverify = lc($r->dir_config('LDAPTLSverify')); | 
| 139 |  |  |  |  |  |  | my $LDAPTLScapath = $r->dir_config('LDAPTLScapath'); | 
| 140 |  |  |  |  |  |  | my $LDAPTLScafile = $r->dir_config('LDAPTLScafile'); | 
| 141 |  |  |  |  |  |  |  | 
| 142 |  |  |  |  |  |  | if($LDAPTLS ne "yes" && $LDAPTLS ne "no"){ | 
| 143 |  |  |  |  |  |  | $LDAPTLS="no"; | 
| 144 |  |  |  |  |  |  | } | 
| 145 |  |  |  |  |  |  |  | 
| 146 |  |  |  |  |  |  | ## bind | 
| 147 |  |  |  |  |  |  | my $LDAPuser = $r->dir_config('LDAPuser'); | 
| 148 |  |  |  |  |  |  | my $LDAPpassword = $r->dir_config('LDAPpassword'); | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | ## baseDN and Filters | 
| 151 |  |  |  |  |  |  | my $LDAPbaseDN = $r->dir_config('LDAPbaseDN'); | 
| 152 |  |  |  |  |  |  | my $LDAPscope =  lc($r->dir_config('LDAPscope')); | 
| 153 |  |  |  |  |  |  | my $LDAPfilter = $r->dir_config('LDAPfilter'); | 
| 154 |  |  |  |  |  |  |  | 
| 155 |  |  |  |  |  |  | if($LDAPscope ne 'base' && $LDAPscope ne 'one' && $LDAPscope ne 'sub'){ | 
| 156 |  |  |  |  |  |  | $LDAPscope = 'sub'; | 
| 157 |  |  |  |  |  |  | } | 
| 158 |  |  |  |  |  |  |  | 
| 159 |  |  |  |  |  |  | my $location = $r->location; | 
| 160 |  |  |  |  |  |  |  | 
| 161 |  |  |  |  |  |  | ## Some error checking | 
| 162 |  |  |  |  |  |  | if (not @LDAPURI) { | 
| 163 |  |  |  |  |  |  | $r->log_error("Apache2::AuthZLDAP : $location, did not specify a LDAPURI"); | 
| 164 |  |  |  |  |  |  | return Apache2::Const::HTTP_UNAUTHORIZED; | 
| 165 |  |  |  |  |  |  | } | 
| 166 |  |  |  |  |  |  |  | 
| 167 |  |  |  |  |  |  | if (not defined $LDAPfilter) { | 
| 168 |  |  |  |  |  |  | $r->log_error("Apache2::AuthZLDAP : $location, did not specify a LDAPfilter"); | 
| 169 |  |  |  |  |  |  | return Apache2::Const::HTTP_UNAUTHORIZED; | 
| 170 |  |  |  |  |  |  | } | 
| 171 |  |  |  |  |  |  |  | 
| 172 |  |  |  |  |  |  | ## did user authentified ? | 
| 173 |  |  |  |  |  |  | ## retrieval of user id | 
| 174 |  |  |  |  |  |  | my $user = $r->user; | 
| 175 |  |  |  |  |  |  | if (not defined $user){ | 
| 176 |  |  |  |  |  |  | $r->log_error("Apache2::AuthZLDAP : $location, user didn't authentify uid empty"); | 
| 177 |  |  |  |  |  |  | return Apache2::Const::HTTP_UNAUTHORIZED; | 
| 178 |  |  |  |  |  |  | }else{ | 
| 179 |  |  |  |  |  |  | $LDAPfilter =~ s/\[uid\]/$user/; | 
| 180 |  |  |  |  |  |  | } | 
| 181 |  |  |  |  |  |  |  | 
| 182 |  |  |  |  |  |  | ## port initialisation | 
| 183 |  |  |  |  |  |  | my $session; ## TODO make this come from a pool maybe? | 
| 184 |  |  |  |  |  |  | my $mesg; | 
| 185 |  |  |  |  |  |  |  | 
| 186 |  |  |  |  |  |  | unless ($session = Net::LDAP->new(\@LDAPURI, capath=>$LDAPSSLcapath, cafile=>$LDAPSSLcafile, verify=>$LDAPSSLverify)) { | 
| 187 |  |  |  |  |  |  | $r->log_error("Apache2::AuthZLDAP : $location, LDAP error cannot create session"); | 
| 188 |  |  |  |  |  |  | return Apache2::Const::HTTP_UNAUTHORIZED; | 
| 189 |  |  |  |  |  |  | } | 
| 190 |  |  |  |  |  |  |  | 
| 191 |  |  |  |  |  |  | if ($LDAPTLS eq 'yes') { | 
| 192 |  |  |  |  |  |  | $mesg = $session->start_tls(capath=>$LDAPTLScapath, cafile=>$LDAPTLScafile, verify=>$LDAPTLSverify); | 
| 193 |  |  |  |  |  |  | if ($mesg->code) { | 
| 194 |  |  |  |  |  |  | $r->log_error("Apache2::AuthZLDAP : $location, LDAP error could not start TLS : ".$mesg->error); | 
| 195 |  |  |  |  |  |  | } | 
| 196 |  |  |  |  |  |  | return Apache2::Const::HTTP_UNAUTHORIZED; | 
| 197 |  |  |  |  |  |  | } | 
| 198 |  |  |  |  |  |  |  | 
| 199 |  |  |  |  |  |  | ## user password bind if configured else anonymous | 
| 200 |  |  |  |  |  |  | if (defined $LDAPuser and defined $LDAPpassword){ | 
| 201 |  |  |  |  |  |  | $mesg = $session->bind($LDAPuser,password=>$LDAPpassword); | 
| 202 |  |  |  |  |  |  | }else{ | 
| 203 |  |  |  |  |  |  | $mesg = $session->bind(); | 
| 204 |  |  |  |  |  |  | } | 
| 205 |  |  |  |  |  |  |  | 
| 206 |  |  |  |  |  |  | if($mesg->code){ | 
| 207 |  |  |  |  |  |  | my $err_msg = 'LDAP error cannot bind '; | 
| 208 |  |  |  |  |  |  | if (defined $LDAPuser){ | 
| 209 |  |  |  |  |  |  | $err_msg .= "as $LDAPuser"; | 
| 210 |  |  |  |  |  |  | }else{ | 
| 211 |  |  |  |  |  |  | $err_msg .= 'anonymously'; | 
| 212 |  |  |  |  |  |  | } | 
| 213 |  |  |  |  |  |  | $r->log_error("Apache2::AuthZLDAP : $location, $err_msg : ".$mesg->error); | 
| 214 |  |  |  |  |  |  | return Apache2::Const::HTTP_UNAUTHORIZED; | 
| 215 |  |  |  |  |  |  | } | 
| 216 |  |  |  |  |  |  |  | 
| 217 |  |  |  |  |  |  | ## search performing, if there is a result, OK | 
| 218 |  |  |  |  |  |  | $mesg = $session->search( # perform a search | 
| 219 |  |  |  |  |  |  | base   => $LDAPbaseDN, | 
| 220 |  |  |  |  |  |  | scope => $LDAPscope, | 
| 221 |  |  |  |  |  |  | filter => $LDAPfilter, | 
| 222 |  |  |  |  |  |  | ); | 
| 223 |  |  |  |  |  |  | if ($mesg->code) { | 
| 224 |  |  |  |  |  |  | $r->log_error("Apache2::AuthZLDAP : $location, LDAP error could not search : ".$mesg->error); | 
| 225 |  |  |  |  |  |  | return Apache2::Const::HTTP_UNAUTHORIZED; | 
| 226 |  |  |  |  |  |  | } | 
| 227 |  |  |  |  |  |  | if ($mesg->count != 0){ | 
| 228 |  |  |  |  |  |  | $r->log->notice("Apache2::AuthZLDAP : $user authorized to access $location"); | 
| 229 |  |  |  |  |  |  | $session->unbind; | 
| 230 |  |  |  |  |  |  | return Apache2::Const::OK; | 
| 231 |  |  |  |  |  |  | }else{ | 
| 232 |  |  |  |  |  |  | $session->unbind; | 
| 233 |  |  |  |  |  |  | $r->log_error("Apache2::AuthZLDAP : $user not allowed to access $location"); | 
| 234 |  |  |  |  |  |  | return Apache2::Const::HTTP_UNAUTHORIZED; | 
| 235 |  |  |  |  |  |  | } | 
| 236 |  |  |  |  |  |  | } | 
| 237 |  |  |  |  |  |  |  | 
| 238 |  |  |  |  |  |  | =head1 AUTHOR | 
| 239 |  |  |  |  |  |  |  | 
| 240 |  |  |  |  |  |  | Dominique Launay, C<<  >> | 
| 241 |  |  |  |  |  |  | Thanks to David Lowry, C<<  >>  for making the code more readable and improving it. | 
| 242 |  |  |  |  |  |  |  | 
| 243 |  |  |  |  |  |  | =head1 BUGS | 
| 244 |  |  |  |  |  |  |  | 
| 245 |  |  |  |  |  |  | Please report any bugs or feature requests through the web interface at | 
| 246 |  |  |  |  |  |  | L | 
| 247 |  |  |  |  |  |  | I will be notified, and then you'll automatically be notified of progress on | 
| 248 |  |  |  |  |  |  | your bug as I make changes. | 
| 249 |  |  |  |  |  |  |  | 
| 250 |  |  |  |  |  |  | =head1 SUPPORT | 
| 251 |  |  |  |  |  |  |  | 
| 252 |  |  |  |  |  |  | You can find documentation for this module with the perldoc command. | 
| 253 |  |  |  |  |  |  |  | 
| 254 |  |  |  |  |  |  | perldoc Apache2::AuthZLDAP | 
| 255 |  |  |  |  |  |  |  | 
| 256 |  |  |  |  |  |  |  | 
| 257 |  |  |  |  |  |  | =over 4 | 
| 258 |  |  |  |  |  |  |  | 
| 259 |  |  |  |  |  |  |  | 
| 260 |  |  |  |  |  |  | =head1 ACKNOWLEDGEMENTS | 
| 261 |  |  |  |  |  |  |  | 
| 262 |  |  |  |  |  |  | =head1 COPYRIGHT & LICENSE | 
| 263 |  |  |  |  |  |  |  | 
| 264 |  |  |  |  |  |  | Copyright 2007 Dominique Launay, all rights reserved. | 
| 265 |  |  |  |  |  |  |  | 
| 266 |  |  |  |  |  |  | This program is released under the following license: GPL | 
| 267 |  |  |  |  |  |  |  | 
| 268 |  |  |  |  |  |  | =cut | 
| 269 |  |  |  |  |  |  |  | 
| 270 |  |  |  |  |  |  | 1; # End of Apache2::AuthZLDAP |