diff options
author | olavmrk <olavmrk@a716ebb1-153a-0410-b759-cfb97c6a1b53> | 2007-09-24 09:56:34 +0000 |
---|---|---|
committer | olavmrk <olavmrk@a716ebb1-153a-0410-b759-cfb97c6a1b53> | 2007-09-24 09:56:34 +0000 |
commit | 1fa6146abe8ee1b8f224646866a855d969bbb0b6 (patch) | |
tree | 30a81b462b338316625daf61e2527a3a4262554d | |
download | mod_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-- | COPYING | 339 | ||||
-rw-r--r-- | Makefile.in | 69 | ||||
-rw-r--r-- | README | 334 | ||||
-rw-r--r-- | TODO | 4 | ||||
-rw-r--r-- | auth_mellon.h | 246 | ||||
-rw-r--r-- | auth_mellon_cache.c | 473 | ||||
-rw-r--r-- | auth_mellon_config.c | 580 | ||||
-rw-r--r-- | auth_mellon_cookie.c | 183 | ||||
-rw-r--r-- | auth_mellon_handler.c | 1283 | ||||
-rw-r--r-- | auth_mellon_httpclient.c | 581 | ||||
-rw-r--r-- | auth_mellon_session.c | 114 | ||||
-rw-r--r-- | auth_mellon_util.c | 518 | ||||
-rwxr-xr-x | autogen.sh | 3 | ||||
-rw-r--r-- | configure.ac | 66 | ||||
-rw-r--r-- | debian/auth_mellon.conf | 11 | ||||
-rw-r--r-- | debian/auth_mellon.load | 1 | ||||
-rw-r--r-- | debian/changelog | 41 | ||||
-rw-r--r-- | debian/compat | 1 | ||||
-rw-r--r-- | debian/control | 15 | ||||
-rw-r--r-- | debian/copyright | 12 | ||||
-rw-r--r-- | debian/dirs | 1 | ||||
-rw-r--r-- | debian/docs | 2 | ||||
-rw-r--r-- | debian/install | 3 | ||||
-rwxr-xr-x | debian/rules | 108 | ||||
-rw-r--r-- | mod_auth_mellon.c | 234 |
25 files changed, 5222 insertions, 0 deletions
@@ -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 @@ -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"); + } +} +?> @@ -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 +}; + |