Initial import of version 0.0.6

git-svn-id: https://modmellon.googlecode.com/svn/trunk@3 a716ebb1-153a-0410-b759-cfb97c6a1b53
This commit is contained in:
olavmrk 2007-09-24 09:56:34 +00:00
commit 1fa6146abe
25 changed files with 5222 additions and 0 deletions

339
COPYING Normal file
View File

@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

69
Makefile.in Normal file
View File

@ -0,0 +1,69 @@
# Source files. mod_auth_mellon.c must be the first file.
SRC=mod_auth_mellon.c \
auth_mellon_cache.c auth_mellon_config.c \
auth_mellon_cookie.c auth_mellon_handler.c \
auth_mellon_util.c \
auth_mellon_session.c \
auth_mellon_httpclient.c
# Files to include when making a .tar.gz-file for distribution
DISTFILES=$(SRC) \
auth_mellon.h \
configure \
configure.ac \
Makefile.in \
autogen.sh \
TODO \
README \
COPYING \
debian/auth_mellon.conf \
debian/auth_mellon.load \
debian/changelog \
debian/compat \
debian/control \
debian/copyright \
debian/dirs \
debian/docs \
debian/install \
debian/rules
all: mod_auth_mellon.la
mod_auth_mellon.la: $(SRC) auth_mellon.h
@APXS2@ @OPENSSL_CFLAGS@ @LASSO_CFLAGS@ @CURL_CFLAGS@ @OPENSSL_LIBS@ @LASSO_LIBS@ @CURL_LIBS@ -Wc,-Wall -c $(SRC)
# Building configure (for distribution)
configure: configure.ac
./autogen.sh
@NAMEVER@.tar.gz: $(DISTFILES)
tar -c --transform="s#^#@NAMEVER@/#" -vzf $@ $(DISTFILES)
.PHONY: install
install: mod_auth_mellon.la
@APXS2@ -i -n auth_mellon $<
.PHONY: distfile
distfile: @NAMEVER@.tar.gz
.PHONY: clean
clean:
rm -f mod_auth_mellon.la
rm -f $(SRC:%.c=%.lo)
rm -f $(SRC:%.c=%.slo)
rm -rf .libs/
.PHONY: distclean
distclean: clean
rm -f Makefile config.log config.status @NAMEVER@.tar.gz *~ \
build-stamp config.guess config.sub
rm -rf debian/mod-auth-mellon
rm -f debian/files
.PHONY: fullclean
fullclean: distclean
rm -f configure aclocal.m4

334
README Normal file
View File

@ -0,0 +1,334 @@
===========================================================================
README file for mod_auth_mellon
===========================================================================
mod_auth_mellon is a authentication module for apache. It authenticates
the user against a SAML 2.0 IdP, and and grants access to directories
depending on attributes received from the IdP.
===========================================================================
Dependencies
===========================================================================
mod_auth_mellon has four dependencies:
* pkg-config
* Apache (>=2.0)
* OpenSSL
* lasso (>=2.1)
You will also require developement headers and tools for all of the
dependencies.
If OpenSSL or lasso are installed in a "strange" directory, then you may
have to specify the directory containing "lasso.pc" and/or "openssl.pc" in
the PKG_CONFIG_PATH environment variable. For example, if openssl is
installed in /usr/local/openssl (with openssl.pc in
/usr/local/openssl/lib/pkgconfig/) and lasso is installed in /opt/lasso
(lasso.pc in /opt/lasso/lib/pkgconfig/), then you can set PKG_CONFIG_PATH
before running configure like this:
PKG_CONFIG_PATH=/usr/local/openssl/lib/pkgconfig:/opt/lasso/lib/pkgconfig
export PKG_CONFIG_PATH
If Apache is installed in a "strange" directory, then you may have to
specify the path to apxs2 using the --with-apxs2=/full/path/to/apxs2
option to configure. If, for example, Apache is installed in /opt/apache,
with apxs2 in /opt/apache/bin, then you run
./configure --with-apxs2=/opt/apache2/bin/apxs2
Note that, depending on your distribution, apxs2 may be named apxs.
===========================================================================
Installing mod_auth_mellon
===========================================================================
mod_auth_mellon uses autoconf, and can be installed by running the
following commands:
./configure
make
make install
===========================================================================
Configuring mod_auth_mellon
===========================================================================
Here we are going to assume that your web servers hostname is
'example.com', and that the directory you are going to protect is
'http://example.com/secret/'. We are also going to assume that you have
configured your web site to use SSL.
You need to edit the configuration file for your web server. Depending on
your distribution, it may be named '/etc/apache/httpd.conf' or something
different.
You need to add a LoadModule directove for mod_auth_mellon. This will
look similar to this:
LoadModule auth_mellon_module /usr/lib/apache2/modules/mod_auth_mellon.so
To find the full path to mod_auth_mellon.so, you may run:
apxs2 -q LIBEXECDIR
This will print the path where Apache stores modules. mod_auth_mellon.so
will be stored in that directory.
After you have added the LoadModule directive, you must add configuration
for mod_auth_mellon. The following is an example configuration:
###########################################################################
# Global configuration for mod_auth_mellon. This configuration is shared by
# every virtual server and location in this instance of apache.
###########################################################################
# MellonCacheSize sets the maximum number of sessions which can be active
# at once. When mod_auth_mellon reaches this limit, it will begin removing
# the least recently used sessions. The server must be restarted before any
# changes to this option takes effect.
# Default: MellonCacheSize 100
MellonCacheSize 100
# MellonLockFile is the full path to a file used for synchronizing access
# to the session data. The path should only be used by one instance of
# apache at a time. The server must be restarted before any changes to this
# option takes effect.
# Default: MellonLockFile "/tmp/mellonLock"
MellonLockFile "/tmp/mellonLock"
###########################################################################
# End of global configuration for mod_auth_mellon.
###########################################################################
# This defines a directory where mod_auth_mellon should do access control.
<Location /secret>
# These are standard Apache apache configuration directives.
# See http://httpd.apache.org/docs/2.2/mod/core.html for information
# about them.
Require valid-user
AuthType "Mellon"
# MellonEnable is used to enable auth_mellon on a location.
# It has three possible values: "off", "info" and "auth".
# They have the following meanings:
# "off": mod_auth_mellon will not do anything in this location.
# This is the default state.
# "info": If the user is authorized to access the resource, then
# we will populate the environment with information about
# the user. If the user isn't authorized, then we won't
# populate the environment, but we won't deny the user
# access either.
# "auth": We will populate the environment with information about
# the user if he is authorized. If he is authenticated
# (logged in), but not authorized (according to the
# MellonRequire directives, then we will return a 403
# Forbidden error. If he isn't authenticated then we will
# redirect him to the login page of the IdP.
#
# Default: MellonEnable "off"
MellonEnable "auth"
# MellonDecoder is used to select which decoder mod_auth_mellon
# will use when decoding attribute values.
# There are two possible values: "none" and "feide". "none" is the
# default.
# They have the following meanings:
# "none": mod_auth_mellon will store the attribute as it is
# received from the IdP. This is the default behaviour.
# "feide": FEIDE currently stores several values in a single
# AttributeValue element. The values are base64 encoded
# and separated by a underscore. This decoder reverses
# this encoding.
# Default: MellonDecoder "none"
MellonDecoder "none"
# MellonVariable is used to select the name of the cookie which
# mod_auth_mellon should use to remember the session id. If you
# want to have different sites running on the same host, then
# you will have to choose a different name for the cookie for each
# site.
# Default: "cookie"
MellonVariable "cookie"
# MellonUser selects which attribute we should use for the username.
# The username is passed on to other apache modules and to the web
# page the user visits. NAME_ID is an attribute which we set to
# the id we get from the IdP.
# Default: MellonUser "NAME_ID"
MellonUser "NAME_ID"
# MellonSetEnv configuration directives allows you to map
# attribute names received from the IdP to names you choose
# yourself. The syntax is 'MellonSetEnv <local name> <IdP name>'.
# You can list multiple MellonSetEnv directives.
# Default. None set.
MellonSetEnv "e-mail" "mail"
# MellonRequire allows you to limit access to those with specific
# attributes. The syntax is
# 'MellonRequire <attribute name> <list of valid values>'.
# Note that the attribute name is the name we received from the
# IdP.
#
# If you don't list any MellonRequire directives, then any user
# authenticated by the IdP will have access to this service. If
# you list several MellonRequire directives, then all of them
# will have to match.
#
# Default: None set.
MellonRequire "eduPersonAffiliation" "student" "employee"
# MellonEndpointPath selects which directory mod_auth_mellon
# should assume contains the SAML 2.0 endpoints. Any request to
# this directory will be handled by mod_auth_mellon.
#
# The path is the full path (from the root of the web server) to
# the directory. The directory must be a sub-directory of this
# <Location ...>.
# Default: MellonEndpointPath "/mellon"
MellonEndpointPath "/secret/endpoint"
# MellonSessionLength sets the maximum lifetime of a session, in
# seconds. The actual lifetime may be shorter, depending on the
# conditions received from the IdP. The default length is 86400
# seconds, which is one day.
# Default: MellonSessionLength 86400
MellonSessionLength 86400
# MellonNoCookieErrorPage is the full path to a page which
# mod_auth_mellon will redirect the user to if he returns from the
# IdP without a cookie with a session id.
# Note that the user may also get this error if he for some reason
# loses the cookie between being redirected to the IdPs login page
# and returning from it.
# If this option is unset, then mod_auth_mellon will return a
# 400 Bad Request error if the cookie is missing.
# Default: unset
MellonNoCookieErrorPage "https://example.com/no_cookie.html"
# MellonSPMetadataFile is the full path to the file containing
# the metadata for this service provider. You must configure this
# before you can use this module.
# Default: None set.
MellonSPMetadataFile /etc/apache2/mellon/sp-metadata.xml
# MellonSPPrivateKeyFile is a .pem file which contains the private
# key of the service provider. The .pem-file cannot be encrypted
# with a password. This directive is optional.
# Default: None set.
MellonSPPrivateKeyFile /etc/apache2/mellon/sp-private-key.pem
# MellonIdPMetadataFile is the full path to the file which contains
# metadata for the IdP you are authenticating against. This
# directive is required.
# Default: None set.
MellonIdPMetadataFile /etc/apache2/mellon/idp-metadata.xml
# MellonIdpPublicKeyFile is the full path of the public key of the
# IdP. This parameter is optional if the public key is embedded
# in the IdP's metadata file.
# Default: None set.
MellonIdPPublicKeyFile /etc/apache2/mellon/idp-public-key.pem
</Location>
===========================================================================
Service provider metadata
===========================================================================
The contents of the metadata will depend on your hostname and on what path
you selected with the MellonEndpointPath configuration directive.
The following is an example of metadata for the example configuration:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<EntityDescriptor
entityID="examlpe.com"
xmlns="urn:oasis:names:tc:SAML:2.0:metadata">
<SPSSODescriptor
AuthnRequestsSigned="false"
WantAssertionsSigned="false"
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<SingleLogoutService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
Location="https://example.com/secret/endpoint/logoutRequest" />
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:transient
</NameIDFormat>
<AssertionConsumerService
index="0"
isDefault="true"
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="https://example.com/secret/endpoint/postResponse" />
</SPSSODescriptor>
</EntityDescriptor>
You should update entityID="example.com" and the two Location attributes.
Note that '/secret/endpoint' in the two Location attributes matches the
path set in MellonEndpointPath.
To use HTTP-Artifact binding instead of the HTTP-POST binding, change
the AssertionConsumerService-element to something like this:
<AssertionConsumerService
index="0"
isDefault="true"
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
Location="https://example.com/secret/endpoint/artifactResponse" />
===========================================================================
Using mod_auth_mellon
===========================================================================
After you have set up mod_auth_mellon, you should be able to visit (in our
example) https://example.com/secret/, and be redirected to the IdP's login
page. After logging in you should be returned to
https://example.com/secret/, and get the contents of that page.
When authenticating a user, mod_auth_mellon will set some environment
variables to the attributes it received from the IdP. The name of the
variables will be MELLON_<attribute name>. If you have specified a
different name with the MellonSetEnv configuration directive, then that
name will be used instead. The name will still be prefixed by 'MELLON_'.
The value of the attribute will be base64 decoded.
mod_auth_mellon supports multivalued attributes with the following format:
<base64 encoded value>_<base64 encoded value>_<base 64 encoded value>...
If an attribute has multiple values, then they will be stored as
MELLON_<name>_0, MELLON_<name>_1, MELLON_<name>_2, ...
Since mod_auth_mellon doesn't know which attributes may have multiple
values, it will store every attribute at least twice. Once named
MELLON_<name>, and once named <MELLON_<name>_0.
In the case of multivalued attributes MELLON_<name> will contain the first
value.
The following code is a simple php-script which prints out all the
variables:
<?php
header('Content-Type: text/plain');
foreach($_SERVER as $key=>$value) {
if(substr($key, 0, 7) == 'MELLON_') {
echo($key . '=' . $value . "\r\n");
}
}
?>

4
TODO Normal file
View File

@ -0,0 +1,4 @@
TODO for auth_mellon:
* Change session storage to use less than 64KiB/session.
* Optimize session lookup.

246
auth_mellon.h Normal file
View File

@ -0,0 +1,246 @@
/*
*
* auth_mellon.h: an authentication apache module
* Copyright © 2003-2007 UNINETT (http://www.uninett.no/)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef MOD_AUTH_MELLON_H
#define MOD_AUTH_MELLON_H
#include <lasso/lasso.h>
#include <lasso/xml/saml-2.0/samlp2_authn_request.h>
#include <lasso/xml/saml-2.0/samlp2_response.h>
#include <lasso/xml/saml-2.0/saml2_assertion.h>
#include <lasso/xml/saml-2.0/saml2_attribute_statement.h>
#include <lasso/xml/saml-2.0/saml2_attribute.h>
#include <lasso/xml/saml-2.0/saml2_attribute_value.h>
#include <lasso/xml/misc_text_node.h>
/* The following are redefined in ap_config_auto.h */
#undef PACKAGE_BUGREPORT
#undef PACKAGE_NAME
#undef PACKAGE_STRING
#undef PACKAGE_TARNAME
#undef PACKAGE_VERSION
#undef HAVE_TIMEGM /* is redefined again in ap_config.h */
#include "apr_base64.h"
#include "apr_time.h"
#include "apr_strings.h"
#include "apr_shm.h"
#include "apr_md5.h"
#include "ap_config.h"
#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"
#include "http_protocol.h"
#include "http_request.h"
/* Size definitions for the session cache.
*/
#define AM_CACHE_KEYSIZE 120
#define AM_CACHE_VARSIZE 128
#define AM_CACHE_VALSIZE 512-AM_CACHE_VARSIZE
#define AM_CACHE_ENVSIZE 128
#define AM_CACHE_USERSIZE 512
#define AM_CACHE_MAX_LASSO_IDENTITY_SIZE 1024
#define AM_CACHE_MAX_LASSO_SESSION_SIZE 3074
/* This is the length of the session id we use.
*/
#define AM_SESSION_ID_LENGTH 32
#define am_get_srv_cfg(s) (am_srv_cfg_rec *)ap_get_module_config((s)->module_config, &auth_mellon_module)
#define am_get_mod_cfg(s) (am_get_srv_cfg((s)))->mc
#define am_get_dir_cfg(r) (am_dir_cfg_rec *)ap_get_module_config((r)->per_dir_config, &auth_mellon_module)
typedef struct am_mod_cfg_rec {
int cache_size;
const char *lock_file;
/* These variables can't be allowed to change after the session store
* has been initialized. Therefore we copy them before initializing
* the session store.
*/
int init_cache_size;
const char *init_lock_file;
apr_shm_t *cache;
apr_global_mutex_t *lock;
} am_mod_cfg_rec;
typedef struct am_srv_cfg_rec {
am_mod_cfg_rec *mc;
} am_srv_cfg_rec;
typedef enum {
am_enable_default,
am_enable_off,
am_enable_info,
am_enable_auth
} am_enable_t;
typedef enum {
am_decoder_default,
am_decoder_none,
am_decoder_feide,
} am_decoder_t;
typedef struct am_dir_cfg_rec {
/* enable_mellon is used to enable auth_mellon for a location.
*/
am_enable_t enable_mellon;
/* The decoder attribute is used to specify which decoder we should use
* when parsing attributes.
*/
am_decoder_t decoder;
const char *varname;
apr_hash_t *require;
apr_hash_t *envattr;
const char *userattr;
/* The "root directory" of our SAML2 endpoints. This path is relative
* to the root of the web server.
*
* This path will always end with '/'.
*/
const char *endpoint_path;
/* Lasso configuration variables. */
const char *sp_metadata_file;
const char *sp_private_key_file;
const char *idp_metadata_file;
const char *idp_public_key_file;
/* Maximum number of seconds a session is valid for. */
int session_length;
/* No cookie error page. */
const char *no_cookie_error_page;
/* Mutex to prevent us from creating several lasso server objects. */
apr_thread_mutex_t *server_mutex;
/* Cached lasso server object. */
LassoServer *server;
} am_dir_cfg_rec;
typedef struct am_cache_env_t {
char varname[AM_CACHE_VARSIZE];
char value[AM_CACHE_VALSIZE];
} am_cache_env_t;
typedef struct am_cache_entry_t {
char key[AM_CACHE_KEYSIZE];
apr_time_t access;
apr_time_t expires;
int logged_in;
unsigned short size;
char user[AM_CACHE_USERSIZE];
/* Variables used to store lasso state between login requests
*and logout requests.
*/
char lasso_identity[AM_CACHE_MAX_LASSO_IDENTITY_SIZE];
char lasso_session[AM_CACHE_MAX_LASSO_SESSION_SIZE];
am_cache_env_t env[AM_CACHE_ENVSIZE];
} am_cache_entry_t;
extern const command_rec auth_mellon_commands[];
void *auth_mellon_dir_config(apr_pool_t *p, char *d);
void *auth_mellon_dir_merge(apr_pool_t *p, void *base, void *add);
void *auth_mellon_server_config(apr_pool_t *p, server_rec *s);
const char *am_cookie_get(request_rec *r);
void am_cookie_set(request_rec *r, const char *id);
void am_cookie_delete(request_rec *r);
am_cache_entry_t *am_cache_lock(server_rec *s, const char *key);
am_cache_entry_t *am_cache_new(server_rec *s, const char *key);
void am_cache_unlock(server_rec *s, am_cache_entry_t *entry);
void am_cache_update_expires(am_cache_entry_t *t, apr_time_t expires);
void am_cache_env_populate(request_rec *r, am_cache_entry_t *session);
int am_cache_env_append(am_cache_entry_t *session,
const char *var, const char *val);
void am_cache_delete(server_rec *s, am_cache_entry_t *session);
int am_cache_set_lasso_state(am_cache_entry_t *session,
const char *lasso_identity,
const char *lasso_session);
const char *am_cache_get_lasso_identity(am_cache_entry_t *session);
const char *am_cache_get_lasso_session(am_cache_entry_t *session);
am_cache_entry_t *am_get_request_session(request_rec *r);
am_cache_entry_t *am_new_request_session(request_rec *r);
void am_release_request_session(request_rec *r, am_cache_entry_t *session);
void am_delete_request_session(request_rec *r, am_cache_entry_t *session);
const char *am_reconstruct_url(request_rec *r);
int am_check_permissions(request_rec *r, am_cache_entry_t *session);
void am_set_nocache(request_rec *r);
int am_read_post_data(request_rec *r, char **data, apr_size_t *length);
char *am_extract_query_parameter(apr_pool_t *pool,
const char *query_string,
const char *name);
char *am_urlencode(apr_pool_t *pool, const char *str);
int am_urldecode(char *data);
char *am_generate_session_id(request_rec *r);
int am_auth_mellon_user(request_rec *r);
int am_check_uid(request_rec *r);
int am_httpclient_get(request_rec *r, const char *uri,
void **buffer, apr_size_t *size);
int am_httpclient_post(request_rec *r, const char *uri,
const void *post_data, apr_size_t post_length,
const char *content_type,
void **buffer, apr_size_t *size);
int am_httpclient_post_str(request_rec *r, const char *uri,
const char *post_data,
const char *content_type,
void **buffer, apr_size_t *size);
extern module AP_MODULE_DECLARE_DATA auth_mellon_module;
#endif /* MOD_AUTH_MELLON_H */

473
auth_mellon_cache.c Normal file
View File

@ -0,0 +1,473 @@
/*
*
* auth_mellon_cache.c: an authentication apache module
* Copyright © 2003-2007 UNINETT (http://www.uninett.no/)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "auth_mellon.h"
/* This function locks the session table and locates a session entry.
* Unlocks the table and returns NULL if the entry wasn't found.
* If a entry was found, then you _must_ unlock it with am_cache_unlock
* after you are done with it.
*
* Parameters:
* server_rec *s The current server.
* const char *key The session key.
*
* Returns:
* The session entry on success or NULL on failure.
*/
am_cache_entry_t *am_cache_lock(server_rec *s, const char *key)
{
am_mod_cfg_rec *mod_cfg;
am_cache_entry_t *table;
int i;
/* Check if we have a valid session key. We abort if we don't. */
if(key == NULL || strlen(key) != AM_SESSION_ID_LENGTH) {
return NULL;
}
mod_cfg = am_get_mod_cfg(s);
/* Lock the table. */
apr_global_mutex_lock(mod_cfg->lock);
table = apr_shm_baseaddr_get(mod_cfg->cache);
for(i = 0; i < mod_cfg->init_cache_size; i++) {
if(strcmp(table[i].key, key) == 0) {
/* We found the entry. */
if(table[i].expires > apr_time_now()) {
/* And it hasn't expired. */
return &table[i];
}
}
}
/* We didn't find a entry matching the key. Unlock the table and
* return NULL;
*/
apr_global_mutex_unlock(mod_cfg->lock);
return NULL;
}
/* This function locks the session table and creates a new session entry.
* It will first attempt to locate a free session. If it doesn't find a
* free session, then it will take the least recentry used session.
*
* Remember to unlock the table with am_cache_unlock(...) afterwards.
*
* Parameters:
* server_rec *s The current server.
* const char *key The key of the session to allocate.
*
* Returns:
* The new session entry on success. NULL if key is a invalid session
* key.
*/
am_cache_entry_t *am_cache_new(server_rec *s, const char *key)
{
am_cache_entry_t *t;
am_mod_cfg_rec *mod_cfg;
am_cache_entry_t *table;
apr_time_t current_time;
int i;
apr_time_t age;
/* Check if we have a valid session key. We abort if we don't. */
if(key == NULL || strlen(key) != AM_SESSION_ID_LENGTH) {
return NULL;
}
/* First we try to find another session with the given key. */
t = am_cache_lock(s, key);
if(t == NULL) {
/* We didn't find a previous session with the key. We will search
* for the least recently used entry or a free entry in stead.
*/
mod_cfg = am_get_mod_cfg(s);
/* Lock the table. */
apr_global_mutex_lock(mod_cfg->lock);
table = apr_shm_baseaddr_get(mod_cfg->cache);
/* Get current time. If we find a entry with expires <= the current
* time, then we can use it.
*/
current_time = apr_time_now();
/* We will use 't' to remember the best/oldest entry. We
* initalize it to the first entry in the table to simplify the
* following code (saves test for t == NULL).
*/
t = &table[0];
/* Iterate over the session table. Update 't' to match the "best"
* entry (the least recently used). 't' will point a free entry
* if we find one. Otherwise, 't' will point to the least recently
* used entry.
*/
for(i = 0; i < mod_cfg->init_cache_size; i++) {
if(table[i].key[0] == '\0') {
/* This entry is free. Update 't' to this entry
* and exit loop.
*/
t = &table[i];
break;
}
if(table[i].expires <= current_time) {
/* This entry is expired, and is therefore free.
* Update 't' and exit loop.
*/
t = &table[i];
break;
}
if(table[i].access < t->access) {
/* This entry is older than 't' - update 't'. */
t = &table[i];
}
}
}
if(t->key[0] != '\0' && t->expires > current_time) {
/* We dropped a LRU entry. Calculate the age in seconds. */
age = (current_time - t->access) / 1000000;
if(age < 3600) {
ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s,
"Dropping LRU entry entry with age = %llis,"
" which is less than one hour. It may be a good"
" idea to increase MellonCacheSize.",
age);
}
}
/* Now 't' points to the entry we are going to use. We initialize
* it and returns it.
*/
strcpy(t->key, key);
/* Far far into the future. */
t->expires = 0x7fffffffffffffffLL;
t->logged_in = 0;
t->size = 0;
t->user[0] = '\0';
t->lasso_identity[0] = '\0';
t->lasso_session[0] = '\0';
return t;
}
/* This function unlocks a session entry.
*
* Parameters:
* server_rec *s The current server.
* am_cache_entry_t *entry The session entry.
*
* Returns:
* Nothing.
*/
void am_cache_unlock(server_rec *s, am_cache_entry_t *entry)
{
am_mod_cfg_rec *mod_cfg;
/* Update access time. */
entry->access = apr_time_now();
mod_cfg = am_get_mod_cfg(s);
apr_global_mutex_unlock(mod_cfg->lock);
}
/* This function updates the expire-timestamp of a session, if the new
* timestamp is earlier than the previous.
*
* Parameters:
* am_cache_entry_t *t The current session.
* apr_time_t expires The new timestamp.
*
* Returns:
* Nothing.
*/
void am_cache_update_expires(am_cache_entry_t *t, apr_time_t expires)
{
/* Check if we should update the expires timestamp. */
if(t->expires == 0 || t->expires > expires) {
t->expires = expires;
}
}
/* This function appends a name-value pair to a session. It is possible to
* store several values with the same name. This is the method used to store
* multivalued fields.
*
* Parameters:
* am_cache_entry_t *t The current session.
* const char *var The name of the value to be stored.
* const char *val The value which should be stored in the session.
*
* Returns:
* OK on success or HTTP_INTERNAL_SERVER_ERROR on failure.
*/
int am_cache_env_append(am_cache_entry_t *t,
const char *var, const char *val)
{
/* Make sure that the name and value will fit inside the
* fixed size buffer.
*/
if(strlen(val) >= AM_CACHE_VALSIZE ||
strlen(var) >= AM_CACHE_VARSIZE) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
"Unable to store session data because it is to big. "
"Name = \"%s\"; Value = \"%s\".", var, val);
return HTTP_INTERNAL_SERVER_ERROR;
}
if(t->size >= AM_CACHE_ENVSIZE) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
"Unable to store attribute value because we have"
" reached the maximum number of name-value pairs for"
" this session.");
return HTTP_INTERNAL_SERVER_ERROR;
}
strcpy(t->env[t->size].varname, var);
strcpy(t->env[t->size].value, val);
t->size++;
return OK;
}
/* This function populates the subprocess environment with data received
* from the IdP.
*
* Parameters:
* request_rec *r The request we should add the data to.
* am_cache_entry_t *t The session data.
*
* Returns:
* Nothing.
*/
void am_cache_env_populate(request_rec *r, am_cache_entry_t *t)
{
am_dir_cfg_rec *d;
int i;
apr_hash_t *counters;
const char *varname;
const char *env_varname;
const char *value;
int *count;
d = am_get_dir_cfg(r);
/* Check if the user attribute has been set, and set it if it
* hasn't been set. */
if(t->user[0] == '\0') {
for(i = 0; i < t->size; ++i) {
if(strcmp(t->env[i].varname, d->userattr) == 0) {
strcpy(t->user, t->env[i].value);
}
}
}
if(t->user[0] != '\0') {
/* We have a user-"name". Set r->user and r->ap_auth_type. */
r->user = apr_pstrdup(r->pool, t->user);
r->ap_auth_type = apr_pstrdup(r->pool, "Mellon");
} else {
/* We don't have a user-"name". Log error. */
ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r,
"Didn't find the attribute \"%s\" in the attributes"
" which were received from the IdP. Cannot set a user"
" for this request without a valid user attribute.",
d->userattr);
}
/* Allocate a set of counters for duplicate variables in the list. */
counters = apr_hash_make(r->pool);
/* Populate the subprocess environment with the attributes we
* received from the IdP.
*/
for(i = 0; i < t->size; ++i) {
varname = t->env[i].varname;
/* Check if we should map this name into another name. */
env_varname = (const char*)apr_hash_get(
d->envattr, varname, APR_HASH_KEY_STRING);
if(env_varname != NULL) {
varname = env_varname;
}
value = t->env[i].value;
/* Find the number of times this variable has been set. */
count = apr_hash_get(counters, varname, APR_HASH_KEY_STRING);
if(count == NULL) {
/* This is the first time. Create a counter for this variable. */
count = apr_palloc(r->pool, sizeof(int));
*count = 0;
apr_hash_set(counters, varname, APR_HASH_KEY_STRING, count);
/* Add the variable without a suffix. */
apr_table_set(r->subprocess_env,
apr_pstrcat(r->pool, "MELLON_", varname, NULL),
value);
}
/* Add the variable with a suffix indicating how many times it has
* been added before.
*/
apr_table_set(r->subprocess_env,
apr_psprintf(r->pool, "MELLON_%s_%d", varname, *count),
value);
/* Increase the count. */
++(*count);
}
}
/* This function deletes a given key from the session store.
*
* Parameters:
* server_rec *s The current server.
* am_cache_entry_t *cache The entry we are deleting.
*
* Returns:
* Nothing.
*/
void am_cache_delete(server_rec *s, am_cache_entry_t *cache)
{
/* We write a null-byte at the beginning of the key to
* mark this slot as unused.
*/
cache->key[0] = '\0';
/* Unlock the entry. */
am_cache_unlock(s, cache);
}
/* This function stores a lasso identity dump and a lasso session dump in
* the given session object.
*
* Parameters:
* am_cache_entry_t *session The session object.
* const char *lasso_identity The identity dump.
* const char *lasso_session The session dump.
*
* Returns:
* OK on success or HTTP_INTERNAL_SERVER_ERROR if the lasso state information
* is to big to fit in our session.
*/
int am_cache_set_lasso_state(am_cache_entry_t *session,
const char *lasso_identity,
const char *lasso_session)
{
if(lasso_identity != NULL) {
if(strlen(lasso_identity) < AM_CACHE_MAX_LASSO_IDENTITY_SIZE) {
strcpy(session->lasso_identity, lasso_identity);
} else {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
"Lasso identity is to big for storage. Size of lasso"
" identity is %u, max size is %u.", strlen(lasso_identity),
AM_CACHE_MAX_LASSO_IDENTITY_SIZE - 1);
return HTTP_INTERNAL_SERVER_ERROR;
}
} else {
/* No identity dump to save. */
strcpy(session->lasso_identity, "");
}
if(lasso_session != NULL) {
if(strlen(lasso_session) < AM_CACHE_MAX_LASSO_SESSION_SIZE) {
strcpy(session->lasso_session, lasso_session);
} else {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
"Lasso session is to big for storage. Size of lasso"
" session is %u, max size is %u.", strlen(lasso_session),
AM_CACHE_MAX_LASSO_SESSION_SIZE - 1);
return HTTP_INTERNAL_SERVER_ERROR;
}
} else {
/* No session dump to save. */
strcpy(session->lasso_session, "");
}
return OK;
}
/* This function retrieves a lasso identity dump from the session object.
*
* Parameters:
* am_cache_entry_t *session The session object.
*
* Returns:
* The identity dump, or NULL if we don't have a session dump.
*/
const char *am_cache_get_lasso_identity(am_cache_entry_t *session)
{
if(strlen(session->lasso_identity) == 0) {
return NULL;
}
return session->lasso_identity;
}
/* This function retrieves a lasso session dump from the session object.
*
* Parameters:
* am_cache_entry_t *session The session object.
*
* Returns:
* The session dump, or NULL if we don't have a session dump.
*/
const char *am_cache_get_lasso_session(am_cache_entry_t *session)
{
if(strlen(session->lasso_session) == 0) {
return NULL;
}
return session->lasso_session;
}

580
auth_mellon_config.c Normal file
View File

@ -0,0 +1,580 @@
/*
*
* auth_mellon_config.c: an authentication apache module
* Copyright © 2003-2007 UNINETT (http://www.uninett.no/)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "auth_mellon.h"
/* This is the default endpoint path. Remember to update the description of
* the MellonEndpointPath configuration directive if you change this.
*/
static const char *default_endpoint_path = "/mellon/";
/* This is the default name of the attribute we use as a username. Remember
* to update the description of the MellonUser configuration directive if
* you change this.
*/
static const char *default_user_attribute = "NAME_ID";
/* This is the default name of the cookie which mod_auth_mellon will set.
* If you change this, then you should also update the description of the
* MellonVar configuration directive.
*/
static const char *default_cookie_name = "cookie";
/* This function handles configuration directives which set a string
* slot in the module configuration.
*
* Parameters:
* cmd_parms *cmd The command structure for this configuration
* directive.
* void *struct_ptr Pointer to the current directory configuration.
* NULL if we are not in a directory configuration.
* This value isn't used by this function.
* const char *arg The string argument following this configuration
* directive in the configuraion file.
*
* Returns:
* NULL on success or an error string on failure.
*/
static const char *am_set_module_config_string_slot(cmd_parms *cmd,
void *struct_ptr,
const char *arg)
{
return ap_set_string_slot(cmd, am_get_mod_cfg(cmd->server), arg);
}
/* This function handles configuration directives which set an int
* slot in the module configuration.
*
* Parameters:
* cmd_parms *cmd The command structure for this configuration
* directive.
* void *struct_ptr Pointer to the current directory configuration.
* NULL if we are not in a directory configuration.
* This value isn't used by this function.
* const char *arg The string argument following this configuration
* directive in the configuraion file.
*
* Returns:
* NULL on success or an error string on failure.
*/
static const char *am_set_module_config_int_slot(cmd_parms *cmd,
void *struct_ptr,
const char *arg)
{
return ap_set_int_slot(cmd, am_get_mod_cfg(cmd->server), arg);
}
/* This function handles the MellonEnable configuration directive.
* This directive can be set to "off", "info" or "auth".
*
* Parameters:
* cmd_parms *cmd The command structure for this configuration
* directive.
* void *struct_ptr Pointer to the current directory configuration.
* const char *arg The string argument following this configuration
* directive in the configuraion file.
*
* Returns:
* NULL on success or an error string if the argument is wrong.
*/
static const char *am_set_enable_slot(cmd_parms *cmd,
void *struct_ptr,
const char *arg)
{
am_dir_cfg_rec *d = (am_dir_cfg_rec *)struct_ptr;
if(!strcasecmp(arg, "auth")) {
d->enable_mellon = am_enable_auth;
} else if(!strcasecmp(arg, "info")) {
d->enable_mellon = am_enable_info;
} else if(!strcasecmp(arg, "off")) {
d->enable_mellon = am_enable_off;
} else {
return "parameter must be 'off', 'info' or 'auth'";
}
return NULL;
}
/* This function handles the MellonDecoder configuration directive.
* This directive can be set to "none" or "feide".
*
* Parameters:
* cmd_parms *cmd The command structure for this configuration
* directive.
* void *struct_ptr Pointer to the current directory configuration.
* const char *arg The string argument following this configuration
* directive in the configuraion file.
*
* Returns:
* NULL on success or an error string if the argument is wrong.
*/
static const char *am_set_decoder_slot(cmd_parms *cmd,
void *struct_ptr,
const char *arg)
{
am_dir_cfg_rec *d = (am_dir_cfg_rec *)struct_ptr;
if(!strcasecmp(arg, "none")) {
d->decoder = am_decoder_none;
} else if(!strcasecmp(arg, "feide")) {
d->decoder = am_decoder_feide;
} else {
return "MellonDecoder must be 'none' or 'feide'";
}
return NULL;
}
/* This function handles the MellonEndpointPath configuration directive.
* If the path doesn't end with a '/', then we will append one.
*
* Parameters:
* cmd_parms *cmd The command structure for the MellonEndpointPath
* configuration directive.
* void *struct_ptr Pointer to the current directory configuration.
* NULL if we are not in a directory configuration.
* const char *arg The string argument containing the path of the
* endpoint directory.
*
* Returns:
* This function will always return NULL.
*/
static const char *am_set_endpoint_path(cmd_parms *cmd,
void *struct_ptr,
const char *arg)
{
am_dir_cfg_rec *d = (am_dir_cfg_rec *)struct_ptr;
/* Make sure that the path ends with '/'. */
if(strlen(arg) == 0 || arg[strlen(arg) - 1] != '/') {
d->endpoint_path = apr_pstrcat(cmd->pool, arg, "/", 0);
} else {
d->endpoint_path = arg;
}
return NULL;
}
/* This function handles the MellonSetEnv configuration directive.
* This directive allows the user to change the name of attributes.
*
* Parameters:
* cmd_parms *cmd The command structure for the MellonSetEnv
* configuration directive.
* void *struct_ptr Pointer to the current directory configuration.
* const char *newName The new name of the attribute.
* const char *oldName The old name of the attribute.
*
* Returns:
* This function will always return NULL.
*/
static const char *am_set_setenv_slot(cmd_parms *cmd,
void *struct_ptr,
const char *newName,
const char *oldName)
{
am_dir_cfg_rec *d = (am_dir_cfg_rec *)struct_ptr;
apr_hash_set(d->envattr, oldName, APR_HASH_KEY_STRING, newName);
return NULL;
}
/* This function handles the MellonRequire configuration directive, which
* allows the user to restrict access based on attributes received from
* the IdP.
*
* Parameters:
* cmd_parms *cmd The command structure for the MellonRequire
* configuration directive.
* void *struct_ptr Pointer to the current directory configuration.
* const char *arg Pointer to the configuration string.
*
* Returns:
* NULL on success or an error string on failure.
*/
static const char *am_set_require_slot(cmd_parms *cmd,
void *struct_ptr,
const char *arg)
{
apr_array_header_t *r;
am_dir_cfg_rec *d = struct_ptr;
char *attribute, *value;
const char **element;
attribute = ap_getword_conf(cmd->pool, &arg);
value = ap_getword_conf(cmd->pool, &arg);
if (*attribute == '\0' || *value == '\0') {
return apr_pstrcat(cmd->pool, cmd->cmd->name,
" takes at least two arguments", NULL);
}
do {
r = (apr_array_header_t *)apr_hash_get(d->require, attribute,
APR_HASH_KEY_STRING);
if (r == NULL) {
r = apr_array_make(cmd->pool, 2, sizeof(const char *));
apr_hash_set(d->require, attribute, APR_HASH_KEY_STRING, r);
}
element = (const char **)apr_array_push(r);
*element = value;
} while (*(value = ap_getword_conf(cmd->pool, &arg)) != '\0');
return NULL;
}
/* This array contains all the configuration directive which are handled
* by auth_mellon.
*/
const command_rec auth_mellon_commands[] = {
/* Global configuration directives. */
AP_INIT_TAKE1(
"MellonCacheSize",
am_set_module_config_int_slot,
(void *)APR_OFFSETOF(am_mod_cfg_rec, cache_size),
RSRC_CONF,
"The number of sessions we can keep track of at once. You must"
" restart the server before any changes to this directive will"
" take effect. The default value is 100."
),
AP_INIT_TAKE1(
"MellonLockFile",
am_set_module_config_string_slot,
(void *)APR_OFFSETOF(am_mod_cfg_rec, lock_file),
RSRC_CONF,
"The lock file for session synchronization."
" Default value is \"/tmp/mellonLock\"."
),
/* Per-location configuration directives. */
AP_INIT_TAKE1(
"MellonEnable",
am_set_enable_slot,
NULL,
OR_AUTHCFG,
"Enable auth_mellon on a location. This can be set to 'off', 'info'"
" and 'auth'. 'off' disables auth_mellon for a location, 'info'"
" will only populate the environment with attributes if the user"
" has logged in already. 'auth' will redirect the user to the IdP"
" if he hasn't logged in yet, but otherwise behaves like 'info'."
),
AP_INIT_TAKE1(
"MellonDecoder",
am_set_decoder_slot,
NULL,
OR_AUTHCFG,
"Select which decoder mod_auth_mellon should use to decode attribute"
" values. This option can be se to either 'none' or 'feide'. 'none'"
" is the default, and will store the attributes as they are received"
" from the IdP. 'feide' is for decoding base64-encoded values which"
" are separated by a underscore."
),
AP_INIT_TAKE1(
"MellonVariable",
ap_set_string_slot,
(void *)APR_OFFSETOF(am_dir_cfg_rec, varname),
OR_AUTHCFG,
"The name of the cookie which auth_mellon will set. Defaults to"
" 'cookie'. This string is appended to 'mellon-' to create the"
" cookie name, and the default name of the cookie will therefore"
" be 'mellon-cookie'."
),
AP_INIT_TAKE1(
"MellonUser",
ap_set_string_slot,
(void *)APR_OFFSETOF(am_dir_cfg_rec, userattr),
OR_AUTHCFG,
"Attribute to set as r->user. Defaults to NAME_ID, which is the"
" attribute we set to the identifier we receive from the IdP."
),
AP_INIT_TAKE2(
"MellonSetEnv",
am_set_setenv_slot,
NULL,
OR_AUTHCFG,
"Renames attributes received from the server. The format is"
" MellonSetEnv <old name> <new name>."
),
AP_INIT_RAW_ARGS(
"MellonRequire",
am_set_require_slot,
NULL,
OR_AUTHCFG,
"Attribute requirements for authorization. Allows you to restrict"
" access based on attributes received from the IdP. If you list"
" several MellonRequire configuration directives, then all of them"
" must match. Every MellonRequire can list several allowed values"
" for the attribute. The syntax is:"
" MellonRequire <attribute> <value1> [value2....]."
),
AP_INIT_TAKE1(
"MellonSessionLength",
ap_set_int_slot,
(void *)APR_OFFSETOF(am_dir_cfg_rec, session_length),
OR_AUTHCFG,
"Maximum number of seconds a session will be valid for. Defaults"
" to 86400 seconds (1 day)."
),
AP_INIT_TAKE1(
"MellonNoCookieErrorPage",
ap_set_string_slot,
(void *)APR_OFFSETOF(am_dir_cfg_rec, no_cookie_error_page),
OR_AUTHCFG,
"Web page to display if the user has disabled cookies. We will"
" return a 400 Bad Request error if this is unset and the user"
" ha disabled cookies."
),
AP_INIT_TAKE1(
"MellonSPMetadataFile",
ap_set_string_slot,
(void *)APR_OFFSETOF(am_dir_cfg_rec, sp_metadata_file),
OR_AUTHCFG,
"Full path to xml file with metadata for the SP."
),
AP_INIT_TAKE1(
"MellonSPPrivateKeyFile",
ap_set_string_slot,
(void *)APR_OFFSETOF(am_dir_cfg_rec, sp_private_key_file),
OR_AUTHCFG,
"Full path to pem file with the private key for the SP."
),
AP_INIT_TAKE1(
"MellonIdPMetadataFile",
ap_set_string_slot,
(void *)APR_OFFSETOF(am_dir_cfg_rec, idp_metadata_file),
OR_AUTHCFG,
"Full path to xml metadata file for the IdP."
),
AP_INIT_TAKE1(
"MellonIdPPublicKeyFile",
ap_set_string_slot,
(void *)APR_OFFSETOF(am_dir_cfg_rec, idp_public_key_file),
OR_AUTHCFG,
"Full path to pem file with the public key for the IdP."
),
AP_INIT_TAKE1(
"MellonEndpointPath",
am_set_endpoint_path,
NULL,
OR_AUTHCFG,
"The root directory of the SAML2 endpoints, relative to the root"
" of the web server. Default value is \"/mellon/\", which will"
" make mod_mellon to the handler for every request to"
" \"http://<servername>/mellon/*\". The path you specify must"
" be contained within the current Location directive."
),
{NULL}
};
/* This function creates and initializes a directory configuration
* object for auth_mellon.
*
* Parameters:
* apr_pool_t *p The pool we should allocate memory from.
* char *d Unused, always NULL.
*
* Returns:
* The new directory configuration object.
*/
void *auth_mellon_dir_config(apr_pool_t *p, char *d)
{
am_dir_cfg_rec *dir = apr_palloc(p, sizeof(*dir));
dir->enable_mellon = am_enable_default;
dir->decoder = am_decoder_default;
dir->varname = default_cookie_name;
dir->require = apr_hash_make(p);
dir->envattr = apr_hash_make(p);
dir->userattr = default_user_attribute;
dir->endpoint_path = default_endpoint_path;
dir->session_length = -1; /* -1 means use default. */
dir->no_cookie_error_page = NULL;
dir->sp_metadata_file = NULL;
dir->sp_private_key_file = NULL;
dir->idp_metadata_file = NULL;
dir->idp_public_key_file = NULL;
apr_thread_mutex_create(&dir->server_mutex, APR_THREAD_MUTEX_DEFAULT, p);
dir->server = NULL;
return dir;
}
/* This function merges two am_dir_cfg_rec structures.
* It will try to inherit from the base where possible.
*
* Parameters:
* apr_pool_t *p The pool we should allocate memory from.
* void *base The original structure.
* void *add The structure we should add to base.
*
* Returns:
* The merged structure.
*/
void *auth_mellon_dir_merge(apr_pool_t *p, void *base, void *add)
{
am_dir_cfg_rec *base_cfg = (am_dir_cfg_rec *)base;
am_dir_cfg_rec *add_cfg = (am_dir_cfg_rec *)add;
am_dir_cfg_rec *new_cfg;
new_cfg = (am_dir_cfg_rec *)apr_palloc(p, sizeof(*new_cfg));
new_cfg->enable_mellon = (add_cfg->enable_mellon != am_enable_default ?
add_cfg->enable_mellon :
base_cfg->enable_mellon);
new_cfg->decoder = (add_cfg->decoder != am_decoder_default ?
add_cfg->decoder :
base_cfg->decoder);
new_cfg->varname = (add_cfg->varname != default_cookie_name ?
add_cfg->varname :
base_cfg->varname);
new_cfg->require = apr_hash_copy(p,
(apr_hash_count(add_cfg->require) > 0) ?
add_cfg->require :
base_cfg->require);
new_cfg->envattr = apr_hash_copy(p,
(apr_hash_count(add_cfg->envattr) > 0) ?
add_cfg->envattr :
base_cfg->envattr);
new_cfg->userattr = (add_cfg->userattr != default_user_attribute ?
add_cfg->userattr :
base_cfg->userattr);
new_cfg->endpoint_path = (
add_cfg->endpoint_path != default_endpoint_path ?
add_cfg->endpoint_path :
base_cfg->endpoint_path
);
new_cfg->session_length = (add_cfg->session_length != -1 ?
add_cfg->session_length :
base_cfg->session_length);
new_cfg->no_cookie_error_page = (add_cfg->no_cookie_error_page != NULL ?
add_cfg->no_cookie_error_page :
base_cfg->no_cookie_error_page);
new_cfg->sp_metadata_file = (add_cfg->sp_metadata_file ?
add_cfg->sp_metadata_file :
base_cfg->sp_metadata_file);
new_cfg->sp_private_key_file = (add_cfg->sp_private_key_file ?
add_cfg->sp_private_key_file :
base_cfg->sp_private_key_file);
new_cfg->idp_metadata_file = (add_cfg->idp_metadata_file ?
add_cfg->idp_metadata_file :
base_cfg->idp_metadata_file);
new_cfg->idp_public_key_file = (add_cfg->idp_public_key_file ?
add_cfg->idp_public_key_file :
base_cfg->idp_public_key_file);
apr_thread_mutex_create(&new_cfg->server_mutex,
APR_THREAD_MUTEX_DEFAULT, p);
new_cfg->server = NULL;
return new_cfg;
}
/* This function creates a new per-server configuration.
* auth_mellon uses the server configuration to store a pointer
* to the global module configuration.
*
* Parameters:
* apr_pool_t *p The pool we should allocate memory from.
* server_rec *s The server we should add our configuration to.
*
* Returns:
* The new per-server configuration.
*/
void *auth_mellon_server_config(apr_pool_t *p, server_rec *s)
{
am_srv_cfg_rec *srv;
am_mod_cfg_rec *mod;
const char key[] = "auth_mellon_server_config";
srv = apr_palloc(p, sizeof(*srv));
/* we want to keeep our global configuration of shared memory and
* mutexes, so we try to find it in the userdata before doing anything
* else */
apr_pool_userdata_get((void **)&mod, key, p);
if (mod) {
srv->mc = mod;
return srv;
}
/* the module has not been initiated at all */
mod = apr_palloc(p, sizeof(*mod));
mod->cache_size = 100; /* ought to be enough for everybody */
mod->lock_file = "/tmp/mellonLock";
mod->init_cache_size = 0;
mod->init_lock_file = NULL;
mod->cache = NULL;
mod->lock = NULL;
apr_pool_userdata_set(mod, key, apr_pool_cleanup_null, p);
srv->mc = mod;
return srv;
}

183
auth_mellon_cookie.c Normal file
View File

@ -0,0 +1,183 @@
/*
*
* auth_mellon_cookie.c: an authentication apache module
* Copyright © 2003-2007 UNINETT (http://www.uninett.no/)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "auth_mellon.h"
/* This function retrieves the name of our cookie.
*
* Parameters:
* request_rec *r The current request. Used to find the identifier of
* the cookie. We also allocate memory from r->pool.
*
* Returns:
* The name of the cookie.
*/
static const char *am_cookie_name(request_rec *r)
{
am_dir_cfg_rec *dir_cfg;
dir_cfg = am_get_dir_cfg(r);
return apr_pstrcat(r->pool, "mellon-", dir_cfg->varname, NULL);
}
/* This functions finds the value of our cookie.
*
* Parameters:
* request_rec *r The request we should find the cookie in.
*
* Returns:
* The value of the cookie, or NULL if we don't find the cookie.
*/
const char *am_cookie_get(request_rec *r)
{
const char *name;
const char *value;
const char *cookie;
char *buffer, *end;
/* don't run for subrequests */
if (r->main) {
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
"cookie_get: Subrequest, so return NULL");
return NULL;
}
name = am_cookie_name(r);
cookie = apr_table_get(r->headers_in, "Cookie");
if(cookie == NULL) {
return NULL;
}
for(value = ap_strstr_c(cookie, name); value != NULL;
value = ap_strstr_c(value + 1, name)) {
if(value != cookie) {
/* value isn't pointing to the start of the string. */
switch(value[-1]) {
/* We allow the name in the cookie-string to be
* preceeded by [\t; ]. Note that only ' ' should be used
* by browsers. We test against the others just to be sure.
*/
case '\t':
case ';':
case ' ':
break;
default:
/* value isn't preceeded by one of the listed characters, and
* therefore we assume that it is part of another cookie.
*/
continue; /* Search for the next instance of the name. */
}
}
if(value[strlen(name)] != '=') {
/* We don't have an equal-sign right after the name. Therefore we
* assume that what we have matched is only part of a longer name.
* We continue searching.
*/
continue;
}
/* Now we have something that matches /[^ ,\t]<name>=/. The value
* (following the equal-sign) can be found at value + strlen(name) + 1.
*/
value += strlen(name) + 1;
buffer = apr_pstrdup(r->pool, value);
end = strchr(buffer, ';');
if(end) {
*end = '\0';
}
return buffer;
}
/* We didn't find the cookie. */
return NULL;
}
/* This function sets the value of our cookie.
*
* Parameters:
* request_rec *r The request we should set the cookie in.
* const char *id The value ve should store in the cookie.
*
* Returns:
* Nothing.
*/
void am_cookie_set(request_rec *r, const char *id)
{
const char *name;
char *cookie;
if (id == NULL)
return;
name = am_cookie_name(r);
cookie = apr_psprintf(r->pool, "%s=%s; Version=1; Path=/", name, id);
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
"cookie_set: %s", cookie);
/* For now we're setting the cookie in both header tables since
* it is unclear which the user will be sent. After a minor release
* this suddenly changed from headers_out to err_headers_out, so to
* be on the safe side...
*/
apr_table_addn(r->headers_out, "Set-Cookie", cookie);
apr_table_addn(r->err_headers_out, "Set-Cookie", cookie);
}
/* This function deletes the cookie.
*
* Parameters:
* request_rec *r The request we should clear the cookie in. We will
* allocate any neccesary memory from r->pool.
*
* Returns:
* Nothing.
*/
void am_cookie_delete(request_rec *r)
{
const char *name;
char *cookie;
name = am_cookie_name(r);
/* Format a cookie. To delete a cookie we set the expires-timestamp
* to the past.
*/
cookie = apr_psprintf(r->pool, "%s=NULL;"
" version=1;"
" expires=Thu, 01-Jan-1970 00:00:00 GMT;"
" path=/",
name);
apr_table_addn(r->headers_out, "Set-Cookie", cookie);
apr_table_addn(r->err_headers_out, "Set-Cookie", cookie);
}

1283
auth_mellon_handler.c Normal file

File diff suppressed because it is too large Load Diff

581
auth_mellon_httpclient.c Normal file
View File

@ -0,0 +1,581 @@
/*
*
* mod_auth_mellon.c: an authentication apache module
* Copyright © 2003-2007 UNINETT (http://www.uninett.no/)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "auth_mellon.h"
#include <curl/curl.h>
/* The size of the blocks we will allocate. */
#define AM_HC_BLOCK_SIZE 1000
/* This structure describes a single-linked list of downloaded blocks. */
typedef struct am_hc_block_s {
/* The next block we have allocated. */
struct am_hc_block_s *next;
/* The number of bytes written to this block. */
apr_size_t used;
/* The data stored in this block. */
uint8_t data[AM_HC_BLOCK_SIZE];
} am_hc_block_t;
/* This structure describes a header for the block list. */
typedef struct {
/* The pool we will allocate memory for new blocks from. */
apr_pool_t *pool;
/* The first block in the linked list of blocks. */
am_hc_block_t *first;
/* The last block in the linked list of blocks. */
am_hc_block_t *last;
} am_hc_block_header_t;
/* This function allocates and initializes a block for data copying.
*
* Parameters:
* apr_pool_t *pool The pool we should allocate the block from.
*
* Returns:
* The new block we allocated.
*/
static am_hc_block_t *am_hc_block_alloc(apr_pool_t *pool)
{
am_hc_block_t *blk;
blk = (am_hc_block_t *)apr_palloc(pool, sizeof(am_hc_block_t));
blk->next = NULL;
blk->used = 0;
return blk;
}
/* This function adds data to the end of a block, and allocates new blocks
* if the data doesn't fit in one block.
*
* Parameters:
* am_hc_block_t *block The block we should begin by appending data to.
* apr_pool_t *pool The pool we should allocate memory for new blocks
* from.
* const uint8_t *data The data we should append to the blocks.
* apr_size_t size The length of the data we should append.
*
* Returns:
* The last block written to (i.e. the next block we should write to).
*/
static am_hc_block_t *am_hc_block_write(
am_hc_block_t *block,
apr_pool_t *pool,
const uint8_t *data, apr_size_t size
)
{
apr_size_t num_cpy;
/* Find the number of bytes we should write to this block. */
num_cpy = AM_HC_BLOCK_SIZE - block->used;
if(num_cpy > size) {
num_cpy = size;
}
/* Copy data to this block. */
memcpy(&block->data[block->used], data, num_cpy);
block->used += num_cpy;
if(block->used == AM_HC_BLOCK_SIZE) {
/* This block is full. Allocate a new block, and continue
* filling it.
*/
block->next = am_hc_block_alloc(pool);
return am_hc_block_write(block->next, pool, &data[num_cpy],
size - num_cpy);
}
/* The next write should be to this block. */
return block;
}
/* This function initializes a am_hc_block_header_t structure, which
* contains information about the linked list of data blocks.
*
* Parameters:
* am_hc_block_header_t *bh Pointer to the data header whcih we
* should initialize.
* apr_pool_t *pool The pool we should allocate data from.
*
* Returns:
* Nothing.
*/
static void am_hc_block_header_init(am_hc_block_header_t *bh,
apr_pool_t *pool)
{
bh->pool = pool;
bh->first = am_hc_block_alloc(pool);
bh->last = bh->first;
}
/* This function writes data to the linked list of blocks identified by
* the stream-parameter. It matches the prototype required by curl.
*
* Parameters:
* void *data The data that should be written. It is size*nmemb
* bytes long.
* size_t size The size of each block of data that should
* be written.
* size_t nmemb The number of blocks of data that should be written.
* void *block_header A pointer to a am_hc_block_header_t structure which
* identifies the linked list we should store data in.
*
* Returns:
* The number of bytes that have been written.
*/
static size_t am_hc_data_write(void *data, size_t size, size_t nmemb,
void *data_header)
{
am_hc_block_header_t *bh;
bh = (am_hc_block_header_t *)data_header;
bh->last = am_hc_block_write(bh->last, bh->pool, (const uint8_t *)data,
size * nmemb);
return size * nmemb;
}
/* This function fetches the data which was written to the databuffers
* in the linked list which the am_hc_data_t structure keeps track of.
*
* Parameters:
* am_hc_block_header_t *bh The header telling us which data buffers
* we should extract data from.
* apr_pool_t *pool The pool we should allocate the data
* buffer from.
* void **buffer A pointer to where we should store a pointer
* to the data buffer we allocate. We will
* always add a null-terminator to the end of
* data buffer. This parameter can't be NULL.
* apr_size_t *size This is a pointer to where we will store the
* length of the data, not including the
* null-terminator we add. This parameter can
* be NULL.
*
* Returns:
* Nothing.
*/
static void am_hc_data_extract(am_hc_block_header_t *bh, apr_pool_t *pool,
void **buffer, apr_size_t *size)
{
am_hc_block_t *blk;
apr_size_t length;
uint8_t *buf;
apr_size_t pos;
/* First we find the length of the data. */
length = 0;
for(blk = bh->first; blk != NULL; blk = blk->next) {
length += blk->used;
}
/* Allocate memory for the data. Add one to the size in order to
* have space for the null-terminator.
*/
buf = (uint8_t *)apr_palloc(pool, length + 1);
/* Copy the data into the buffer. */
pos = 0;
for(blk = bh->first; blk != NULL; blk = blk->next) {
memcpy(&buf[pos], blk->data, blk->used);
pos += blk->used;
}
/* Add the null-terminator. */
buf[length] = 0;
/* Set up the return values. */
*buffer = (void *)buf;
if(size != NULL) {
*size = length;
}
}
/* This function creates a curl object and performs generic initialization
* of it.
*
* Parameters:
* request_rec *r The request we should log errors against.
* const char *uri The URI we should request.
* am_hc_block_header_t *bh The buffer curl will write response data to.
* char *curl_error A buffer of size CURL_ERROR_SIZE where curl
* will store error messages.
*
* Returns:
* A initialized curl object on succcess, or NULL on error.
*/
static CURL *am_httpclient_init_curl(request_rec *r, const char *uri,
am_hc_block_header_t *bh,
char *curl_error)
{
CURL *curl;
CURLcode res;
/* Initialize the curl object. */
curl = curl_easy_init();
if(curl == NULL) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Failed to initialize a curl object.");
return NULL;
}
/* Set up error reporting. */
res = curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_error);
if(res != CURLE_OK) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Failed to set curl error buffer: [%u]\n", res);
goto cleanup_fail;
}
/* Disable progress reporting. */
res = curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
if(res != CURLE_OK) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Failed to disable curl progress reporting: [%u] %s",
res, curl_error);
goto cleanup_fail;
}
/* Disable use of signals. */
res = curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
if(res != CURLE_OK) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Failed to disable signals in curl: [%u] %s",
res, curl_error);
goto cleanup_fail;
}
/* Set the timeout of the transfer. It is currently set to two minutes. */
res = curl_easy_setopt(curl, CURLOPT_TIMEOUT, 120L);
if(res != CURLE_OK) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Failed to set the timeout of the curl download:"
" [%u] %s", res, curl_error);
goto cleanup_fail;
}
/* Enable SSL peer certificate verification. */
res = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
if(res != CURLE_OK) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Failed to enable SSL peer certificate verification:"
" [%u] %s", res, curl_error);
goto cleanup_fail;
}
/* Enable SSL peer hostname verification. */
res = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1L);
if(res != CURLE_OK) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Failed to enable SSL peer hostname verification:"
" [%u] %s", res, curl_error);
goto cleanup_fail;
}
/* Enable fail on http error. */
res = curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
if(res != CURLE_OK) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Failed to enable failure on http error: [%u] %s",
res, curl_error);
goto cleanup_fail;
}
/* Select which uri we should download. */
res = curl_easy_setopt(curl, CURLOPT_URL, uri);
if(res != CURLE_OK) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Failed to set curl download uri to \"%s\": [%u] %s",
uri, res, curl_error);
goto cleanup_fail;
}
/* Set up data writing. */
/* Set curl write function. */
res = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, am_hc_data_write);
if(res != CURLE_OK) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Failed to set the curl write function: [%u] %s",
res, curl_error);
goto cleanup_fail;
}
/* Set the curl write function parameter. */
res = curl_easy_setopt(curl, CURLOPT_WRITEDATA, bh);
if(res != CURLE_OK) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Failed to set the curl write function data: [%u] %s",
res, curl_error);
goto cleanup_fail;
}
return curl;
cleanup_fail:
curl_easy_cleanup(curl);
return NULL;
}
/* This function downloads data from a specified URI.
*
* Parameters:
* request_rec *r The apache request this download is associated
* with. It is used for memory allocation and logging.
* const char *uri The URI we should download.
* void **buffer A pointer to where we should store a pointer to the
* downloaded data. We will always add a null-terminator
* to the data. This parameter can't be NULL.
* apr_size_t *size This is a pointer to where we will store the length
* of the downloaded data, not including the
* null-terminator we add. This parameter can be NULL.
*
* Returns:
* OK on success, or HTTP_INTERNAL_SERVER_ERROR on failure. On failure we
* will write a log message describing the error.
*/
int am_httpclient_get(request_rec *r, const char *uri,
void **buffer, apr_size_t *size)
{
am_hc_block_header_t bh;
CURL *curl;
char curl_error[CURL_ERROR_SIZE];
CURLcode res;
/* Initialize the data storage. */
am_hc_block_header_init(&bh, r->pool);
/* Initialize the curl object. */
curl = am_httpclient_init_curl(r, uri, &bh, curl_error);
if(curl == NULL) {
return HTTP_INTERNAL_SERVER_ERROR;
}
/* Do the download. */
res = curl_easy_perform(curl);
if(res != CURLE_OK) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Failed to download data from the uri \"%s\": [%u] %s",
uri, res, curl_error);
goto cleanup_fail;
}
/* Free the curl object. */
curl_easy_cleanup(curl);
/* Copy the data. */
am_hc_data_extract(&bh, r->pool, buffer, size);
return OK;
cleanup_fail:
curl_easy_cleanup(curl);
return HTTP_INTERNAL_SERVER_ERROR;
}
/* This function downloads data from a specified URI by issuing a POST
* request.
*
* Parameters:
* request_rec *r The apache request this download is
* associated with. It is used for memory
* allocation and logging.
* const char *uri The URI we should post data to.
* const void *post_data The POST data we should send.
* apr_size_t post_length The length of the POST data.
* const char *content_type The content type of the POST data. This
* parameter can be NULL, in which case the
* content type will be
* "application/x-www-form-urlencoded".
* void **buffer A pointer to where we should store a pointer
* to the downloaded data. We will always add a
* null-terminator to the data. This parameter
* can't be NULL.
* apr_size_t *size This is a pointer to where we will store the
* length of the downloaded data, not including
* the null-terminator we add. This parameter
* can be NULL.
*
* Returns:
* OK on success. On failure we will write a log message describing the
* error, and return HTTP_INTERNAL_SERVER_ERROR.
*/
int am_httpclient_post(request_rec *r, const char *uri,
const void *post_data, apr_size_t post_length,
const char *content_type,
void **buffer, apr_size_t *size)
{
am_hc_block_header_t bh;
CURL *curl;
char curl_error[CURL_ERROR_SIZE];
CURLcode res;
struct curl_slist *ctheader;
/* Initialize the data storage. */
am_hc_block_header_init(&bh, r->pool);
/* Initialize the curl object. */
curl = am_httpclient_init_curl(r, uri, &bh, curl_error);
if(curl == NULL) {
return HTTP_INTERNAL_SERVER_ERROR;
}
/* Enable POST request. */
res = curl_easy_setopt(curl, CURLOPT_POST, 1L);
if(res != CURLE_OK) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Failed to enable POST request: [%u] %s",
res, curl_error);
goto cleanup_fail;
}
/* Set POST data size. */
res = curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, post_length);
if(res != CURLE_OK) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Failed to set the POST data length: [%u] %s",
res, curl_error);
goto cleanup_fail;
}
/* Set POST data. */
res = curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data);
if(res != CURLE_OK) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Failed to set the POST data: [%u] %s",
res, curl_error);
goto cleanup_fail;
}
/* Set the content-type header. */
/* Set default content type if content_type is NULL. */
if(content_type == NULL) {
content_type = "application/x-www-form-urlencoded";
}
/* Create header list. */
ctheader = NULL;
ctheader = curl_slist_append(ctheader, apr_pstrcat(
r->pool,
"Content-Type: ",
content_type,
NULL
));
/* Set headers. */
res = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, ctheader);
if(res != CURLE_OK) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Failed to set content-type header to \"%s\": [%u] %s",
content_type, res, curl_error);
goto cleanup_fail;
}
/* Do the download. */
res = curl_easy_perform(curl);
if(res != CURLE_OK) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Failed to download data from the uri \"%s\": [%u] %s",
uri, res, curl_error);
goto cleanup_fail;
}
/* Free the curl object. */
curl_easy_cleanup(curl);
/* Free the content-type header. */
curl_slist_free_all(ctheader);
/* Copy the data. */
am_hc_data_extract(&bh, r->pool, buffer, size);
return OK;
cleanup_fail:
curl_easy_cleanup(curl);
return HTTP_INTERNAL_SERVER_ERROR;
}
/* This function downloads data from a specified URI by issuing a POST
* request.
*
* Parameters:
* request_rec *r The apache request this download is
* associated with. It is used for memory
* allocation and logging.
* const char *uri The URI we should post data to.
* const char *post_data The POST data we should send.
* const char *content_type The content type of the POST data. This
* parameter can be NULL, in which case the
* content type will be
* "application/x-www-form-urlencoded".
* void **buffer A pointer to where we should store a pointer
* to the downloaded data. We will always add a
* null-terminator to the data. This parameter
* can't be NULL.
* apr_size_t *size This is a pointer to where we will store the
* length of the downloaded data, not including
* the null-terminator we add. This parameter
* can be NULL.
*
* Returns:
* OK on success. On failure we will write a log message describing the
* error, and return HTTP_INTERNAL_SERVER_ERROR.
*/
int am_httpclient_post_str(request_rec *r, const char *uri,
const char *post_data,
const char *content_type,
void **buffer, apr_size_t *size)
{
return am_httpclient_post(r, uri, post_data, strlen(post_data),
content_type, buffer, size);
}

114
auth_mellon_session.c Normal file
View File

@ -0,0 +1,114 @@
/*
*
* auth_mellon_session.c: an authentication apache module
* Copyright © 2003-2007 UNINETT (http://www.uninett.no/)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "auth_mellon.h"
/* This function gets the session associated with a user.
*
* Parameters:
* request_rec *r The request we received from the user.
*
* Returns:
* The session associated with the user who places the request, or
* NULL if we don't have a session yes.
*/
am_cache_entry_t *am_get_request_session(request_rec *r)
{
const char *session_id;
/* Get session id from cookie. */
session_id = am_cookie_get(r);
if(session_id == NULL) {
/* Cookie is unset - we don't have a session. */
return NULL;
}
return am_cache_lock(r->server, session_id);
}
/* This function creates a new session.
*
* Parameters:
* request_rec *r The request we are processing.
*
* Returns:
* The new session, or NULL if we have an internal error.
*/
am_cache_entry_t *am_new_request_session(request_rec *r)
{
const char *session_id;
/* Generate session id. */
session_id = am_generate_session_id(r);
if(session_id == NULL) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Error creating session id.");
return NULL;
}
/* Set session id. */
am_cookie_set(r, session_id);
return am_cache_new(r->server, session_id);
}
/* This function releases the session which was returned from
* am_get_request_session.
*
* Parameters:
* request_rec *r The request we are processing.
* am_cache_entry_t *session The session we are releasing.
*
* Returns:
* Nothing.
*/
void am_release_request_session(request_rec *r, am_cache_entry_t *session)
{
am_cache_unlock(r->server, session);
}
/* This function releases and deletes the session which was returned from
* am_get_request_session.
*
* Parameters:
* request_rec *r The request we are processing.
* am_cache_entry_t *session The session we are deleting.
*
* Returns:
* Nothing.
*/
void am_delete_request_session(request_rec *r, am_cache_entry_t *session)
{
/* Delete the cookie. */
am_cookie_delete(r);
if(session == NULL) {
return;
}
/* Delete session from the session store. */
am_cache_delete(r->server, session);
}

518
auth_mellon_util.c Normal file
View File

@ -0,0 +1,518 @@
/*
*
* auth_mellon_util.c: an authentication apache module
* Copyright © 2003-2007 UNINETT (http://www.uninett.no/)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <openssl/err.h>
#include <openssl/rand.h>
#include "auth_mellon.h"
/* This function is used to get the url of the current request.
*
* Parameters:
* request_rec *r The current request.
*
* Returns:
* A string containing the full url of the current request.
* The string is allocated from r->pool.
*/
const char *am_reconstruct_url(request_rec *r)
{
const char *url;
/* This function will construct an full url for a given path relative to
* the root of the web site. To configure what hostname and port this
* function will use, see the UseCanonicalName configuration directive.
*/
url = ap_construct_url(r->pool, r->unparsed_uri, r);
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"reconstruct_url: url==\"%s\", unparsed_uri==\"%s\"", url,
r->unparsed_uri);
return url;
}
/* This function checks if the user has access according
* to the MellonRequire directives.
*
* Parameters:
* request_rec *r The current request.
* am_cache_entry_t *session The current session.
*
* Returns:
* OK if the user has access and HTTP_FORBIDDEN if he doesn't.
*/
int am_check_permissions(request_rec *r, am_cache_entry_t *session)
{
am_dir_cfg_rec *dir_cfg;
apr_hash_index_t *idx;
const char *key;
apr_array_header_t *rlist;
int i, j;
int rlist_ok;
const char **re;
dir_cfg = am_get_dir_cfg(r);
/* Iterate over all require-directives. */
for(idx = apr_hash_first(r->pool, dir_cfg->require);
idx != NULL;
idx = apr_hash_next(idx)) {
/* Get current require directive. key will be the name
* of the attribute, and rlist is a list of all allowed values.
*/
apr_hash_this(idx, (const void **)&key, NULL, (void **)&rlist);
/* Reset status to 0 before search. */
rlist_ok = 0;
re = (const char **)rlist->elts;
/* rlist is an array of all the valid values for this attribute. */
for(i = 0; i < rlist->nelts && !rlist_ok; i++) {
/* Search for a matching attribute in the session data. */
for(j = 0; j < session->size && !rlist_ok; j++) {
if(strcmp(session->env[j].varname, key) == 0 &&
strcmp(session->env[j].value, re[i]) == 0) {
/* We found a attribute with the correct name
* and value.
*/
rlist_ok = 1;
}
}
}
if(!rlist_ok) {
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
"Client failed to match required attribute \"%s\".",
key);
return HTTP_FORBIDDEN;
}
}
return OK;
}
/* This function disables caching of the response to this request. It does
* this by setting the Pragme: no-cache and Cache-Control: no-cache headers.
*
* Parameters:
* request_rec *r The request we are handling.
*
* Returns:
* Nothing.
*/
void am_set_nocache(request_rec *r)
{
/* We set headers in both r->headers_out and r->err_headers_out, so that
* we can be sure that they will be included.
*/
apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
apr_table_setn(r->err_headers_out, "Cache-Control", "no-cache");
apr_table_setn(r->headers_out, "Pragma", "no-cache");
apr_table_setn(r->err_headers_out, "Pragma", "no-cache");
}
/* This function reads the post data for a request.
*
* The data is stored in a buffer allocated from the request pool.
* After successful operation *data contains a pointer to the data and
* *length contains the length of the data.
* The data will always be null-terminated.
*
* Parameters:
* request_rec *r The request we read the form data from.
* char **data Pointer to where we will store the pointer
* to the data we read.
* apr_size_t *length Pointer to where we will store the length
* of the data we read. Pass NULL if you don't
* need to know the length of the data.
*
* Returns:
* OK if we successfully read the POST data.
* An error if we fail to read the data.
*/
int am_read_post_data(request_rec *r, char **data, apr_size_t *length)
{
apr_size_t bytes_read;
apr_size_t bytes_left;
apr_size_t len;
long read_length;
int rc;
/* Prepare to receive data from the client. We request that apache
* dechunks data if it is chunked.
*/
rc = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK);
if (rc != OK) {
return rc;
}
/* This function will send a 100 Continue response if the client is
* waiting for that. If the client isn't going to send data, then this
* function will return 0.
*/
if (!ap_should_client_block(r)) {
len = 0;
} else {
len = r->remaining;
}
if (length != NULL) {
*length = len;
}
*data = (char *)apr_palloc(r->pool, len + 1);
/* Make sure that the data is null-terminated. */
(*data)[len] = '\0';
bytes_read = 0;
bytes_left = len;
while (bytes_left > 0) {
/* Read data from the client. Returns 0 on EOF or error, the
* number of bytes otherwise.
*/
read_length = ap_get_client_block(r, &(*data)[bytes_read],
bytes_left);
if (read_length == 0) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Failed to read POST data from client.");
return HTTP_INTERNAL_SERVER_ERROR;
}
bytes_read += read_length;
bytes_left -= read_length;
}
return OK;
}
/* extract_query_parameter is a function which extracts the value of
* a given parameter in a query string. The query string can be the
* query_string parameter of a GET request, or it can be the data
* passed to the web server in a POST request.
*
* Parameters:
* apr_pool_t *pool The memory pool which the memory for
* the value will be allocated from.
* const char *query_string Either the query_string from a GET
* request, or the data from a POST
* request.
* const char *name The name of the parameter to extract.
* Note that the search for this name is
* case sensitive.
*
* Returns:
* The value of the parameter or NULL if we don't find the parameter.
*/
char *am_extract_query_parameter(apr_pool_t *pool,
const char *query_string,
const char *name)
{
const char *ip;
const char *value_end;
apr_size_t namelen;
if (query_string == NULL) {
return NULL;
}
ip = query_string;
namelen = strlen(name);
/* Find parameter. Searches for /[^&]<name>[&=$]/.
* Moves ip to the first character after the name (either '&', '='
* or '\0').
*/
for (;;) {
/* First we find the name of the parameter. */
ip = strstr(ip, name);
if (ip == NULL) {
/* Parameter not found. */
return NULL;
}
/* Then we check what is before the parameter name. */
if (ip != query_string && ip[-1] != '&') {
/* Name not preceded by [^&]. */
ip++;
continue;
}
/* And last we check what follows the parameter name. */
if (ip[namelen] != '=' && ip[namelen] != '&'
&& ip[namelen] != '\0') {
/* Name not followed by [&=$]. */
ip++;
continue;
}
/* We have found the pattern. */
ip += namelen;
break;
}
/* Now ip points to the first character after the name. If this
* character is '&' or '\0', then this field doesn't have a value.
* If this character is '=', then this field has a value.
*/
if (ip[0] == '=') {
ip += 1;
}
/* The value is from ip to '&' or to the end of the string, whichever
* comes first. */
value_end = strchr(ip, '&');
if (value_end != NULL) {
/* '&' comes first. */
return apr_pstrndup(pool, ip, value_end - ip);
} else {
/* Value continues until the end of the string. */
return apr_pstrdup(pool, ip);
}
}
/* This function urldecodes a string in-place.
*
* Parameters:
* char *data The string to urldecode.
*
* Returns:
* OK if successful or HTTP_BAD_REQUEST if any escape sequence decodes to a
* null-byte ('\0'), or if an invalid escape sequence is found.
*/
int am_urldecode(char *data)
{
int rc;
char *ip;
/* First we replace all '+'-characters with space. */
for (ip = strchr(data, '+'); ip != NULL; ip = strchr(ip, '+')) {
*ip = ' ';
}
/* Then we call ap_unescape_url_keep2f to decode all the "%xx"
* escapes. This function returns HTTP_NOT_FOUND if the string
* contains a null-byte.
*/
rc = ap_unescape_url_keep2f(data);
if (rc == HTTP_NOT_FOUND) {
return HTTP_BAD_REQUEST;
}
return rc;
}
/* This function urlencodes a string. It will escape all characters
* except a-z, A-Z, 0-9, '_' and '.'.
*
* Parameters:
* apr_pool_t *pool The pool we should allocate memory from.
* const char *str The string we should urlencode.
*
* Returns:
* The urlencoded string, or NULL if str == NULL.
*/
char *am_urlencode(apr_pool_t *pool, const char *str)
{
const char *ip;
apr_size_t length;
char *ret;
char *op;
int hi, low;
/* Return NULL if str is NULL. */
if(str == NULL) {
return NULL;
}
/* Find the length of the output string. */
length = 0;
for(ip = str; *ip; ip++) {
if(*ip >= 'a' && *ip <= 'z') {
length++;
} else if(*ip >= 'A' && *ip <= 'Z') {
length++;
} else if(*ip >= '0' && *ip <= '9') {
length++;
} else if(*ip == '_' || *ip == '.') {
length++;
} else {
length += 3;
}
}
/* Add space for null-terminator. */
length++;
/* Allocate memory for string. */
ret = (char *)apr_palloc(pool, length);
/* Encode string. */
for(ip = str, op = ret; *ip; ip++, op++) {
if(*ip >= 'a' && *ip <= 'z') {
*op = *ip;
} else if(*ip >= 'A' && *ip <= 'Z') {
*op = *ip;
} else if(*ip >= '0' && *ip <= '9') {
*op = *ip;
} else if(*ip == '_' || *ip == '.') {
*op = *ip;
} else {
*op = '%';
op++;
hi = (*ip & 0xf0) >> 4;
if(hi < 0xa) {
*op = '0' + hi;
} else {
*op = 'A' + hi - 0xa;
}
op++;
low = *ip & 0x0f;
if(low < 0xa) {
*op = '0' + low;
} else {
*op = 'A' + low - 0xa;
}
}
}
/* Make output string null-terminated. */
*op = '\0';
return ret;
}
/* This function generates a given number of (pseudo)random bytes.
* The current implementation uses OpenSSL's RAND_*-functions.
*
* Parameters:
* request_rec *r The request we are generating random bytes for.
* The request is used for configuration and
* error/warning reporting.
* void *dest The address if the buffer we should fill with data.
* apr_size_t count The number of random bytes to create.
*
* Returns:
* OK on success, or HTTP_INTERNAL_SERVER on failure.
*/
int am_generate_random_bytes(request_rec *r, void *dest, apr_size_t count)
{
int rc;
rc = RAND_pseudo_bytes((unsigned char *)dest, (int)count);
if(rc == -1) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Error generating random data: %lu",
ERR_get_error());
return HTTP_INTERNAL_SERVER_ERROR;
}
if(rc == 0) {
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
"Random data is not cryptographically strong.");
}
return OK;
}
/* This function generates a session id which is AM_SESSION_ID_LENGTH
* characters long. The session id will consist of hexadecimal characters.
*
* Parameters:
* request_rec *r The request we generate a session id for.
*
* Returns:
* The session id, made up of AM_SESSION_ID_LENGTH hexadecimal characters,
* terminated by a null-byte.
*/
char *am_generate_session_id(request_rec *r)
{
int rc;
char *ret;
int rand_data_len;
unsigned char *rand_data;
int i;
unsigned char b;
int hi, low;
ret = (char *)apr_palloc(r->pool, AM_SESSION_ID_LENGTH + 1);
/* We need to round the length of the random data _up_, in case the
* length of the session id isn't even.
*/
rand_data_len = (AM_SESSION_ID_LENGTH + 1) / 2;
/* Fill the last rand_data_len bytes of the string with
* random bytes. This allows us to overwrite from the beginning of
* the string.
*/
rand_data = (unsigned char *)&ret[AM_SESSION_ID_LENGTH - rand_data_len];
/* Generate random numbers. */
rc = am_generate_random_bytes(r, rand_data, rand_data_len);
if(rc != OK) {
return NULL;
}
/* Convert the random bytes to hexadecimal. Note that we will write
* AM_SESSION_LENGTH+1 characters if we have a non-even length of the
* session id. This is OK - we will simply overwrite the last character
* with the null-terminator afterwards.
*/
for(i = 0; i < AM_SESSION_ID_LENGTH; i += 2) {
b = rand_data[i / 2];
hi = (b >> 4) & 0xf;
low = b & 0xf;
if(hi >= 0xa) {
ret[i] = 'a' + hi - 0xa;
} else {
ret[i] = '0' + hi;
}
if(low >= 0xa) {
ret[i+1] = 'a' + low - 0xa;
} else {
ret[i+1] = '0' + low;
}
}
/* Add null-terminator- */
ret[AM_SESSION_ID_LENGTH] = '\0';
return ret;
}

3
autogen.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
autoreconf --force --install
rm -rf autom4te.cache/

66
configure.ac Normal file
View File

@ -0,0 +1,66 @@
AC_INIT([mod_auth_mellon],[0.0.6],[olavmrk@gmail.com])
AC_SUBST(NAMEVER, AC_PACKAGE_TARNAME()-AC_PACKAGE_VERSION())
# This section defines the --with-apxs2 option.
AC_ARG_WITH(
[apxs2],
[ --with-apxs2=PATH Full path to the apxs2 executable.],
[
APXS2=${withval}
],
[
APXS2='unknown'
]
)
if test "$APXS2" = 'unknown'; then
# The user didn't specify the --with-apxs2-option.
# Search for apxs2 in the specified directories
AC_PATH_PROG(APXS2, apxs2, 'unknown',
/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin)
if test "$APXS2" = 'unknown'; then
# Didn't find apxs2 in any of the specified directories.
# Search for apxs instead.
AC_PATH_PROG(APXS2, apxs, 'unknown',
/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin)
fi
fi
# Test if $APXS2 exists and is an executable.
if test ! -x "$APXS2"; then
# $APXS2 isn't a executable file.
AC_MSG_ERROR([
Could not find apxs2. Please spesify the path to apxs2
using the --with-apxs2=/full/path/to/apxs2 option.
The executable may also be named 'apxs'.
])
fi
# Replace any occurances of @APXS2@ with the value of $APXS2 in the Makefile.
AC_SUBST(APXS2)
# We need the lasso library for SAML2 communication.
PKG_CHECK_MODULES(LASSO, lasso)
AC_SUBST(LASSO_CFLAGS)
AC_SUBST(LASSO_LIBS)
# We need the curl library for HTTP-Artifact downloads.
PKG_CHECK_MODULES(CURL, libcurl)
AC_SUBST(CURL_CFLAGS)
AC_SUBST(CURL_LIBS)
# We also need openssl for its random number generator.
PKG_CHECK_MODULES(OPENSSL, openssl)
AC_SUBST(OPENSSL_CFLAGS)
AC_SUBST(OPENSSL_LIBS)
# Create Makefile from Makefile.in
AC_CONFIG_FILES([Makefile])
AC_OUTPUT

11
debian/auth_mellon.conf vendored Normal file
View File

@ -0,0 +1,11 @@
# MellonCacheSize sets the maximum number of sessions which can be active
# at once. When mod_auth_mellon reaches this limit, it will begin removing
# the least recently used sessions.
# Default: MellonCacheSize 100
#MellonCacheSize 100
# MellonLockFile is the full path to a file used for synchronizing access
# to the session data. The path should only be used by one instance of
# apache at a time.
# Default: MellonLockFile "/tmp/mellonLock"
#MellonLockFile "/tmp/mellonLock"

1
debian/auth_mellon.load vendored Normal file
View File

@ -0,0 +1 @@
LoadModule auth_mellon_module /usr/lib/apache2/modules/mod_auth_mellon.so

41
debian/changelog vendored Normal file
View File

@ -0,0 +1,41 @@
libapache2-mod-auth-mellon (0.0.6-1) unstable; urgency=low
* Update version to 0.0.6.
-- Olav Morken <olavmrk@gmail.com> Wed, 15 Aug 2007 14:03:23 +0200
libapache2-mod-auth-mellon (0.0.5-1) unstable; urgency=low
* Update version to 0.0.5.
-- Olav Morken <olavmrk@gmail.com> Wed, 8 Aug 2007 11:36:13 +0200
libapache2-mod-auth-mellon (0.0.4-1) unstable; urgency=low
* Update version to 0.0.4.
-- Olav Morken <olavmrk@gmail.com> Tue, 7 Aug 2007 10:30:43 +0200
libapache2-mod-auth-mellon (0.0.3-1) unstable; urgency=low
* Update version to 0.0.3.
-- Olav Morken <olavmrk@gmail.com> Fri, 13 Jul 2007 14:30:05 +0200
libapache2-mod-auth-mellon (0.0.2-1) unstable; urgency=low
* Update version to 0.0.2.
-- Olav Morken <olavmrk@gmail.com> Tue, 10 Jul 2007 08:55:49 +0200
libapache2-mod-auth-mellon (0.0.1-1) unstable; urgency=low
* Initial release
-- Olav Morken <olavmrk@gmail.com> Mon, 9 Jul 2007 09:52:45 +0200

1
debian/compat vendored Normal file
View File

@ -0,0 +1 @@
5

15
debian/control vendored Normal file
View File

@ -0,0 +1,15 @@
Source: libapache2-mod-auth-mellon
Section: web
Priority: extra
Maintainer: Olav Morken <olavmrk@gmail.com>
Build-Depends: debhelper (>= 5), autotools-dev, apache2-prefork-dev (>= 2.0.55), libcurl3-dev, liblasso3-dev (>= 2.1.0)
Standards-Version: 3.7.2
Package: libapache2-mod-auth-mellon
Architecture: any
Depends: openssl, apache2.2-common, libcurl3, liblasso3 (>= 2.1.0)
Description: A SAML 2.0 authentication module for Apache
mod-auth-mellon is an Apache module which enables you to authenticate
users of a web site against a SAML 2.0 enabled IdP.
After installing this package, you should run "a2enmon auth_mellon". For
configuration information, see /usr/share/doc/mod-auth-mellon/README.gz

12
debian/copyright vendored Normal file
View File

@ -0,0 +1,12 @@
This package was debianized by Olav Morken <olavmrk@gmail.com> on
Fri, 6 Jul 2007 15:25:15 +0200.
Copyright: 2007 UNINETT
License:
GPL
The Debian packaging is (C) 2007, UNINETT and
is licensed under the GPL, see `/usr/share/common-licenses/GPL'.

1
debian/dirs vendored Normal file
View File

@ -0,0 +1 @@
usr/lib/apache2/modules

2
debian/docs vendored Normal file
View File

@ -0,0 +1,2 @@
README
TODO

3
debian/install vendored Normal file
View File

@ -0,0 +1,3 @@
.libs/mod_auth_mellon.so usr/lib/apache2/modules
debian/auth_mellon.load /etc/apache2/mods-available
debian/auth_mellon.conf /etc/apache2/mods-available

108
debian/rules vendored Executable file
View File

@ -0,0 +1,108 @@
#!/usr/bin/make -f
# -*- makefile -*-
# Sample debian/rules that uses debhelper.
# This file was originally written by Joey Hess and Craig Small.
# As a special exception, when this file is copied by dh-make into a
# dh-make output file, you may use that output file without restriction.
# This special exception was added by Craig Small in version 0.37 of dh-make.
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
# These are used for cross-compiling and for saving the configure script
# from having to guess our platform (since we know it already)
DEB_HOST_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_HOST_GNU_TYPE)
DEB_BUILD_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE)
CFLAGS = -Wall -g
ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS)))
CFLAGS += -O0
else
CFLAGS += -O2
endif
configure:
./autogen.sh
config.status: configure
dh_testdir
# Add here commands to configure the package.
./configure --host=$(DEB_HOST_GNU_TYPE) --build=$(DEB_BUILD_GNU_TYPE) --prefix=/usr --mandir=\$${prefix}/share/man --infodir=\$${prefix}/share/info CFLAGS="$(CFLAGS)" LDFLAGS="-Wl,-z,defs"
build: build-stamp
build-stamp: config.status
dh_testdir
# Add here commands to compile the package.
$(MAKE)
#docbook-to-man debian/mod-auth-mellon.sgml > mod-auth-mellon.1
touch $@
clean:
dh_testdir
dh_testroot
rm -f build-stamp
# Add here commands to clean up after the build process.
-$(MAKE) distclean
ifneq "$(wildcard /usr/share/misc/config.sub)" ""
cp -f /usr/share/misc/config.sub config.sub
endif
ifneq "$(wildcard /usr/share/misc/config.guess)" ""
cp -f /usr/share/misc/config.guess config.guess
endif
dh_clean
install: build
dh_testdir
dh_testroot
dh_clean -k
dh_installdirs
dh_install
# Build architecture-independent files here.
binary-indep: build install
# We have nothing to do by default.
# Build architecture-dependent files here.
binary-arch: build install
dh_testdir
dh_testroot
dh_installchangelogs
dh_installdocs
dh_installexamples
# dh_install
# dh_installmenu
# dh_installdebconf
# dh_installlogrotate
# dh_installemacsen
# dh_installpam
# dh_installmime
# dh_python
# dh_installinit
# dh_installcron
# dh_installinfo
dh_installman
dh_link
dh_strip
dh_compress
dh_fixperms
# dh_perl
# dh_makeshlibs
dh_installdeb
# dh_shlibdeps
dh_gencontrol
dh_md5sums
dh_builddeb
binary: binary-indep binary-arch
.PHONY: build clean binary-indep binary-arch binary install

234
mod_auth_mellon.c Normal file
View File

@ -0,0 +1,234 @@
/*
*
* mod_auth_mellon.c: an authentication apache module
* Copyright © 2003-2007 UNINETT (http://www.uninett.no/)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "auth_mellon.h"
#include <curl/curl.h>
/* This function is called on server exit. It destroys the shared memory we
* allocated for storing session data, and the global mutex we used to
* synchronize access to the shared memory.
*
* The function is registered as a cleanup-function on the configuration
* pool.
*
* Parameters:
* void *p A pointer to the current server record.
*
* Returns:
* This function always return OK.
*/
static apr_status_t am_global_kill(void *p)
{
server_rec *s = (server_rec *) p;
am_mod_cfg_rec *m = am_get_mod_cfg(s);
if (m->cache) {
/* Destroy the shared memory for session data. */
apr_shm_destroy(m->cache);
m->cache = NULL;
}
if(m->lock) {
/* Destroy the mutex. */
apr_global_mutex_destroy(m->lock);
m->lock = NULL;
}
return OK;
}
/* This function is called after the configuration of the server is parsed
* (it's a post-config hook).
*
* It initializes the shared memory and the mutex which is used to protect
* the shared memory.
*
* Parameters:
* apr_pool_t *conf The configuration pool. Valid as long as this
* configuration is valid.
* apr_pool_t *log A pool for memory which is cleared after each read
* through the config files.
* apr_pool_t *tmp A pool for memory which will be destroyed after
* all the post_config hooks are run.
* server_rec *s The current server record.
*
* Returns:
* OK on successful initialization, or !OK on failure.
*/
static int am_global_init(apr_pool_t *conf, apr_pool_t *log,
apr_pool_t *tmp, server_rec *s)
{
am_cache_entry_t *table;
apr_size_t mem_size;
am_mod_cfg_rec *mod;
int rv, i;
const char userdata_key[] = "auth_mellon_init";
char buffer[512];
void *data;
/* Apache tests loadable modules by loading them (as is the only way).
* This has the effect that all modules are loaded and initialised twice,
* and we just want to initialise shared memory and mutexes when the
* module loads for real!
*
* To accomplish this, we store a piece of data as userdata in the
* process pool the first time the function is run. This data can be
* detected on all subsequent runs, and then we know that this isn't the
* first time this function runs.
*/
apr_pool_userdata_get(&data, userdata_key, s->process->pool);
if (!data) {
/* This is the first time this function is run. */
apr_pool_userdata_set((const void *)1, userdata_key,
apr_pool_cleanup_null, s->process->pool);
return OK;
}
mod = am_get_mod_cfg(s);
/* If the session store is initialized then we can't change it. */
if(mod->cache != NULL) {
ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
"auth_mellon session store already initialized -"
" reinitialization skipped.");
return OK;
}
/* Copy from the variables set by the configuration file into variables
* which will be set only once. We do this to avoid confusion if the user
* tries to change the parameters of the session store after it is
* initialized.
*/
mod->init_cache_size = mod->cache_size;
mod->init_lock_file = apr_pstrdup(conf, mod->lock_file);
/* find out the memory size of the cache */
mem_size = sizeof(am_cache_entry_t) * mod->init_cache_size;
/* register a function to clean up the whole mess on exit */
apr_pool_cleanup_register(conf, s,
am_global_kill,
apr_pool_cleanup_null);
/* Create the shared memory, exit if it fails. */
rv = apr_shm_create(&(mod->cache), mem_size, NULL, conf);
if (rv != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
"shm_create: Error [%d] \"%s\"", rv,
apr_strerror(rv, buffer, sizeof(buffer)));
return !OK;
}
/* Initialize the session table. */
table = apr_shm_baseaddr_get(mod->cache);
for (i = 0; i < mod->cache_size; i++) {
table[i].key[0] = '\0';
table[i].access = 0;
}
/* Now create the mutex that we need for locking the shared memory, then
* test for success. we really need this, so we exit on failure. */
rv = apr_global_mutex_create(&(mod->lock),
mod->init_lock_file,
APR_LOCK_DEFAULT,
conf);
if (rv != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
"mutex_create: Error [%d] \"%s\"", rv,
apr_strerror(rv, buffer, sizeof(buffer)));
return !OK;
}
return OK;
}
/* This function is run when each child process of apache starts.
* apr_global_mutex_child_init must be run on the session data mutex for
* every child process of apache.
*
* Parameters:
* apr_pool_t *p This pool is for data associated with this
* child process.
* server_rec *s The server record for the current server.
*
* Returns:
* Nothing.
*/
static void am_child_init(apr_pool_t *p, server_rec *s)
{
am_mod_cfg_rec *m = am_get_mod_cfg(s);
apr_status_t rv;
CURLcode curl_res;
/* Reinitialize the mutex for the child process. */
rv = apr_global_mutex_child_init(&(m->lock), m->init_lock_file, p);
if (rv != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
"Child process could not connect to mutex");
}
/* lasso_init() must be run before any other lasso-functions. */
lasso_init();
/* curl_global_init() should be called before any other curl
* function. Relying on curl_easy_init() to call curl_global_init()
* isn't thread safe.
*/
curl_res = curl_global_init(CURL_GLOBAL_SSL);
if(curl_res != CURLE_OK) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
"Failed to initialize curl library: %u", curl_res);
}
return;
}
static void register_hooks(apr_pool_t *p)
{
ap_hook_access_checker(am_auth_mellon_user, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_check_user_id(am_check_uid, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_post_config(am_global_init, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_child_init(am_child_init, NULL, NULL, APR_HOOK_MIDDLE);
return;
}
module AP_MODULE_DECLARE_DATA auth_mellon_module =
{
STANDARD20_MODULE_STUFF,
auth_mellon_dir_config,
auth_mellon_dir_merge,
auth_mellon_server_config,
NULL,
auth_mellon_commands,
register_hooks
};