summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorolavmrk <olavmrk@a716ebb1-153a-0410-b759-cfb97c6a1b53>2007-09-24 09:56:34 +0000
committerolavmrk <olavmrk@a716ebb1-153a-0410-b759-cfb97c6a1b53>2007-09-24 09:56:34 +0000
commit1fa6146abe8ee1b8f224646866a855d969bbb0b6 (patch)
tree30a81b462b338316625daf61e2527a3a4262554d
downloadmod_auth_mellon-1fa6146abe8ee1b8f224646866a855d969bbb0b6.tar.gz
mod_auth_mellon-1fa6146abe8ee1b8f224646866a855d969bbb0b6.tar.xz
mod_auth_mellon-1fa6146abe8ee1b8f224646866a855d969bbb0b6.zip
Initial import of version 0.0.6
git-svn-id: https://modmellon.googlecode.com/svn/trunk@3 a716ebb1-153a-0410-b759-cfb97c6a1b53
-rw-r--r--COPYING339
-rw-r--r--Makefile.in69
-rw-r--r--README334
-rw-r--r--TODO4
-rw-r--r--auth_mellon.h246
-rw-r--r--auth_mellon_cache.c473
-rw-r--r--auth_mellon_config.c580
-rw-r--r--auth_mellon_cookie.c183
-rw-r--r--auth_mellon_handler.c1283
-rw-r--r--auth_mellon_httpclient.c581
-rw-r--r--auth_mellon_session.c114
-rw-r--r--auth_mellon_util.c518
-rwxr-xr-xautogen.sh3
-rw-r--r--configure.ac66
-rw-r--r--debian/auth_mellon.conf11
-rw-r--r--debian/auth_mellon.load1
-rw-r--r--debian/changelog41
-rw-r--r--debian/compat1
-rw-r--r--debian/control15
-rw-r--r--debian/copyright12
-rw-r--r--debian/dirs1
-rw-r--r--debian/docs2
-rw-r--r--debian/install3
-rwxr-xr-xdebian/rules108
-rw-r--r--mod_auth_mellon.c234
25 files changed, 5222 insertions, 0 deletions
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..d511905
--- /dev/null
+++ b/COPYING
@@ -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.
diff --git a/Makefile.in b/Makefile.in
new file mode 100644
index 0000000..3d27348
--- /dev/null
+++ b/Makefile.in
@@ -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
diff --git a/README b/README
new file mode 100644
index 0000000..eeded77
--- /dev/null
+++ b/README
@@ -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");
+ }
+}
+?>
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..7e5f6cf
--- /dev/null
+++ b/TODO
@@ -0,0 +1,4 @@
+TODO for auth_mellon:
+
+* Change session storage to use less than 64KiB/session.
+* Optimize session lookup.
diff --git a/auth_mellon.h b/auth_mellon.h
new file mode 100644
index 0000000..39b4773
--- /dev/null
+++ b/auth_mellon.h
@@ -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 */
diff --git a/auth_mellon_cache.c b/auth_mellon_cache.c
new file mode 100644
index 0000000..e1593dd
--- /dev/null
+++ b/auth_mellon_cache.c
@@ -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;
+}
diff --git a/auth_mellon_config.c b/auth_mellon_config.c
new file mode 100644
index 0000000..250bb8f
--- /dev/null
+++ b/auth_mellon_config.c
@@ -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;
+}
+
diff --git a/auth_mellon_cookie.c b/auth_mellon_cookie.c
new file mode 100644
index 0000000..9bbb716
--- /dev/null
+++ b/auth_mellon_cookie.c
@@ -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);
+}
diff --git a/auth_mellon_handler.c b/auth_mellon_handler.c
new file mode 100644
index 0000000..919b8db
--- /dev/null
+++ b/auth_mellon_handler.c
@@ -0,0 +1,1283 @@
+/*
+ *
+ * auth_mellon_handler.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"
+
+
+static LassoServer *am_get_lasso_server(request_rec *r)
+{
+ am_dir_cfg_rec *cfg;
+ gint ret;
+
+ cfg = am_get_dir_cfg(r);
+
+ apr_thread_mutex_lock(cfg->server_mutex);
+ if(cfg->server == NULL) {
+ cfg->server = lasso_server_new(cfg->sp_metadata_file,
+ cfg->sp_private_key_file,
+ NULL, NULL);
+ if(cfg->server == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Error initializing lasso server object. Please"
+ " verify the following configuration directives:"
+ " MellonSPMetadataFile and MellonSPPrivateKeyFile.");
+
+ apr_thread_mutex_unlock(cfg->server_mutex);
+ return NULL;
+ }
+
+
+ ret = lasso_server_add_provider(cfg->server, LASSO_PROVIDER_ROLE_IDP,
+ cfg->idp_metadata_file,
+ cfg->idp_public_key_file, NULL);
+ if(ret != 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Error adding IdP to lasso server object. Please"
+ " verify the following configuration directives:"
+ " MellonIdPMetadataFile and"
+ " MellonIdPPublicKeyFile.");
+
+ lasso_server_destroy(cfg->server);
+ cfg->server = NULL;
+
+ apr_thread_mutex_unlock(cfg->server_mutex);
+ return NULL;
+ }
+ }
+
+ apr_thread_mutex_unlock(cfg->server_mutex);
+
+ return cfg->server;
+}
+
+
+/* This function stores dumps of the LassoIdentity and LassoSession objects
+ * for the given LassoProfile object. The dumps are stored in the session
+ * belonging to the current request.
+ *
+ * Parameters:
+ * request_rec *r The current request.
+ * LassoProfile *profile The profile object.
+ *
+ * Returns:
+ * OK on success or HTTP_INTERNAL_SERVER_ERROR on failure.
+ */
+static int am_save_lasso_profile_state(request_rec *r, LassoProfile *profile)
+{
+ am_cache_entry_t *am_session;
+ LassoIdentity *lasso_identity;
+ LassoSession *lasso_session;
+ gchar *identity_dump;
+ gchar *session_dump;
+ int ret;
+
+ lasso_identity = lasso_profile_get_identity(profile);
+ if(lasso_identity == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ "The current LassoProfile object doesn't contain a"
+ " LassoIdentity object.");
+ identity_dump = NULL;
+ } else {
+ identity_dump = lasso_identity_dump(lasso_identity);
+ if(identity_dump == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Could not create a identity dump from the"
+ " LassoIdentity object.");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ lasso_session = lasso_profile_get_session(profile);
+ if(lasso_session == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ "The current LassoProfile object doesn't contain a"
+ " LassoSession object.");
+ session_dump = NULL;
+ } else {
+ session_dump = lasso_session_dump(lasso_session);
+ if(session_dump == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Could not create a session dump from the"
+ " LassoSession object.");
+ if(identity_dump != NULL) {
+ g_free(identity_dump);
+ }
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+
+ am_session = am_get_request_session(r);
+ if(am_session == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Could not get auth_mellon session while attempting"
+ " to store the lasso profile state.");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* Save the profile state. */
+ ret = am_cache_set_lasso_state(am_session, identity_dump, session_dump);
+
+ am_release_request_session(r, am_session);
+
+
+ if(identity_dump != NULL) {
+ g_free(identity_dump);
+ }
+
+ if(session_dump != NULL) {
+ g_free(session_dump);
+ }
+
+ return ret;
+}
+
+
+/* This function restores dumps of a LassoIdentity object and a LassoSession
+ * object. The dumps are fetched from the session belonging to the current
+ * request and restored to the given LassoProfile object.
+ *
+ * Parameters:
+ * request_rec *r The current request.
+ * LassoProfile *profile The profile object.
+ *
+ * Returns:
+ * OK on success or HTTP_INTERNAL_SERVER_ERROR on failure.
+ */
+static int am_restore_lasso_profile_state(request_rec *r,
+ LassoProfile *profile)
+{
+ am_cache_entry_t *am_session;
+ const char *identity_dump;
+ const char *session_dump;
+ int rc;
+
+
+ am_session = am_get_request_session(r);
+ if(am_session == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Could not get auth_mellon session while attempting"
+ " to restore the lasso profile state.");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ identity_dump = am_cache_get_lasso_identity(am_session);
+ if(identity_dump != NULL) {
+ rc = lasso_profile_set_identity_from_dump(profile, identity_dump);
+ if(rc < 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Could not restore identity from dump."
+ " Lasso error: [%i] %s", rc, lasso_strerror(rc));
+ am_release_request_session(r, am_session);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ session_dump = am_cache_get_lasso_session(am_session);
+ if(session_dump != NULL) {
+ rc = lasso_profile_set_session_from_dump(profile, session_dump);
+ if(rc < 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Could not restore session from dump."
+ " Lasso error: [%i] %s", rc, lasso_strerror(rc));
+ am_release_request_session(r, am_session);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ am_release_request_session(r, am_session);
+
+ return OK;
+}
+
+
+/* This function handles an IdP initiated logout request.
+ *
+ * Parameters:
+ * request_rec *r The logout request.
+ *
+ * Returns:
+ * OK on success, or an error if any of the steps fail.
+ */
+static int am_handle_logout_request(request_rec *r)
+{
+ LassoServer *server;
+ LassoLogout *logout;
+ gint res;
+ am_cache_entry_t *session;
+
+ server = am_get_lasso_server(r);
+ if(server == NULL) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ logout = lasso_logout_new(server);
+ if(logout == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Error creating lasso logout object.");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* Restore lasso profile state. We ignore errors since we want to be able
+ * to redirect the user back to the IdP even in the case of an error.
+ */
+ am_restore_lasso_profile_state(r, LASSO_PROFILE(logout));
+
+ /* Process the logout message. Ignore missing signature. */
+ res = lasso_logout_process_request_msg(logout, r->args);
+ if(res != 0 && res != LASSO_DS_ERROR_SIGNATURE_NOT_FOUND) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Error processing logout request message."
+ " Lasso error: [%i] %s", res, lasso_strerror(res));
+
+ lasso_logout_destroy(logout);
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* Validate the logout message. Ignore missing signature. */
+ res = lasso_logout_validate_request(logout);
+ if(res != 0 && res != LASSO_DS_ERROR_SIGNATURE_NOT_FOUND) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+ "Error validating logout request."
+ " Lasso error: [%i] %s", res, lasso_strerror(res));
+ /* We continue with the logout despite the error. A error could be
+ * caused by the IdP believing that we are logged in when we are not.
+ */
+ }
+
+
+ /* Delete the session. */
+ session = am_get_request_session(r);
+ if(session != NULL) {
+ am_delete_request_session(r, session);
+ }
+
+
+ /* Create response message. */
+ res = lasso_logout_build_response_msg(logout);
+ if(res != 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Error building logout response message."
+ " Lasso error: [%i] %s", res, lasso_strerror(res));
+
+ lasso_logout_destroy(logout);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+
+ /* Set redirect target. */
+ apr_table_setn(r->headers_out, "Location",
+ apr_pstrdup(r->pool, LASSO_PROFILE(logout)->msg_url));
+
+ lasso_logout_destroy(logout);
+
+ /* HTTP_SEE_OTHER is a redirect where post-data isn't sent to the
+ * new target.
+ */
+ return HTTP_SEE_OTHER;
+}
+
+
+/* This function parses a timestamp for a SAML 2.0 condition.
+ *
+ * Parameters:
+ * request_rec *r The current request. Used for logging of errors.
+ * const char *timestamp The timestamp we should parse. Must be on
+ * the following format: "YYYY-MM-DDThh:mm:ssZ"
+ *
+ * Returns:
+ * An apr_time_t value with the timestamp, or 0 on error.
+ */
+static apr_time_t am_parse_timestamp(request_rec *r, const char *timestamp)
+{
+ int i;
+ char c;
+ const char *expected;
+ apr_time_exp_t time_exp;
+ apr_time_t res;
+ apr_status_t rc;
+
+ /* Verify length of timestamp. */
+ if(strlen(timestamp) != 20){
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+ "Invalid length of timestamp: \"%s\".", timestamp);
+ }
+
+ /* Verify components of timestamp. */
+ for(i = 0; i < 20; i++) {
+ c = timestamp[i];
+
+ expected = NULL;
+
+ switch(i) {
+
+ case 4:
+ case 7:
+ /* Matches " - - " */
+ if(c != '-') {
+ expected = "'-'";
+ }
+ break;
+
+ case 10:
+ /* Matches " T " */
+ if(c != 'T') {
+ expected = "'T'";
+ }
+ break;
+
+ case 13:
+ case 16:
+ /* Matches " : : " */
+ if(c != ':') {
+ expected = "':'";
+ }
+ break;
+
+ case 19:
+ /* Matches " Z" */
+ if(c != 'Z') {
+ expected = "'Z'";
+ }
+ break;
+
+ default:
+ /* Matches "YYYY MM DD hh mm ss " */
+ if(c < '0' || c > '9') {
+ expected = "a digit";
+ }
+ break;
+ }
+
+ if(expected != NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Invalid character in timestamp at position %i."
+ " Expected %s, got '%c'. Full timestamp: \"%s\"",
+ i, expected, c, timestamp);
+ return 0;
+ }
+ }
+
+ time_exp.tm_usec = 0;
+ time_exp.tm_sec = (timestamp[17] - '0') * 10 + (timestamp[18] - '0');
+ time_exp.tm_min = (timestamp[14] - '0') * 10 + (timestamp[15] - '0');
+ time_exp.tm_hour = (timestamp[11] - '0') * 10 + (timestamp[12] - '0');
+ time_exp.tm_mday = (timestamp[8] - '0') * 10 + (timestamp[9] - '0');
+ time_exp.tm_mon = (timestamp[5] - '0') * 10 + (timestamp[6] - '0') - 1;
+ time_exp.tm_year = (timestamp[0] - '0') * 1000 +
+ (timestamp[1] - '0') * 100 + (timestamp[2] - '0') * 10 +
+ (timestamp[3] - '0') - 1900;
+
+ time_exp.tm_wday = 0; /* Unknown. */
+ time_exp.tm_yday = 0; /* Unknown. */
+
+ time_exp.tm_isdst = 0; /* UTC, no daylight savings time. */
+ time_exp.tm_gmtoff = 0; /* UTC, no offset from UTC. */
+
+ rc = apr_time_exp_gmt_get(&res, &time_exp);
+ if(rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r,
+ "Error converting timestamp \"%s\".",
+ timestamp);
+ return 0;
+ }
+
+ return res;
+}
+
+
+/* This function sets the session expire timestamp based on NotOnOrAfter
+ * attribute of a condition element.
+ *
+ * Parameters:
+ * request_rec *r The current request. Used to log
+ * errors.
+ * am_cache_entry_t *session The current session.
+ * LassoSaml2Assertion *assertion The assertion which we will extract
+ * the conditions from.
+ *
+ * Returns:
+ * Nothing.
+ */
+static void am_handle_condition(request_rec *r, am_cache_entry_t *session,
+ LassoSaml2Assertion *assertion)
+{
+ const char *not_on_or_after;
+ apr_time_t t;
+
+
+ /* Find timestamp. */
+
+ if(assertion->Conditions == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+ "Did not receive conditions for an assertion.");
+ return;
+ }
+
+ not_on_or_after = assertion->Conditions->NotOnOrAfter;
+
+ if(not_on_or_after == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+ "Condition without NotOnOrAfter attribute.");
+ return;
+ }
+
+ /* Parse timestamp. */
+ t = am_parse_timestamp(r, not_on_or_after);
+ if(t == 0) {
+ return;
+ }
+
+ /* Updates the expires timestamp if this one is earlier than the
+ * previous timestamp.
+ */
+ am_cache_update_expires(session, t);
+}
+
+
+/* This function is for decoding and storing attributes with the feide
+ * encoding. It takes in an attribute name and a value. The value is split
+ * into multiple values. We base64 decode these values, and store them in the
+ * session data.
+ *
+ * Parameters:
+ * request_rec *r The current request.
+ * am_cache_entry_t *session The current session.
+ * const char *name Name of the attribute.
+ * const char *value The value(s) of the attribute.
+ *
+ * Returns:
+ * OK on success or an error from am_cache_env_append(...) if it was unable
+ * to store the attribute.
+ */
+static int am_store_attribute_feide(request_rec *r, am_cache_entry_t *session,
+ const char *name, const char *value)
+{
+ char *edit_value;
+ char *start;
+ char *next;
+ int len;
+ int ret;
+
+ /* We need to be able to change the value. */
+ edit_value = apr_pstrdup(r->pool, value);
+
+ for(start = edit_value; start != NULL; start = next) {
+ /* The values are separated by '_'. */
+ next = strchr(start, '_');
+
+ if(next != NULL) {
+ /* Insert null-terminator after current value. */
+ *next = '\0';
+
+ /* The next value begins at next+1. */
+ next++;
+ }
+
+ /* Now start points to the current value, which we have
+ * null-terminated. next points to the next value, or NULL if
+ * this is the last value.
+ */
+
+ /* base64-decode current value.
+ * From looking at the source of apr_base64_decode_binary, it
+ * appears to be safe to use in-place.
+ */
+ len = apr_base64_decode_binary((unsigned char *)start, start);
+
+ /* Add null-terminator at end of string. */
+ start[len] = '\0';
+
+
+ /* Store current name-value-pair. */
+ ret = am_cache_env_append(session, name, start);
+ if(ret != OK) {
+ return ret;
+ }
+ }
+
+ return OK;
+}
+
+
+/* This function is for storing attributes without any encoding. We just store
+ * the attribute as it is.
+ *
+ * Parameters:
+ * request_rec *r The current request.
+ * am_cache_entry_t *session The current session.
+ * const char *name The name of the attribute.
+ * const char *value The value of the attribute.
+ *
+ * Returns:
+ * OK on success or an error from am_cache_env_append(...) if it failed.
+ */
+static int am_store_attribute_none(request_rec *r, am_cache_entry_t *session,
+ const char *name, const char *value)
+{
+ /* Store current name-value-pair. */
+ return am_cache_env_append(session, name, value);
+}
+
+
+/* This function passes a name-value pair to the decoder selected by the
+ * MellonDecoder configuration option. The decoder will decode the value
+ * and store it in the session data.
+ *
+ * Parameters:
+ * request_rec *r The current request.
+ * am_cache_entry_t *session The current session.
+ * const char *name The name of the attribute.
+ * const char *value The value of the attribute.
+ *
+ * Returns:
+ * OK on success or an error from the attribute decoder if it failed.
+ */
+static int am_store_attribute(request_rec *r, am_cache_entry_t *session,
+ const char *name, const char *value)
+{
+ am_dir_cfg_rec *dir_cfg;
+
+ dir_cfg = am_get_dir_cfg(r);
+
+ switch(dir_cfg->decoder) {
+ case am_decoder_none:
+ return am_store_attribute_none(r, session, name, value);
+
+ case am_decoder_feide:
+ return am_store_attribute_feide(r, session, name, value);
+
+ default:
+ return am_store_attribute_none(r, session, name, value);
+ }
+}
+
+
+/* This function iterates over a list of assertion elements, and adds all the
+ * attributes it finds to the session data for the current user.
+ *
+ * Parameters:
+ * request_rec *r The current request.
+ * const char *name_id The name identifier we received from the IdP.
+ * GList *assertions A list of LassoSaml2Assertion objects.
+ *
+ * Returns:
+ * HTTP_BAD_REQUEST if we couldn't find the session id of the user, or
+ * OK if no error occured.
+ */
+static int add_attributes(request_rec *r, const char *name_id,
+ GList *assertions)
+{
+ am_dir_cfg_rec *dir_cfg;
+ am_cache_entry_t *session;
+ GList *asrt_itr;
+ LassoSaml2Assertion *assertion;
+ GList *atr_stmt_itr;
+ LassoSaml2AttributeStatement *atr_stmt;
+ GList *atr_itr;
+ LassoSaml2Attribute *attribute;
+ GList *value_itr;
+ LassoSaml2AttributeValue *value;
+ LassoMiscTextNode *value_text;
+ int ret;
+
+ dir_cfg = am_get_dir_cfg(r);
+
+ /* Get the session this request belongs to. */
+ session = am_get_request_session(r);
+ if(session == NULL) {
+ if(am_cookie_get(r) == NULL) {
+ /* Missing cookie. */
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
+ "User has disabled cookies, or has lost"
+ " the cookie before returning from the SAML2"
+ " login server.");
+ if(dir_cfg->no_cookie_error_page != NULL) {
+ apr_table_setn(r->headers_out, "Location",
+ dir_cfg->no_cookie_error_page);
+ return HTTP_SEE_OTHER;
+ } else {
+ /* Return 400 Bad Request when the user hasn't set a
+ * no-cookie error page.
+ */
+ return HTTP_BAD_REQUEST;
+ }
+ } else {
+ /* Missing session data. */
+ ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r,
+ "User has returned from the IdP with a session"
+ " id we can't locate in the table. This may be"
+ " caused by the MellonCacheSize being set to"
+ " low.");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ /* Set expires to whatever is set by MellonSessionLength. */
+ if(dir_cfg->session_length == -1) {
+ /* -1 means "use default. The current default is 86400 seconds. */
+ am_cache_update_expires(session, apr_time_now()
+ + apr_time_make(86400, 0));
+ } else {
+ am_cache_update_expires(session, apr_time_now()
+ + apr_time_make(dir_cfg->session_length, 0));
+ }
+
+ /* Mark user as logged in. */
+ session->logged_in = 1;
+
+ /* Save session information. */
+ ret = am_cache_env_append(session, "NAME_ID", name_id);
+ if(ret != OK) {
+ am_release_request_session(r, session);
+ return ret;
+ }
+
+ /* assertions is a list of LassoSaml2Assertion objects. */
+ for(asrt_itr = g_list_first(assertions); asrt_itr != NULL;
+ asrt_itr = g_list_next(asrt_itr)) {
+
+ assertion = LASSO_SAML2_ASSERTION(asrt_itr->data);
+
+ /* Update expires timestamp of session. */
+ am_handle_condition(r, session, assertion);
+
+ /* assertion->AttributeStatement is a list of
+ * LassoSaml2AttributeStatement objects.
+ */
+ for(atr_stmt_itr = g_list_first(assertion->AttributeStatement);
+ atr_stmt_itr != NULL;
+ atr_stmt_itr = g_list_next(atr_stmt_itr)) {
+
+ atr_stmt = LASSO_SAML2_ATTRIBUTE_STATEMENT(atr_stmt_itr->data);
+
+ /* atr_stmt->Attribute is list of LassoSaml2Attribute objects. */
+ for(atr_itr = g_list_first(atr_stmt->Attribute);
+ atr_itr != NULL;
+ atr_itr = g_list_next(atr_itr)) {
+
+ attribute = LASSO_SAML2_ATTRIBUTE(atr_itr->data);
+
+ /* attribute->AttributeValue is a list of
+ * LassoSaml2AttributeValue objects.
+ */
+ for(value_itr = g_list_first(attribute->AttributeValue);
+ value_itr != NULL;
+ value_itr = g_list_next(value_itr)) {
+
+ value = LASSO_SAML2_ATTRIBUTE_VALUE(
+ attribute->AttributeValue->data
+ );
+
+ /* value->any is a list with the child nodes of the
+ * AttributeValue element.
+ *
+ * We assume that the list contains a single text node.
+ */
+ if(value->any == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+ "AttributeValue element was empty.");
+ continue;
+ }
+
+ /* Verify that this is a LassoMiscTextNode object. */
+ if(!LASSO_IS_MISC_TEXT_NODE(value->any->data)) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+ "AttributeValue element contained an "
+ " element which wasn't a text node.");
+ continue;
+ }
+
+ value_text = LASSO_MISC_TEXT_NODE(value->any->data);
+
+
+ /* Decode and save the attribute. */
+ ret = am_store_attribute(r, session, attribute->Name,
+ value_text->content);
+ if(ret != OK) {
+ am_release_request_session(r, session);
+ return ret;
+ }
+ }
+ }
+ }
+
+ /* TODO: lasso only verifies the signature on the _first_ asserion
+ * element. Therefore we can't trust any of following assertions.
+ * If the Response-element is signed then we can trust all the
+ * assertions, but we have no way to find what element is signed.
+ */
+ break;
+ }
+
+ am_release_request_session(r, session);
+
+ return OK;
+}
+
+
+/* This function finishes handling of a login response after it has been parsed
+ * by the HTTP-POST or HTTP-Artifact handler.
+ *
+ * Parameters:
+ * request_rec *r The current request.
+ * LassoLogin *login The login object which has been initialized with the
+ * data we have received from the IdP.
+ * char *relay_state The RelayState parameter from the POST data or from
+ * the request url. This parameter is urlencoded, and
+ * this function will urldecode it in-place. Therefore it
+ * must be possible to overwrite the data.
+ *
+ * Returns:
+ * A HTTP status code which should be returned to the client.
+ */
+static int am_handle_reply_common(request_rec *r, LassoLogin *login,
+ char *relay_state)
+{
+ const char *name_id;
+ GList *assertions;
+ int rc;
+
+ if(LASSO_PROFILE(login)->nameIdentifier == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "No acceptable name identifier found in"
+ " SAML 2.0 response.");
+ lasso_login_destroy(login);
+ return HTTP_BAD_REQUEST;
+ }
+
+ name_id = LASSO_SAML2_NAME_ID(LASSO_PROFILE(login)->nameIdentifier)
+ ->content;
+
+ assertions = LASSO_SAMLP2_RESPONSE(LASSO_PROFILE(login)->response)
+ ->Assertion;
+
+
+ rc = add_attributes(r, name_id, assertions);
+ if(rc != OK) {
+ lasso_login_destroy(login);
+ return rc;
+ }
+
+ rc = lasso_login_accept_sso(login);
+ if(rc < 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Unable to accept SSO message."
+ " Lasso error: [%i] %s", rc, lasso_strerror(rc));
+ lasso_login_destroy(login);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+
+ /* Save the profile state. */
+ rc = am_save_lasso_profile_state(r, LASSO_PROFILE(login));
+ if(rc != OK) {
+ lasso_login_destroy(login);
+ return rc;
+ }
+
+ lasso_login_destroy(login);
+
+
+ /* No RelayState - we don't know what to do. */
+ if(relay_state == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "RelayState wasn't included in reply from IdP.");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ rc = am_urldecode(relay_state);
+ if (rc != OK) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r,
+ "Could not urldecode RelayState value.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ apr_table_setn(r->headers_out, "Location",
+ relay_state);
+
+ /* HTTP_SEE_OTHER should be a redirect where the browser doesn't repeat
+ * the POST data to the new page.
+ */
+ return HTTP_SEE_OTHER;
+}
+
+
+/* This function handles responses to login requests received with the
+ * HTTP-POST binding.
+ *
+ * Parameters:
+ * request_rec *r The request we received.
+ *
+ * Returns:
+ * HTTP_SEE_OTHER on success, or an error on failure.
+ */
+static int am_handle_post_reply(request_rec *r)
+{
+ int rc;
+ char *post_data;
+ char *saml_response;
+ LassoServer *server;
+ LassoLogin *login;
+ char *relay_state;
+
+ /* Make sure that this is a POST request. */
+ if(r->method_number != M_POST) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Exptected POST request for HTTP-POST endpoint."
+ " Got a %s request instead.", r->method);
+
+ /* According to the documentation for request_rec, a handler which
+ * doesn't handle a request method, should set r->allowed to the
+ * methods it handles, and return DECLINED.
+ * However, the default handler handles GET-requests, so for GET
+ * requests the handler should return HTTP_METHOD_NOT_ALLOWED.
+ */
+ r->allowed = M_POST;
+
+ if(r->method_number == M_GET) {
+ return HTTP_METHOD_NOT_ALLOWED;
+ } else {
+ return DECLINED;
+ }
+ }
+
+ /* Read POST-data. */
+ rc = am_read_post_data(r, &post_data, NULL);
+ if (rc != OK) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r,
+ "Error reading POST data.");
+ return rc;
+ }
+
+ /* Extract the SAMLResponse-field from the data. */
+ saml_response = am_extract_query_parameter(r->pool, post_data,
+ "SAMLResponse");
+ if (saml_response == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r,
+ "Could not find SAMLResponse field in POST data.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ rc = am_urldecode(saml_response);
+ if (rc != OK) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r,
+ "Could not urldecode SAMLResponse value.");
+ return rc;
+ }
+
+ server = am_get_lasso_server(r);
+ if(server == NULL) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ login = lasso_login_new(server);
+ if (login == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Failed to initialize LassoLogin object.");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* Process login responce. */
+ rc = lasso_login_process_authn_response_msg(login, saml_response);
+ if (rc != 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Error processing authn response."
+ " Lasso error: [%i] %s", rc, lasso_strerror(rc));
+
+ lasso_login_destroy(login);
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* Extract RelayState parameter. */
+ relay_state = am_extract_query_parameter(r->pool, post_data,
+ "RelayState");
+
+ /* Finish handling the reply with the common handler. */
+ return am_handle_reply_common(r, login, relay_state);
+}
+
+
+/* This function handles responses to login requests which use the
+ * HTTP-Artifact binding.
+ *
+ * Parameters:
+ * request_rec *r The request we received.
+ *
+ * Returns:
+ * HTTP_SEE_OTHER on success, or an error on failure.
+ */
+static int am_handle_artifact_reply(request_rec *r)
+{
+ int rc;
+ LassoServer *server;
+ LassoLogin *login;
+ char *response;
+ char *relay_state;
+
+ /* Make sure that this is a GET request. */
+ if(r->method_number != M_GET) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Exptected GET request for the HTTP-Artifact endpoint."
+ " Got a %s request instead.", r->method);
+
+ /* According to the documentation for request_rec, a handler which
+ * doesn't handle a request method, should set r->allowed to the
+ * methods it handles, and return DECLINED.
+ * However, the default handler handles GET-requests, so for GET
+ * requests the handler should return HTTP_METHOD_NOT_ALLOWED.
+ * This endpoints handles GET requests, so it isn't necessary to
+ * check for method_number == M_GET.
+ */
+ r->allowed = M_GET;
+
+ return DECLINED;
+ }
+
+ server = am_get_lasso_server(r);
+ if(server == NULL) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ login = lasso_login_new(server);
+ if (login == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Failed to initialize LassoLogin object.");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* Parse artifact url. */
+ rc = lasso_login_init_request(login, r->args,
+ LASSO_HTTP_METHOD_ARTIFACT_GET);
+ if(rc < 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Failed to handle login response."
+ " Lasso error: [%i] %s", rc, lasso_strerror(rc));
+ lasso_login_destroy(login);
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* Prepare SOAP request. */
+ rc = lasso_login_build_request_msg(login);
+ if(rc < 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Failed to prepare SOAP message for HTTP-Artifact"
+ " resolution."
+ " Lasso error: [%i] %s", rc, lasso_strerror(rc));
+ lasso_login_destroy(login);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* Do the SOAP request. */
+ rc = am_httpclient_post_str(
+ r,
+ LASSO_PROFILE(login)->msg_url,
+ LASSO_PROFILE(login)->msg_body,
+ "text/xml",
+ (void**)&response,
+ NULL
+ );
+ if(rc != OK) {
+ lasso_login_destroy(login);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ rc = lasso_login_process_response_msg(login, response);
+ if(rc != 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Failed to handle HTTP-Artifact response data."
+ " Lasso error: [%i] %s", rc, lasso_strerror(rc));
+ lasso_login_destroy(login);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* Extract the RelayState parameter. */
+ relay_state = am_extract_query_parameter(r->pool, r->args,
+ "RelayState");
+
+ /* Finish handling the reply with the common handler. */
+ return am_handle_reply_common(r, login, relay_state);
+}
+
+
+/* This function takes a request for an endpoint and passes it on to the
+ * correct handler function.
+ *
+ * Parameters:
+ * request_rec *r The request we are currently handling.
+ *
+ * Returns:
+ * The return value of the endpoint handler function,
+ * or HTTP_NOT_FOUND if we don't have a handler for the requested
+ * endpoint.
+ */
+static int am_endpoint_handler(request_rec *r)
+{
+ const char *endpoint;
+ am_dir_cfg_rec *dir = am_get_dir_cfg(r);
+
+ /* r->uri starts with cfg->endpoint_path, so we can find the endpoint
+ * by extracting the string following chf->endpoint_path.
+ */
+ endpoint = &r->uri[strlen(dir->endpoint_path)];
+
+
+ if(!strcmp(endpoint, "postResponse")) {
+ return am_handle_post_reply(r);
+ } else if(!strcmp(endpoint, "artifactResponse")) {
+ return am_handle_artifact_reply(r);
+ } else if(!strcmp(endpoint, "logoutRequest")) {
+ return am_handle_logout_request(r);
+ } else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Endpoint \"%s\" not handled by mod_auth_mellon.",
+ endpoint);
+
+ return HTTP_NOT_FOUND;
+ }
+
+}
+
+
+static int am_auth_new_ticket(request_rec *r)
+{
+ LassoServer *server;
+ LassoLogin *login;
+ LassoSamlp2AuthnRequest *request;
+ gint ret;
+ char *redirect_to;
+
+ /* Create session. */
+ am_release_request_session(r, am_new_request_session(r));
+
+
+ server = am_get_lasso_server(r);
+ if(server == NULL) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ login = lasso_login_new(server);
+ if(login == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Error creating LassoLogin object from LassoServer.");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ret = lasso_login_init_authn_request(login, NULL,
+ LASSO_HTTP_METHOD_REDIRECT);
+ if(ret != 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Error creating login request."
+ " Lasso error: [%i] %s", ret, lasso_strerror(ret));
+ lasso_login_destroy(login);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ request = LASSO_SAMLP2_AUTHN_REQUEST(LASSO_PROFILE(login)->request);
+
+ request->ForceAuthn = FALSE;
+ request->IsPassive = FALSE;
+
+ request->NameIDPolicy->Format
+ = g_strdup(LASSO_SAML2_NAME_IDENTIFIER_FORMAT_TRANSIENT);
+
+ request->NameIDPolicy->AllowCreate = TRUE;
+
+ LASSO_SAMLP2_REQUEST_ABSTRACT(request)->Consent
+ = g_strdup(LASSO_SAML2_CONSENT_IMPLICIT);
+
+ LASSO_PROFILE(login)->msg_relayState = g_strdup(am_reconstruct_url(r));
+
+ ret = lasso_login_build_authn_request_msg(login);
+ if(ret != 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Error building login request."
+ " Lasso error: [%i] %s", ret, lasso_strerror(ret));
+ lasso_login_destroy(login);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+
+ redirect_to = apr_pstrdup(r->pool, LASSO_PROFILE(login)->msg_url);
+
+ /* Check if the lasso library added the RelayState. If lasso didn't add
+ * a RelayState parameter, then we add one ourself. This should hopefully
+ * be removed in the future.
+ */
+ if(strstr(redirect_to, "&RelayState=") == NULL
+ && strstr(redirect_to, "?RelayState=") == NULL) {
+ /* The url didn't contain the relaystate parameter. */
+ redirect_to = apr_pstrcat(
+ r->pool, redirect_to, "&RelayState=",
+ am_urlencode(r->pool, LASSO_PROFILE(login)->msg_relayState),
+ NULL
+ );
+ }
+
+ apr_table_setn(r->headers_out, "Location", redirect_to);
+
+ lasso_login_destroy(login);
+
+ /* We don't want to include POST data (in case this was a POST request). */
+ return HTTP_SEE_OTHER;
+}
+
+
+int am_auth_mellon_user(request_rec *r)
+{
+ am_dir_cfg_rec *dir = am_get_dir_cfg(r);
+ int return_code = HTTP_UNAUTHORIZED;
+ am_cache_entry_t *session;
+
+ /* check if we are a subrequest. if we are, then just return OK
+ * without any checking since these cannot be injected (heh). */
+ if (r->main)
+ return OK;
+
+ /* Check that the user has enabled authentication for this directory. */
+ if(dir->enable_mellon == am_enable_off
+ || dir->enable_mellon == am_enable_default) {
+ return DECLINED;
+ }
+
+
+ /* Disable all caching within this location. */
+ am_set_nocache(r);
+
+ /* Check if this is a request for one of our endpoints. We check if
+ * the uri starts with the path set with the MellonEndpointPath
+ * configuration directive.
+ */
+ if(strstr(r->uri, dir->endpoint_path) == r->uri) {
+ return am_endpoint_handler(r);
+ }
+
+ /* Get the session of this request. */
+ session = am_get_request_session(r);
+
+
+ if(dir->enable_mellon == am_enable_auth) {
+ /* This page requires the user to be authenticated and authorized. */
+
+ if(session == NULL || !session->logged_in) {
+ /* We don't have a valid session. */
+
+ if(session) {
+ /* Release the session. */
+ am_release_request_session(r, session);
+ }
+
+ /* Send the user to the authentication page on the IdP. */
+ return am_auth_new_ticket(r);
+ }
+
+ /* Verify that the user has access to this resource. */
+ return_code = am_check_permissions(r, session);
+ if(return_code != OK) {
+ am_release_request_session(r, session);
+
+ return return_code;
+ }
+
+
+ /* The user has been authenticated, and we can now populate r->user
+ * and the r->subprocess_env with values from the session store.
+ */
+ am_cache_env_populate(r, session);
+
+ /* Release the session. */
+ am_release_request_session(r, session);
+
+ return OK;
+
+ } else {
+ /* dir->enable_mellon == am_enable_info:
+ * We should pass information about the user to the web application
+ * if the user is authorized to access this resource.
+ * However, we shouldn't attempt to do any access control.
+ */
+
+ if(session != NULL
+ && session->logged_in
+ && am_check_permissions(r, session) == OK) {
+
+ /* The user is authenticated and has access to the resource.
+ * Now we populate the environment with information about
+ * the user.
+ */
+ am_cache_env_populate(r, session);
+ }
+
+ if(session != NULL) {
+ /* Release the session. */
+ am_release_request_session(r, session);
+ }
+
+ /* We shouldn't really do any access control, so we always return
+ * DECLINED.
+ */
+ return DECLINED;
+ }
+}
+
+
+int am_check_uid(request_rec *r)
+{
+ am_cache_entry_t *session;
+ int return_code = HTTP_UNAUTHORIZED;
+
+ /* check if we are a subrequest. if we are, then just return OK
+ * without any checking since these cannot be injected (heh). */
+ if (r->main)
+ return OK;
+
+
+ /* Get the session of this request. */
+ session = am_get_request_session(r);
+
+ /* If we don't have a session, then we can't authorize the user. */
+ if(session == NULL) {
+ return HTTP_UNAUTHORIZED;
+ }
+
+ /* If the user isn't logged in, then we can't authorize the user. */
+ if(!session->logged_in) {
+ return HTTP_UNAUTHORIZED;
+ }
+
+ /* Verify that the user has access to this resource. */
+ return_code = am_check_permissions(r, session);
+ if(return_code != OK) {
+ am_release_request_session(r, session);
+ return HTTP_UNAUTHORIZED;
+ }
+
+ /* The user has been authenticated, and we can now populate r->user
+ * and the r->subprocess_env with values from the session store.
+ */
+ am_cache_env_populate(r, session);
+
+ /* Release the session. */
+ am_release_request_session(r, session);
+
+ return OK;
+}
diff --git a/auth_mellon_httpclient.c b/auth_mellon_httpclient.c
new file mode 100644
index 0000000..6f2f04b
--- /dev/null
+++ b/auth_mellon_httpclient.c
@@ -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);
+}
diff --git a/auth_mellon_session.c b/auth_mellon_session.c
new file mode 100644
index 0000000..580b2e2
--- /dev/null
+++ b/auth_mellon_session.c
@@ -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);
+}
diff --git a/auth_mellon_util.c b/auth_mellon_util.c
new file mode 100644
index 0000000..4f371cc
--- /dev/null
+++ b/auth_mellon_util.c
@@ -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;
+}
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..832703f
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+autoreconf --force --install
+rm -rf autom4te.cache/
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..837f402
--- /dev/null
+++ b/configure.ac
@@ -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
diff --git a/debian/auth_mellon.conf b/debian/auth_mellon.conf
new file mode 100644
index 0000000..2d3d336
--- /dev/null
+++ b/debian/auth_mellon.conf
@@ -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"
diff --git a/debian/auth_mellon.load b/debian/auth_mellon.load
new file mode 100644
index 0000000..e085284
--- /dev/null
+++ b/debian/auth_mellon.load
@@ -0,0 +1 @@
+LoadModule auth_mellon_module /usr/lib/apache2/modules/mod_auth_mellon.so
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..b3660fd
--- /dev/null
+++ b/debian/changelog
@@ -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
+
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..7ed6ff8
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+5
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..459023f
--- /dev/null
+++ b/debian/control
@@ -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
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..fbeea05
--- /dev/null
+++ b/debian/copyright
@@ -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'.
diff --git a/debian/dirs b/debian/dirs
new file mode 100644
index 0000000..8e8d38b
--- /dev/null
+++ b/debian/dirs
@@ -0,0 +1 @@
+usr/lib/apache2/modules
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 0000000..724e084
--- /dev/null
+++ b/debian/docs
@@ -0,0 +1,2 @@
+README
+TODO
diff --git a/debian/install b/debian/install
new file mode 100644
index 0000000..333941e
--- /dev/null
+++ b/debian/install
@@ -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
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..0e9f4ec
--- /dev/null
+++ b/debian/rules
@@ -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
diff --git a/mod_auth_mellon.c b/mod_auth_mellon.c
new file mode 100644
index 0000000..0f1ee5e
--- /dev/null
+++ b/mod_auth_mellon.c
@@ -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
+};
+