From 105a6c1315c94784767506aacc3382673b3fb782 Mon Sep 17 00:00:00 2001 From: Alberto Di Meglio Date: Tue, 26 Oct 2004 17:54:24 +0000 Subject: [PATCH] First version of this file --- org.gridsite.core/CHANGES | 114 ++ org.gridsite.core/INSTALL | 39 + org.gridsite.core/LICENSE | 47 + org.gridsite.core/README | 3 + org.gridsite.core/VERSION | 4 + org.gridsite.core/build.xml | 195 ++ org.gridsite.core/doc/admin.html | 103 ++ org.gridsite.core/doc/build-apache2.sh | 79 + org.gridsite.core/doc/config.html | 192 ++ org.gridsite.core/doc/gacl.html | 84 + org.gridsite.core/doc/htcp.1 | 138 ++ org.gridsite.core/doc/htll.1 | 1 + org.gridsite.core/doc/htls.1 | 1 + org.gridsite.core/doc/htmkdir.1 | 1 + org.gridsite.core/doc/htrm.1 | 1 + org.gridsite.core/doc/httpd-fileserver.conf | 145 ++ org.gridsite.core/doc/httpd-webserver.conf | 217 +++ org.gridsite.core/doc/index.html | 92 + org.gridsite.core/doc/install.html | 148 ++ org.gridsite.core/doc/library.html | 1 + org.gridsite.core/doc/module.html | 271 +++ org.gridsite.core/doc/urlencode.1 | 46 + org.gridsite.core/doc/user.html | 302 ++++ org.gridsite.core/interface/gridsite-gacl.h | 188 ++ org.gridsite.core/interface/gridsite.h | 269 +++ org.gridsite.core/project/build.properties | 0 org.gridsite.core/project/configure.properties.xml | 8 + org.gridsite.core/project/dependencies.properties | 9 + org.gridsite.core/project/gridsite.core.csf.xml | 255 +++ org.gridsite.core/project/properties.xml | 53 + org.gridsite.core/project/taskdefs.xml | 31 + org.gridsite.core/project/version.properties | 3 + org.gridsite.core/src/Doxyfile | 993 +++++++++++ org.gridsite.core/src/Makefile | 204 +++ org.gridsite.core/src/doxygen.css | 49 + org.gridsite.core/src/doxyheader.html | 1 + org.gridsite.core/src/gaclexample.c | 147 ++ org.gridsite.core/src/gridsite.spec | 76 + org.gridsite.core/src/grst_admin.h | 57 + org.gridsite.core/src/grst_admin_file.c | 1571 +++++++++++++++++ org.gridsite.core/src/grst_admin_gacl.c | 968 ++++++++++ org.gridsite.core/src/grst_admin_main.c | 365 ++++ org.gridsite.core/src/grst_gacl.c | 1136 ++++++++++++ org.gridsite.core/src/grst_http.c | 407 +++++ org.gridsite.core/src/grst_x509.c | 846 +++++++++ org.gridsite.core/src/htcp | Bin 0 -> 20971 bytes org.gridsite.core/src/htcp.c | 1127 ++++++++++++ org.gridsite.core/src/mod_gridsite.c | 1853 ++++++++++++++++++++ org.gridsite.core/src/mod_ssl-private.h | 106 ++ org.gridsite.core/src/real-gridsite-admin.cgi | Bin 0 -> 81444 bytes org.gridsite.core/src/roffit | 370 ++++ org.gridsite.core/src/urlencode.c | 73 + 52 files changed, 13389 insertions(+) create mode 100644 org.gridsite.core/CHANGES create mode 100644 org.gridsite.core/INSTALL create mode 100644 org.gridsite.core/LICENSE create mode 100644 org.gridsite.core/README create mode 100644 org.gridsite.core/VERSION create mode 100644 org.gridsite.core/build.xml create mode 100644 org.gridsite.core/doc/admin.html create mode 100644 org.gridsite.core/doc/build-apache2.sh create mode 100644 org.gridsite.core/doc/config.html create mode 100644 org.gridsite.core/doc/gacl.html create mode 100644 org.gridsite.core/doc/htcp.1 create mode 100644 org.gridsite.core/doc/htll.1 create mode 100644 org.gridsite.core/doc/htls.1 create mode 100644 org.gridsite.core/doc/htmkdir.1 create mode 100644 org.gridsite.core/doc/htrm.1 create mode 100644 org.gridsite.core/doc/httpd-fileserver.conf create mode 100644 org.gridsite.core/doc/httpd-webserver.conf create mode 100644 org.gridsite.core/doc/index.html create mode 100644 org.gridsite.core/doc/install.html create mode 100644 org.gridsite.core/doc/library.html create mode 100644 org.gridsite.core/doc/module.html create mode 100644 org.gridsite.core/doc/urlencode.1 create mode 100644 org.gridsite.core/doc/user.html create mode 100644 org.gridsite.core/interface/gridsite-gacl.h create mode 100644 org.gridsite.core/interface/gridsite.h create mode 100644 org.gridsite.core/project/build.properties create mode 100644 org.gridsite.core/project/configure.properties.xml create mode 100644 org.gridsite.core/project/dependencies.properties create mode 100644 org.gridsite.core/project/gridsite.core.csf.xml create mode 100644 org.gridsite.core/project/properties.xml create mode 100644 org.gridsite.core/project/taskdefs.xml create mode 100644 org.gridsite.core/project/version.properties create mode 100644 org.gridsite.core/src/Doxyfile create mode 100644 org.gridsite.core/src/Makefile create mode 100644 org.gridsite.core/src/doxygen.css create mode 100644 org.gridsite.core/src/doxyheader.html create mode 100644 org.gridsite.core/src/gaclexample.c create mode 100644 org.gridsite.core/src/gridsite.spec create mode 100644 org.gridsite.core/src/grst_admin.h create mode 100644 org.gridsite.core/src/grst_admin_file.c create mode 100644 org.gridsite.core/src/grst_admin_gacl.c create mode 100644 org.gridsite.core/src/grst_admin_main.c create mode 100644 org.gridsite.core/src/grst_gacl.c create mode 100644 org.gridsite.core/src/grst_http.c create mode 100644 org.gridsite.core/src/grst_x509.c create mode 100644 org.gridsite.core/src/htcp create mode 100644 org.gridsite.core/src/htcp.c create mode 100644 org.gridsite.core/src/mod_gridsite.c create mode 100644 org.gridsite.core/src/mod_ssl-private.h create mode 100644 org.gridsite.core/src/real-gridsite-admin.cgi create mode 100755 org.gridsite.core/src/roffit create mode 100644 org.gridsite.core/src/urlencode.c diff --git a/org.gridsite.core/CHANGES b/org.gridsite.core/CHANGES new file mode 100644 index 0000000..c7c17d5 --- /dev/null +++ b/org.gridsite.core/CHANGES @@ -0,0 +1,114 @@ +* Thu Jul 22 2004 Andrew McNab +- ==== GridSite version 1.0.4 ==== +* Mon Jul 19 2004 Andrew McNab +- Changes in line with EGEE SCM - most importantly + the top level directory becomes org.gridsite.core +* Mon Jul 19 2004 Andrew McNab +- ==== GridSite version 1.0.3 ==== +* Mon Jun 28 2004 Andrew McNab +- In GRSTx509CheckChain() and GRSTx509CompactCreds() + we now accept the first cert in a chain as a CA + even if it is X509v3 but without the CA bits set. + (On the basis that the first chain is from the + administrator-installed CA files store.) +* Sun Jun 27 2004 Andrew McNab +- ==== GridSite version 1.0.2 ==== +* Sun Jun 27 2004 Andrew McNab +- Fix for Bug #2860 (so can now read DN Lists over + HTTPS when have no user certificate if relevant + .gacl gives permission but not ) +- Include gridsite-gacl.h mods from Daniel Kouril + to fix faulty definitions + of GACLnewEntry() and GACLnewAcl() and to make + a legacy non-static GACLparseEntry() wrapper. +* Thu Jun 17 2004 Andrew McNab +- Changes to mod_gridsite.h for Fedora Core 2 / + Apache 2.0.49+ mod_ssl changes (mod_ssl-private.h) +* Wed Jun 9 2004 Andrew McNab +- Incorporate EGEE CVS layout changes in production + branch. +* Wed Jun 9 2004 Andrew McNab +- ==== GridSite version 1.0.1 ==== +* Sun Dec 14 2003 Andrew McNab +- 1.0.0 is first full production release + (development now in 1.1.x branch) +* Sun Dec 14 2003 Andrew McNab +- ==== GridSite version 1.0.0 ==== +* Sat Dec 13 2003 Andrew McNab +- Remove need for modified mod_ssl-gridsite: now + mod_gridsite intercepts callbacks with wrappers. +- Add GRSTx509NameCmp() which compares string reps of + DNs across OpenSSL version changes (ie Email=) +* Fri Dec 12 2003 Andrew McNab +- ==== GridSite version 0.9.11 ==== +* Thu Dec 11 2003 Andrew McNab +- Simplify checking of cert/proxy chain in + mod_ssl-gridsite: rely on mod_ssl/OpenSSL more. +* Wed Dec 2 2003 Andrew McNab +- ==== GridSite version 0.9.10 ==== +* Tue Dec 1 2003 Andrew McNab +- GACL ignores leading/trailing spaces in values. +* Sat Nov 29 2003 Andrew McNab +- Better directory listing in htcp. +- htcp now built as separate binary RPM. +- gridsite-admin.cgi upload now redirects to same + directory after upload (Bug #1939); allows + optional new name for file (Request / Bug #1940); + and has better checking of ../dir/file attacks. +* Sat Nov 29 2003 Andrew McNab +- ==== GridSite version 0.9.8 ==== +* Thu Nov 27 2003 Andrew McNab +- Shiv's updated GACL editor, with redirects. +* Wed Nov 26 2003 Andrew McNab +- Include Daniel Stenberg's roffit script to make + HTML man pages for htcp and urlencode. +- Various fixes found when installing GridPP WWW. +* Wed Nov 26 2003 Andrew McNab +- ==== GridSite version 0.9.7 ==== +* Thu Nov 20 2003 Andrew McNab +- Major updates to htcp (htrm/htls/htll) +- GACL now recurses subdirectories when examining + the DN List directories path. +* Sat Nov 15 2003 Andrew McNab +- ==== GridSite version 0.9.6 ==== +* Fri Nov 14 2003 Andrew McNab +- Function call fixes in grst-admin.cgi +* Thu Nov 13 2003 Andrew McNab +- Add htcp (curl-url-get reborn) +* Thu Nov 13 2003 Andrew McNab +- ==== GridSite version 0.9.5 ==== +* Thu Nov 13 2003 Andrew McNab +- More grst-admin.cgi GACL updates from Shiv. +- .gacl security improvements to grst-admin.cgi from + Shiv Kaushal and Peter Moore. +* Tue Nov 11 2003 Andrew McNab +- One RPM instead of three, with version from VERSION +- Textarea for HTML/Text editing now 80 columns +* Mon Nov 10 2003 Andrew McNab +- Add delegation level and GridSiteGSIProxyLimit + support. +- Add GridSiteAdminList handling to mod_gridsite + and real-gridsite-admin.cgi +* Sun Nov 9 2003 Andrew McNab +- Add directory create/delete, and file/dir rename. +- Add ZIP listing/unzipping via external unzip + utility from http://www.info-zip.org/pub/infozip/ +* Mon Nov 3 2003 Andrew McNab +- Include next version of Shiv's GACL editor. +- Add rpm-usr target to Makefile, to make RPMs + out-of-the-box compatible with RH9 and its Apache2 +- Use REMOTE_DOUBLE_REV for GACL hostname creds in + mod_gridsite.c/mod_gridsite_perm_handler() +* Sun Oct 26 2003 Andrew McNab +- Include GACL editor in real-gridsite-admin.cgi + from Shiv Kaushal +* Sun Oct 26 2003 Andrew McNab +- Reorganise into a single build tree, including + Apache 2.0 .h files to remove circular dependency. +* Sun Oct 26 2003 Andrew McNab +- ==== GridSite version 0.9.4 ==== +* Sun Oct 19 2003 Andrew McNab +- Include many pieces of GridSite code from 0.3.x (CGI) + fileGridSite and mod_gridsite 0.9.0 +* Sun Oct 19 2003 Andrew McNab +- ==== GridSite version 0.9.3 ==== diff --git a/org.gridsite.core/INSTALL b/org.gridsite.core/INSTALL new file mode 100644 index 0000000..68e150a --- /dev/null +++ b/org.gridsite.core/INSTALL @@ -0,0 +1,39 @@ +BUILDING/INSTALLING GRIDSITE +============================ + +For more detailed instructions, see the install.html file, either +in the ./doc subdirectory in the sources, in the directory +gridsite-VERSION/html of the docs directory when GridSite is +installed, or http://www.gridpp.ac.uk/gridsite/1.0.x/install.html + +GridSite is currently only supported on Linux, but should be +trivially portable to other Unix platforms where the GNU build +tools are available. + +When building from source, two routes are available: building +with Make or with RPM. + +BUILDING WITH MAKE +================== + +make +make install + +will build all components and install them all under the default +locations of /usr/local/[lib|bin|include|sbin] The default prefix +/usr/local is set by the prefix variable in the top level Makefile + +BUILDING WITH RPM +================= + +For RedHat Linux and derivatives, building with RPM is recommended. +The command + +make rpm + +will build the gridsite and htcp binary RPMs in the directory +../RPMTMP/RPMS/i386 relative to the working directory. A SRPM is +put into ../RPMTMP/SRPMS + +Building with RPM uses the default prefix /usr, although the +resulting RPMs are relocatable to other hierarchies. diff --git a/org.gridsite.core/LICENSE b/org.gridsite.core/LICENSE new file mode 100644 index 0000000..ce67433 --- /dev/null +++ b/org.gridsite.core/LICENSE @@ -0,0 +1,47 @@ +Copyright (c) 2002-4, Andrew McNab and Shiv Kaushal, +University of Manchester. All rights reserved. + +Redistribution and use in source and binary forms, with or +without modification, are permitted provided that the following +conditions are met: + + o Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + o Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + +Clearly marked portions of the published GridSite source code +are derived from Apache httpd or its modules, and are covered +by the Apache Software License: + +Copyright 2001-2004 The Apache Software Foundation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/org.gridsite.core/README b/org.gridsite.core/README new file mode 100644 index 0000000..5fbb1db --- /dev/null +++ b/org.gridsite.core/README @@ -0,0 +1,3 @@ +See INSTALL for build and installation instructions, and +http://www.gridpp.ac.uk/gridsite/ for configuration and +usage guides. diff --git a/org.gridsite.core/VERSION b/org.gridsite.core/VERSION new file mode 100644 index 0000000..10c4a0f --- /dev/null +++ b/org.gridsite.core/VERSION @@ -0,0 +1,4 @@ +MAJOR_VERSION=1 +MINOR_VERSION=1.0 +PATCH_VERSION=1.0.4 +VERSION=$(PATCH_VERSION) diff --git a/org.gridsite.core/build.xml b/org.gridsite.core/build.xml new file mode 100644 index 0000000..3ade883 --- /dev/null +++ b/org.gridsite.core/build.xml @@ -0,0 +1,195 @@ + + + + + + + Ant build file to build the Gridsite Core Component + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <project name="${subsystem.name}" type="post-subsystem" packageName="gridsite-${subsystem.prefix}"/> + + + + diff --git a/org.gridsite.core/doc/admin.html b/org.gridsite.core/doc/admin.html new file mode 100644 index 0000000..1f7f422 --- /dev/null +++ b/org.gridsite.core/doc/admin.html @@ -0,0 +1,103 @@ +GridSite Admin Guide + +

GridSite Admin Guide

+ +

+This Guide is intended for people administrating areas of GridSite +websites or fileservers, or managing GridSite's DN List groups - that is, +how to use GridSite to manage other people's access to parts of the site - +for example, people's write access to areas devoted to specific subprojects. + +

+ There is a separate +User Guide + which explains how to authenticate to the server with X.509 certificates, +and how to manage files via a standard web browser or with command-line +HTTPS clients. You should be familiar with the User Guide to fully +understand this Admin Guide. + +

+ You may also find the +Config Guide + useful to understand how the Apache webserver is configured with GridSite +extensions. If you are also the Apache webmaster for your site, you will +definitely need to read the Config Guide to create the httpd.conf file. +However, if you only need to manage webpages and files, then this Admin +Guide and the User Guide should be sufficient. + +

Groups and DN Lists

+ +

+GridSite defines groups of people using plain text DN Lists - that is, lists +of people's certificate DNs. Each DN List has a URL which uniquely +identifies the list (and may also allow other sites to obtain the list and +use it themselves.) For example, the list of all GridPP members is +https://www.gridpp.ac.uk/dn-lists/gridpp (note that it's https:// not +http:// - this means that other sites that download the list can check the +certificate of www.gridpp.ac.uk and know they're talking to the +authoritative source of the lists.) + +

+The system can also have a number of other DN Lists which are associated with +specific groups of people and perhaps with specific areas of responsibility +of the website. If the DN List directory URI is /dn-lists/ then +there is a full list of the DN Lists exported by the server at that URI +(for example, https://www.gridpp.ac.uk/dn-lists/ ) + +

+If you have permission to modify a DN List, you can start changing it by +going to /dn-lists/ (via HTTPS), using the "Manage directory" +button and finding the URL of your DN List in the listings. You may +need to go down into a subdirectory to find your list. For +example, https://www.gridpp.ac.uk/dn-lists/atlas is in the atlas +subdirectory of /dn-lists/ (You may wish to bookmark the listing of such +a directory if you frequently work with one.) + +

+DN List directories are managed by the ACLs described in the next section, +and if you have write permission, you can edit the lists already there, and +add new lists with the same prefix (this means you can readily create your +own subgroups.) + +

Access Control Lists

+ +

+DN Lists appear in the Grid Access Control Lists (GACL) used by GridSite. +These are stored as .gacl files in directories: if the .gacl file is +present, it governs access to the directory; if it is absent, then the +parent directories are searched upwards until a .gacl is found. + +

+The GridSite GACL Reference explains the XML format +of these files, but they +can be edited using the ACL editor built into the GridSite system by people +who have the Admin permission within the ACL. + +

+If you have this permission in a given directory, when you view directory +listings or files in that directory you will see the option "Manage +Directory" in the page footer. This allows you to get a listing of the +directory and the .gacl file will appear at the top if it's present. If not, +then there will be a button to create a new .gacl file with the same +permissions as have been inherited by that directory from its parent. + +

+GACL allows quite complex conditions to be imposed on access, but normally +you can think of an ACL as being composed of a number of entries, each of +which contains one condition (the required credential) and a set of allowed +and denied permissions. + +

+Credentials can be individual user's certificate names or whole groups of +certificate names if a DN List is given. (You can also specifiy hostname +patterns using Unix shell wildcards (eg *.ac.uk) or EDG VOMS attribute +certificates - see the GACL Reference for details.) + +

+Permissions can be Admin (edit the ACL), Write (create, modify or delete +files), List (browse the directory) or Read (read files.) Permissions can be +allowed or denied. If denied by any entry, the permission is not available +to that user or DN List (depending on what credential type was associated +with the Deny.) + + diff --git a/org.gridsite.core/doc/build-apache2.sh b/org.gridsite.core/doc/build-apache2.sh new file mode 100644 index 0000000..f1246d4 --- /dev/null +++ b/org.gridsite.core/doc/build-apache2.sh @@ -0,0 +1,79 @@ +#!/bin/sh +# +# Copyright (c) 2002-3, Andrew McNab, University of Manchester +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or +# without modification, are permitted provided that the following +# conditions are met: +# +# o Redistributions of source code must retain the above +# copyright notice, this list of conditions and the following +# disclaimer. +# o Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials +# provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +#----------------------------------------------------------------------------- +# For more information about GridSite: http://www.gridpp.ac.uk/gridsite/ +#----------------------------------------------------------------------------- +# +# This script takes an Apache .tar.gz as the single command line argument, +# unpacks the file, modifies the httpd.spec it contains to work without +# the "-C" option to configure (which RedHat 7.3 doesnt like) and +# outputs source and binary RPMs in SRPMS and RPMS/i386 + +if [ "$1" = "" ] ; then + echo Must give a tar.gz file name + exit +fi + +export MYTOPDIR=`pwd` + +if [ -x /usr/bin/rpmbuild ] ; then + export RPMCMD=rpmbuild +else + export RPMCMD=rpm +fi + +echo "$1" | grep '\.tar\.gz$' >/dev/null 2>&1 +if [ $? = 0 ] ; then # a gzipped source tar ball + + rm -Rf $MYTOPDIR/BUILD $MYTOPDIR/BUILDROOT $MYTOPDIR/SOURCES + mkdir -p $MYTOPDIR/SOURCES $MYTOPDIR/SPECS $MYTOPDIR/BUILD \ + $MYTOPDIR/SRPMS $MYTOPDIR/RPMS/i386 $MYTOPDIR/BUILDROOT + + shortname=`echo $1 | sed 's:^.*/::' | sed 's:\.tar\.gz$::'` + + cp -f $1 SOURCES + + tar zxvf SOURCES/$shortname.tar.gz $shortname/httpd.spec + cp -f $shortname/httpd.spec SPECS + + sed -e 's/configure -C /configure /' \ + SPECS/httpd.spec >SPECS/httpd-2.spec + + $RPMCMD --define "_topdir $MYTOPDIR" \ + -ba --buildroot $MYTOPDIR/BUILDROOT SPECS/httpd-2.spec + + exit +fi + +echo I dont recognise the file type (must be .tar.gz) + +exit diff --git a/org.gridsite.core/doc/config.html b/org.gridsite.core/doc/config.html new file mode 100644 index 0000000..6e747e2 --- /dev/null +++ b/org.gridsite.core/doc/config.html @@ -0,0 +1,192 @@ +GridSite Config Guide + +

GridSite Config Guide

+ +

+This Guide is intended for webmasters setting up +GridSite with an Apache 2.0 +webserver. We assume you have root access to the server machine to do this. +There is a separate Admin Guide for +people administrating areas of GridSite +websites or fileservers, or managing GridSite's DN List groups. That is, for +people managing files on the server rather than the server itself. + +

Installation

+ +

+We assume you have installed Apache 2.0 and GridSite, using the +Building and Installation Guide where necessary. +This Config Guide assumes installation has been done under /usr. For an +alternative tree like /usr/local, the relative paths should be the same. + +

+Installation should have given you an Apache 2.0 httpd binary at +/usr/sbin/httpd and a set of standard Apache 2.0 modules in +/usr/lib/httpd/modules/ including the standard mod_ssl +and our mod_gridsite.so module. + +

+GridSite also includes some commands and man pages in /usr/bin and +/usr/share/man/man1: urlencode and +htcp. + +

Certificates

+ +

+You must also install the CA root certificates of the CA's +used by the users you wish to talk to. These should be installed in +/etc/grid-security/certificates as files like 01621954.0, and RPMs and tar +files for many common European and North American CAs are available from + +https://datagrid.in2p3.fr/distribution/datagrid/security/ + +

+This location also has VOMS server certificate RPMs which install into +the /etc/grid-security/vomsdir directory. You may also manually install VOMS +server certificates into that directory with any filename. (GridSite +currently parses the certificate itself when looking for a match, rather +than checking the filename.) + +

+The server itself needs a certificate to supply to clients that use HTTPS +connections. You should apply for this from your Certification Authority +(for example, the UK e-Science +CA) and your request must use the advertised hostname of your server +(the one that appears in URLs and not, for instance, the canonical name of +the host itself.) This advertised hostname should appear in the +Distinguished Name of your request. (For example +/C=UK/O=eScience/OU=Manchester/L=HEP/CN=www.gridpp.ac.uk) For compatability +with standard browsers, the /CN= component should not include any +Globus-style service name (so not /CN=host/www.gridpp.ac.uk) If +possible, you should also include the advertised hostname as a DNS Subject +Alternative Name. Consult your CA first if you're in any doubt about how to +compose your certificate request. + +

+Once you've got your certificate, +Apache uses the certificate and private key in PEM format. If you obtained +your certificate and key in PKCS#12 or .p12 format (eg by exporting from a web +browser), you can convert the .p12 file to .pem with the following commands: +

+openssl pkcs12 -in ck.p12 -clcerts -nokeys -out hostcert.pem
+openssl pkcs12 -in ck.p12 -nodes  -nocerts -out hostkey.pem
+
+ +

+Copy the PEM files to /etc/grid-security/ as hostcert.pem (which +should be world readable) and hostkey.pem (which should only be readable by +root): + +

+chown root.root hostkey.pem hostcert.pem
+chmod 400 hostkey.pem
+chmod 444 hostcert.pem
+
+ +

httpd.conf

+ +

+/etc/httpd/conf/httpd.conf is the key to configuring the Apache 2.0 +webserver. The directives in this file determine which files the server will +publish, how they are handled, which areas are writeable and who can access +them. Through mod_gridsite.so, the GridSite system itself is configured by +directives in this file. + +

+The easiest way to get started is to examine the example httpd.conf files we +provide. + + + +

httpd-fileserver.conf

+ +

+httpd-fileserver.conf is an example +configuration file to use Apache/GridSite as a read/write HTTP(S) +fileserver, including comments on how to get the server up and running. + +

httpd-webserver.conf

+ +

+httpd-webserver.conf is an example +configuration file to use Apache/GridSite as a Web Server +(that is, primarily for interactive use with a browser) +including comments on how to get the server up and running. + +

GridSite Directives

+ +

+The mod_gridsite reference lists all the GridSite +httpd.conf directives. + +

+To start serving files, make a directory /var/www/htdocs owned by +nobody.nobody, including the .gacl access control file described below, +and add the following directive to the HTTPS <Directory> section: + +

+GridSiteMethods GET PUT DELETE + +

+If you wish to accept Globus GSI Proxies as well as full X.509 user +certificates, set GridSiteGSIProxyLimit to the depth of proxy you +wish to accept. (As a _rough_ guide: 0=No Proxies; 1=Proxy on user's +machine; 2=Proxy owned by running Globus job; 3=Proxy delegated by a +Globus job.) + +

GACL access control

+ +

+The GACL reference explains the XML access +control files used by GridSite. These allow flexible policies to be written, +in terms of X.509 user certificates, GSI proxies, VOMS attribute +certificates, DN List groups and DNS hostnames. + +

+For example, to give all clients read and list permission: +

+

+<gacl>
+<entry>
+  <any-user/>
+  <allow><read/><list/></allow>
+</entry>
+</gacl>
+
+ +

+To enable writing, add DN List, Person or VOMS entries to the file. +For example: + +

+

+<gacl>
+<entry>
+  <any-user/>
+  <allow><read/><list/></allow>
+</entry>
+<entry>
+  <person>
+  <dn>/C=UK/O=eScience/OU=Manchester/L=HEP/CN=Andrew McNab</dn>
+  </person>
+  <allow><write/></allow>
+</entry>
+</gacl>
+
+ +

+The GACL file that governs a directory is stored as .gacl in that directory. +If no .gacl is present, then GridSite will search the parent directories in +ascending order until one is found. + + + + diff --git a/org.gridsite.core/doc/gacl.html b/org.gridsite.core/doc/gacl.html new file mode 100644 index 0000000..12e58c7 --- /dev/null +++ b/org.gridsite.core/doc/gacl.html @@ -0,0 +1,84 @@ +GridSite: Grid Access Control Language + +

GridSite: Grid Access Control Language

+ +

+GACL is the authorization policy language used by +GridSite GACL allows +policies to be written in terms of common Grid credentials: X.509 +identities, GSI proxies, VOMS attribute certificates and lists of X.509 +identities. + +

+GridSite both uses GACL policies and provides a GACL manipulation API for +C/C++ in the GridSite library. + +

Credentials

+ +

+In GridSite 1.0.x, four credential types are supported: + +

+<person> +<dn>/O=Grid/CN=Name</dn> +</person> + +

+<voms> +<fqan>/vo.dom.ain/group</fqan> +</voms> + +

+<dn-list> +<url>https://www.vo.dom.ain/dn-lists/group</url> +</dn-list> + +

+<dns> +<hostname>host*.dom.ain</hostname> +</dns> + +

Permissions

+ +

+Five permissions are supported: Admin, Write, List, Exec and Read. Admin is +permission to modify the authorization policy itself, but applications can +map the other permissions to local methods as appropriate to their +environment. For filesystems and fileservers, Write, List and Read have +their usual meanings: creating or modifying files or directories; browsing +directories; reading files. Exec is not used by GridSite itself, and +applications are free to give it a meaning within their own contexts. + +

+In 1.0.x, only per-directory GACL files are supported, and the file is stored +in the directory in question, or in one of its parent directories. (GridSite +searches upwards until it finds one.) + +

+In GACL files, the permissions are represented by single tags: +<admin/>, <write/>, <list/>, <exec/>, <read/>. +Permission +tags are contained within Allow or Deny blocks. For example: +<allow><read/><list/></allow> or +<deny><admin/></deny>. + +

Entries

+ +

+Entries associate credentials with permission statements. Entries consist of +one or more credential blocks, and either an Allow or a Deny block, or both. +If multiple credentials are present in one entry, they must all be held by a +user to receive the association permissions. (So Entries provide logical AND +of credentials.) + +

Access Control Lists

+ +

+ACLs consist of a list of one or more Entry blocks. When a user's credentials +are compared to the ACL, the permissions given to the user by Allow blocks +are recorded, along with those forbidden by Deny blocks. When all entries +have been evaluated, any forbidden permissions are removed from those +granted. (So Deny always wins over Allow, even between different Entries, +but otherwise ACLs provide logical OR of credentials.) + + diff --git a/org.gridsite.core/doc/htcp.1 b/org.gridsite.core/doc/htcp.1 new file mode 100644 index 0000000..05b0718 --- /dev/null +++ b/org.gridsite.core/doc/htcp.1 @@ -0,0 +1,138 @@ +.TH htcp 1 "December 2003" htcp "HTCP Manual" +.SH NAME +.B htcp, htrm, htls, htll, htmkdir +\- get, put, delete or list HTTP/HTTPS files or directories +.SH SYNOPSIS +.B htcp [options] +.I Source-URL[s] [Destination URL] +.SH DESCRIPTION +.B htcp +is a client to fetch files or directory listings from remote servers using +HTTP or HTTPS, or to put or delete files or directories onto remote servers +using HTTPS. htcp is similar to scp(1), but uses HTTP/HTTPS rather than ssh +as its transfer protocol. + +When talking to an HTTPS server, htcp can run "anonymously", with a +standard X.509 user certificate and key, or with a GSI Proxy. This makes +htcp very useful in Grid environments where many users have certificates +and where jobs and users have access to GSI proxies. + +.SH URLs +htcp supports the file:, http: and https: URL schemes as sources and +destinations. If no scheme is given, the URL scheme is assumed to be file: +and relative to the current directory if not an absolute path. + +If multiple sources are given, they will be used in turn and the destination +must be a directory (directories are indicated by a trailing /) However, +source and destination cannot both refer to remote servers. + +.SH OPTIONS +.IP "-v/--verbose" +Turn on debugging information. Used once, this option will enable htcp's +messages to stderr. Used twice, will also enable the underlying libcurl +messages. + +.IP "--delete" +Instead of copying files, delete all the URLs given on the command line. +Calling the program as htrm has the same effect. + +.IP "--list" +.br +Instead of copying files, output lists of files located in the URL-directories +given on the command line. Calling the program as htls has the same effect. + +.IP "--long-list" +Instead of copying files, output long listings of files located in the +URL-directories given on the command line. If available, the size in bytes +and modification time of each file is given. Calling the program as +htll has the same effect. + +.IP "--mkdir" +Instead of copying files, attempt to create a directory on a remote server +with HTTP PUT. The server must support the convention that PUT to a URL with +a trailing slash means create a directory. No file body is sent. Calling the +program as htmkdir has the same effect. + +.IP "--anon" +.br +Do not attempt to use X.509 user certificates or GSI proxies to authenticate +to the remote HTTPS server. This means you are "anonymous", but the server's +identity may still be verified and the connection is still encrypted. + +.IP "--cert and --key " +Path to the PEM-encoded +X.509 or GSI Proxy user certificate and key to use for HTTPS +connections, intead of "anonymous mode." If only one of --key or --cert +is given, then that will be tried for both. If neither is given, then the +following order of precedence is used: +the file name held by the variable X509_USER_PROXY; the file +/tmp/x509up_uID (with Unix UID equal to ID); the file names held by +X509_USER_CERT / X509_USER_KEY; the files ~/.globus/usercert.pem and +~/.globus/userkey.pem (where ~/ is the home directory of the user.) + +.IP "--capath " +Path to the PEM-encoded CA root certificates to use when +verifying remote servers' host certificates in HTTPS connections. Ideally +this should be a directory of hash.0 files as described in the OpenSSL +verify(1) man page, but a file may be used instead. If --capath is not +given, the value of the environment variable X509_CERT_DIR will be tried. +If this is not valid, then /etc/grid-security/certificates will be used. + +.IP "--no-verify" +Do not use CA root certificates to verify remote servers' host certificates. +This is useful for testing sites before their certificate is set up properly, +but leaves you vulnerable to "man in the middle" attacks by hostile servers +masquerading as your target. + +.SH FILES +.IP /tmp/x509up_uID +Default GSI Proxy file for Unix UID equal to ID. + +.IP /etc/grid-security/certificates +Default location for trusted Certification Authority root certificates to use +when checking server certificates. + +.IP /tmp/.ca-roots-XXXXXX +Prior to 7.9.8, the underlying curl library did not support the CA root +certificates directory. +If built with an old version of libcurl, htcp will concatenate the +certificates in the CA roots directory into a unique temporary file and use +that. + +.SH ENVIRONMENT + +.IP X509_CERT_DIR +Holds directory to search for Certification Authority root certificates when +verifying server certificates. (Tried if --capath is not given on the +command line.) + +.IP X509_USER_PROXY +Holds file name of a GSI Proxy to use as user certificate. (Tried if --cert or +--key are not given on the command line.) + +.IP "X509_USER_CERT and X509_USER_KEY" +Holds file name of X.509 user certificate and key. (Tried if X509_USER_PROXY +is not valid.) + +.SH EXIT CODES +0 is returned on complete success. Curl error codes are returned when +reported by the underlying curl library, and CURLE_HTTP_RETURNED_ERROR (22) +is returned when the HTTP(S) server returns a code outside the range 200-299. +The manpage libcurl-errors(3) lists all the curl error codes. + +.SH TO DO +Recursive copying. Server-side wildcards. Parallel streams. Error recovery. + +.SH BUGS +Not enough beta testing (hint hint...) + +.SH AUTHOR +Andrew McNab + +htcp is part of GridSite: http://www.gridpp.ac.uk/gridsite/ +.SH "SEE ALSO" +.BR scp(1), +.BR curl(1), +.BR wget(1), +.BR verify(1), +.BR libcurl-errors(3) diff --git a/org.gridsite.core/doc/htll.1 b/org.gridsite.core/doc/htll.1 new file mode 100644 index 0000000..11a60d1 --- /dev/null +++ b/org.gridsite.core/doc/htll.1 @@ -0,0 +1 @@ +.so man1/htcp.1 diff --git a/org.gridsite.core/doc/htls.1 b/org.gridsite.core/doc/htls.1 new file mode 100644 index 0000000..11a60d1 --- /dev/null +++ b/org.gridsite.core/doc/htls.1 @@ -0,0 +1 @@ +.so man1/htcp.1 diff --git a/org.gridsite.core/doc/htmkdir.1 b/org.gridsite.core/doc/htmkdir.1 new file mode 100644 index 0000000..11a60d1 --- /dev/null +++ b/org.gridsite.core/doc/htmkdir.1 @@ -0,0 +1 @@ +.so man1/htcp.1 diff --git a/org.gridsite.core/doc/htrm.1 b/org.gridsite.core/doc/htrm.1 new file mode 100644 index 0000000..11a60d1 --- /dev/null +++ b/org.gridsite.core/doc/htrm.1 @@ -0,0 +1 @@ +.so man1/htcp.1 diff --git a/org.gridsite.core/doc/httpd-fileserver.conf b/org.gridsite.core/doc/httpd-fileserver.conf new file mode 100644 index 0000000..1b197e7 --- /dev/null +++ b/org.gridsite.core/doc/httpd-fileserver.conf @@ -0,0 +1,145 @@ +############################################################################## +## GridSite httpd-fileserver.conf - Andrew McNab +## +## Example configuration file for GridSite as an HTTP(S) fileserver. +## +## For GridSite documentation, see http://www.gridpp.ac.uk/gridsite/ +## +## This file should be renamed /etc/httpd/conf/httpd.conf and Apache +## restarted to use Apache2/GridSite as a simple HTTP(S) fileserver. +## +## You do not need to install the GridSite mod_ssl.so module if you +## do not wish to use Globus Proxies or VOMS attributes, but you must +## have the mod_gridsite.so in /usr/lib/httpd/modules +## +## We're assuming you have (a) the host's hostcert.pem and hostkey.pem +## in /etc/grid-security/ and (b) the Certification Authorities' you +## trust have their root certs in /etc/grid-security/certificates +## +## (You can get RPMs for many European and North American Grid CAs +## from https://datagrid.in2p3.fr/distribution/datagrid/security/ ) +## +## If you want to use DN Lists in ACLs, they should be placed/downloaded +## in /etc/grid-security/dn-lists/ +## +## To start serving files, make a directory /var/www/htdocs owned by +## nobody.nobody, including the file .gacl containing: +## +## +## +## +## +## +## +## +## To enable writing, add DN List, Person or VOMS entries to the GACL +## (see the GridSite GACL document for the syntax.) For example: +## +## +## +## +## +## +## +## +## /C=UK/O=eScience/OU=Manchester/L=HEP/CN=Andrew McNab +## +## +## +## +## +## and add the following directive to the HTTPS section: +## +## GridSiteMethods GET PUT DELETE +## +## If you wish to accept Globus GSI Proxies as well as full X.509 user +## certificates, set GridSiteGSIProxyLimit to the depth of proxy you +## wish to accept. +## +## (As a _rough_ guide: 0=No Proxies; 1=Proxy on user's machine; 2=Proxy +## owned by running Globus job; 3=Proxy delegated by a Globus job.) +## +## With this done and Apache restarted, you can upload a file with: +## +## curl -v --cert ~/.globus/usercert.pem --key ~/.globus/userkey.pem \ +## --capath /etc/grid-security/certificates --upload-file /tmp/tmp.txt \ +## https://INSERT.HOSTNAME.HERE/tmp.txt +## +## (or with --cert /tmp/x509up_u`id -u` --key /tmp/x509up_u`id -u` to use +## a Globus GSI Proxy created with grid-proxy-init.) +############################################################################## + +ServerRoot "/etc/httpd" + +PidFile logs/httpd.pid + +Timeout 300 +KeepAlive On +MaxKeepAliveRequests 100 +KeepAliveTimeout 15 + +LoadModule log_config_module /usr/lib/httpd/modules/mod_log_config.so +LoadModule ssl_module /usr/lib/httpd/modules/mod_ssl.so +LoadModule gridsite_module /usr/lib/httpd/modules/mod_gridsite.so +LoadModule mime_module /usr/lib/httpd/modules/mod_mime.so +LoadModule dir_module /usr/lib/httpd/modules/mod_dir.so + +TypesConfig /etc/mime.types + +# User and group who will own files created by Apache +User nobody +Group nobody + +DocumentRoot "/var/www/htdocs" + + + AllowOverride None + + +LogLevel debug +LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined + +CustomLog logs/httpd-gridsite-access combined +ErrorLog logs/httpd-gridsite-errors + +HostnameLookups On + +###################################################################### +# Plain unauthenticated HTTP on port 80 +###################################################################### + +Listen 80 + + + + GridSiteIndexes on + GridSiteAuth on + GridSiteDNlists /etc/grid-security/dn-lists/ + + + + +###################################################################### +# Secured and possibly authenticated HTTPS on port 443 +###################################################################### +Listen 443 + + +SSLEngine on +SSLCertificateFile /etc/grid-security/hostcert.pem +SSLCertificateKeyFile /etc/grid-security/hostkey.pem +SSLCACertificatePath /etc/grid-security/certificates +#SSLCARevocationPath YOUR CRL DIRECTORY WOULD GO HERE +SSLVerifyClient optional +SSLVerifyDepth 10 +SSLOptions +ExportCertData +StdEnvVars + + + GridSiteIndexes on + GridSiteAuth on + GridSiteDNlists /etc/grid-security/dn-lists/ + GridSiteGSIProxyLimit 0 +# GridSiteMethods GET PUT DELETE + + + diff --git a/org.gridsite.core/doc/httpd-webserver.conf b/org.gridsite.core/doc/httpd-webserver.conf new file mode 100644 index 0000000..0662f15 --- /dev/null +++ b/org.gridsite.core/doc/httpd-webserver.conf @@ -0,0 +1,217 @@ +############################################################################## +## GridSite httpd-webserver.conf - Andrew McNab +## +## Example configuration file for GridSite as a Web Server +## (that is, primarily for interactive use with a browser.) +## +## For GridSite documentation, see http://www.gridpp.ac.uk/gridsite/ +## +## This file should be renamed /etc/httpd/conf/httpd.conf and Apache +## restarted to use Apache2/GridSite as a webserver. +## +## You do not need to install the GridSite mod_ssl.so module if you +## do not wish to use Globus Proxies or VOMS attributes, but you must +## have the mod_gridsite.so in /usr/lib/httpd/modules +## +## We're assuming you have (a) the host's hostcert.pem and hostkey.pem +## in /etc/grid-security/ and (b) the Certification Authorities' you +## trust have their root certs in /etc/grid-security/certificates +## +## (You can get RPMs for many European and North American Grid CAs +## from https://datagrid.in2p3.fr/distribution/datagrid/security/ ) +## +## If you want to use DN Lists in ACLs, they should be placed/downloaded +## in /etc/grid-security/dn-lists/ or /var/www/htdocs/dn-lists/ +## (Lists in /etc/grid-security/dn-lists/ override lists elsewhere.) +## +## To start serving files, make a directory /var/www/htdocs owned by +## nobody.nobody, including the file .gacl containing: +## +## +## +## +## +## +## +## +## To enable writing, add DN List, Person or VOMS entries to the GACL +## (see the GridSite GACL document for the syntax.) For example: +## +## +## +## +## +## +## +## +## /C=UK/O=eScience/OU=Manchester/L=HEP/CN=Andrew McNab +## +## +## +## +## +## and add the following directive to the HTTPS section: +## +## GridSiteMethods GET PUT DELETE +## +## If you wish to accept Globus GSI Proxies as well as full X.509 user +## certificates, set GridSiteGSIProxyLimit to the depth of proxy you +## wish to accept. +## +## (As a _rough_ guide: 0=No Proxies; 1=Proxy on user's machine; 2=Proxy +## owned by running Globus job; 3=Proxy delegated by a Globus job.) +## +## With this done and Apache restarted, you can upload a file with: +## +## curl -v --cert ~/.globus/usercert.pem --key ~/.globus/userkey.pem \ +## --capath /etc/grid-security/certificates --upload-file /tmp/tmp.txt \ +## https://INSERT.HOSTNAME.HERE/tmp.txt +## +## (or with --cert /tmp/x509up_u`id -u` --key /tmp/x509up_u`id -u` to use +## a Globus GSI Proxy created with grid-proxy-init.) +############################################################################## + +ServerRoot "/etc/httpd" + +## You MUST put your server's fully qualified domain name here +## This, the DOMAIN part of the https://DOMAIN/... URLs you want +ServerName FULL.SERVER.NAME + +PidFile logs/httpd.pid + +Timeout 300 +KeepAlive On +MaxKeepAliveRequests 100 +KeepAliveTimeout 15 + +LoadModule log_config_module /usr/lib/httpd/modules/mod_log_config.so +LoadModule ssl_module /usr/lib/httpd/modules/mod_ssl.so +LoadModule gridsite_module /usr/lib/httpd/modules/mod_gridsite.so +LoadModule mime_module /usr/lib/httpd/modules/mod_mime.so +LoadModule dir_module /usr/lib/httpd/modules/mod_dir.so +LoadModule alias_module /usr/lib/httpd/modules/mod_alias.so +LoadModule cgi_module /usr/lib/httpd/modules/mod_cgi.so + +TypesConfig /etc/mime.types + +# User and group who will own files created by Apache +User nobody +Group nobody + +DocumentRoot "/var/www/htdocs" + + + AllowOverride None + + +LogLevel debug +LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined + +CustomLog logs/httpd-gridsite-access combined +ErrorLog logs/httpd-gridsite-errors + +HostnameLookups On + +###################################################################### +# Plain unauthenticated HTTP on port 80 +###################################################################### + +Listen 80 + + +## This is used to serve the Manage Directory links in footers, +## and to allow you to edit files and ACLs via your browser. +ScriptAlias /real-gridsite-admin.cgi /usr/sbin/real-gridsite-admin.cgi + + + ## This sets up GACL authorization for this server. + GridSiteAuth on + + ## This exports various bits of info into the CGI environment + ## variables (and is needed for gridsite-admin.cgi to work.) + GridSiteEnvs on + + ## Nice GridSite directory listings (without truncating file names!) + GridSiteIndexes on + + ## If this is on, GridSite will look for gridsitehead.txt and + ## gridsitefoot.txt in the current directory or its parents, and + ## use them to replace the and tags in .html files. + GridSiteHtmlFormat on + + ## These directives (and the ScriptAlias above) allow authorized + ## people to manage files, ACLs and DN Lists through their web + ## browsers. Via HTTP, this just means extended directory listings + ## and History pages. + GridSiteAdminURI /real-gridsite-admin.cgi + GridSiteAdminFile gridsite-admin.cgi + + + + +###################################################################### +# Secured and possibly authenticated HTTPS on port 443 +###################################################################### +Listen 443 + + +SSLEngine on +SSLCertificateFile /etc/grid-security/hostcert.pem +SSLCertificateKeyFile /etc/grid-security/hostkey.pem +SSLCACertificatePath /etc/grid-security/certificates +#SSLCARevocationPath YOUR CRL DIRECTORY WOULD GO HERE +SSLVerifyClient optional +SSLVerifyDepth 10 +SSLOptions +ExportCertData +StdEnvVars + +## This is used to serve the Manage Directory links in footers, +## and to allow you to edit files and ACLs via your browser. +ScriptAlias /real-gridsite-admin.cgi /usr/sbin/real-gridsite-admin.cgi + + + ## This sets up GACL authorization for this server. + GridSiteAuth on + + ## This exports various bits of info into the CGI environment + ## variables (and is needed for gridsite-admin.cgi to work.) + GridSiteEnvs on + + ## Nice GridSite directory listings (without truncating file names!) + GridSiteIndexes on + + ## If this is on, GridSite will look for gridsitehead.txt and + ## gridsitefoot.txt in the current directory or its parents, and + ## use them to replace the and tags in .html files. + GridSiteHtmlFormat on + + ## This is the path of directories (and all their subdirectories) for + ## GACL to search when it encounters a dn-list credential. The DN List + ## files are plain text, one DN per line, and must have the full url + ## as the file name, but URL Encoded - eg with urlencode(1) + GridSiteDNlists /etc/grid-security/dn-lists/:/var/www/htdocs/dn-lists/ + + ## This is used to form the URL at which DN Lists "owned" by this + ## server are exported. https://FULL.SERVER.NAME/dn-lists/file + ## ALL FILES WITH URLs ON THIS SERVER WILL BE EXPORTED IRRESPECTIVE + ## OF WHERE THEY ARE FOUND ON THE DN-LISTS PATH!! + GridSiteDNlistsURI /dn-lists/ + + ## If this is greater than zero, we will accept GSI Proxies for clients + ## (full client certificates - eg inside web browsers - are always ok) + GridSiteGSIProxyLimit 0 + + ## This directive allows authorized people to write/delete files + ## from non-browser clients - eg with htcp(1) + GridSiteMethods GET PUT DELETE + + ## These directives (and the ScriptAlias above) allow authorized + ## people to manage files, ACLs and DN Lists through their web + ## browsers via HTTPS. The value of GridSiteAdminFile appears to + ## exist in every directory, but is internally redirected by + ## mod_gridsite to the value of GridSiteAdminURI (the ScriptAlias + ## then maps that onto the real-gridsite-admin.cgi executable.) + GridSiteAdminURI /real-gridsite-admin.cgi + GridSiteAdminFile gridsite-admin.cgi + + + diff --git a/org.gridsite.core/doc/index.html b/org.gridsite.core/doc/index.html new file mode 100644 index 0000000..11aaa86 --- /dev/null +++ b/org.gridsite.core/doc/index.html @@ -0,0 +1,92 @@ +GridSite 1.0.x Documentation + +

GridSite 1.0.x Documentation

+ +

+GridSite +is a set of extensions to the Apache 2.0 webserver, which support +Grid security based on X.509 certificates. Since GridSite applies access +control within Apache itself, via mod_gridsite, Grid authorization and +the associated verified credentials are available to all technologies +supported by Apache, including static file serving, SSI, CGI, PHP, JSP and +mod_perl. + +

Guides

+ +

+

+
User Guide +
End-user documentation for people managing webpages and files on + GridSite servers, either through the web interface or with command + line clients like htcp. +

+ +

Admin Guide +
For people administering areas of GridSite websites or fileservers, or + managing GridSite's support for DN List groups. +

+ +

Building and Installation +
Instructions for building GridSite from source, and installing from + binaries or RPMs. +

+ +

Config Guide +
For webmasters setting up Apache 2.0 and GridSite, and writing the + Apache httpd.conf file. +

+ +

httpd-fileserver.conf and + httpd-webserver.conf +
Example configuration files for simple HTTP(S) fileservers and + webservers, with explanatory comments. +

+ +

+ +

Reference

+ +

+

+
Grid Access Control Lists +
Syntax and usage of the XML Grid Access Control Lists used by GridSite. +

+ +

htcp and + urlencode man pages +
Command line tools for copying files to or from HTTP(S) servers, and + for URL-encoding strings. +

+ + + +

mod_gridsite +
An Apache 2.0 module which enforces access control via Grid Access + Control Lists, and X.509, GSI or VOMS credentials. mod_gridsite also + gives Apache built-in support for the HTTP PUT and DELETE methods, and + formatting of HTML pages with standard headers and footers. +

+ + + +

gridsite.h API reference +
A detailed description of the C API provided by libgridsite, generated + from the sources by doxygen. +

+ +

+ + diff --git a/org.gridsite.core/doc/install.html b/org.gridsite.core/doc/install.html new file mode 100644 index 0000000..6755b1e --- /dev/null +++ b/org.gridsite.core/doc/install.html @@ -0,0 +1,148 @@ +GridSite: Building and Installation Guide + +

GridSite: Building and Installation Guide

+ +

+This Guide explains how to build GridSite from source, and how to install +the server components alongside an Apache 2.0 webserver. There is a +separate Config Guide which explains how to modify +the httpd.conf file, and how to set up other files and directories used by +the system. You should look through all of this Building and Installation +Guide to decide which is the easiest route for your system. + +

Installing with RPM

+ +

+If you are installing on Linux with the binary RPM release, you can skip +most of this Guide, install the binary rpm(s) and go straight +to the Config Guide. + +

+We currently distribute GridSite RPMs for RedHat Linux versions 9 and 7.3 +from our download area at + +https://www.gridpp.ac.uk/gridsite/download/ + +

+RedHat 9: +This is the simpler case, since the standard release includes a suitable +version of Apache 2.0: just install the gridsite-...-1.i386.rpm to get the +various GridSite components. + +

+RedHat 7.3: +This is more complicated because you must also install a back-ported Apache +2.0 RPM. We distribute RPMs built on 7.3 aimed at RedHat 7.3 +machines with updates, from our download area. These are built from the +tar.gz and .spec files distributed by the +Apache Foundation itself, using the +build-apache2.sh script in the GridSite +/usr/share/doc/gridsite directory. The Apache RPMs install in /usr, and you +should at least install the httpd and mod_ssl RPMs. +You must also install the gridsite-...-1.i386.rpm as above. + +

+GridSite also depends on shared libraries from libcurl and libxml2, and the +RPMs distributed as part of the standard RedHat 7.3 and 9 releases are +sufficient. + +

+With the RPMs installed, you can proceed to the +Config Guide. + +

Requirements for building GridSite from source

+ +

+GridSite is currently only supported on Linux, but should be +straightforwardly +portable to other Unix platforms where the GNU build tools are available. + +

+GridSite consists of a core library (libgridsite[.so|.a]), an Apache module +(mod_gridsite.so), a CGI utility (gridsite-admin.cgi) and some command line +tools (htcp, urlencode.) + +

+All of the components use the GridSite library, and this in turn depends on +libcurl and libxml2. You will need the development versions of these +packages installed before you can proceed. (They are available as part of +RedHat Linux releases 7.x onwards, for instance.) + +

Building GridSite with Make

+ +

+Our download area at + +https://www.gridpp.ac.uk/gridsite/download/ includes a tar-ball +distribution of the sources, which can be unpacked and used to build +GridSite from source. (Bleeding-edge developers can get the current snapshot +of the same files from our CVS area.) + +

+GridSite needs a copy of the Apache 2.0 include files to build, and the +location of this is set by the MYCFLAGS variable in the top-level Makefile. +For manual builds, the default +MYCFLAGS=-I/usr/local/include/httpd is used. +If you wish to use the GridSite module with Apache +2.0 installed elsewhere, you should change the MYCFLAGS variable to point to +the includes directory installed by the development part of that Apache 2.0 +distribution. + +

+

+make 
+make install
+
+ +

+will build all components and install them all under the default +locations of /usr/local/[lib|bin|include|sbin] The default prefix for manual +builds is +/usr/local, as set by the prefix variable in the top level Makefile +(/usr is the default for RPMs.) + +

Building GridSite with RPM

+ +

+For RedHat Linux and derivatives, building with RPM is recommended. +The command make rpm in the top level of the source tree +will build the GridSite and htcp binary RPMs in the +directory ../RPMTMP/RPMS/i386 relative to the working directory. An SRPM is +put into ../RPMTMP/SRPMS +This build assumes the Apache 2.0 includes are in /usr/include/httpd. + +

+If you make RPMs on a RedHat 9 system (or a 7.3 system with our httpd RPM +installed), you can install the resulting GridSite +RPM alongside the standard Apache 2.0 RPM without having to +modify shared library or Apache module paths. + +

+For other configurations, +you can modify the assumed location of the Apache 2.0 includes +by changing the MYCFLAGS variable in the rpm target near the +foot of the top level Makefile. + +

Building Apache 2.0

+ +

+If it is not possible to use binary RPMs of Apache 2.0, +then it can be built from source using the build-apache2.sh script +found in the GridSite docs directory. +The script includes instructions on how to build from the tarballs +distributed by the Apache Foundation. +(it removes the -C option from "configure -C" in the .spec file +and builds the RPMs under the current directory.) + +

+If these targets do not work on your build platform, +the Makefile and the scriptlets in the included SPEC files are a good +starting point for building Apache by hand yourself. The complexities of +this are outside of the scope of this Guide, but you are welcome to ask for +assistance on the +GridSite +Discussion List, although +www.apache.org is a better starting +point for purely Apache problems. + + diff --git a/org.gridsite.core/doc/library.html b/org.gridsite.core/doc/library.html new file mode 100644 index 0000000..28458ae --- /dev/null +++ b/org.gridsite.core/doc/library.html @@ -0,0 +1 @@ +library docs diff --git a/org.gridsite.core/doc/module.html b/org.gridsite.core/doc/module.html new file mode 100644 index 0000000..7f2096e --- /dev/null +++ b/org.gridsite.core/doc/module.html @@ -0,0 +1,271 @@ +GridSite Apache module: mod_gridsite + +

GridSite Apache module: mod_gridsite

+ +

+mod_gridsite is an Apache 2.0 module which enforces access control via Grid +Access Control Lists, and X.509, GSI or VOMS credentials. mod_gridsite also +gives Apache built-in support for the HTTP PUT and DELETE methods, and +formatting of HTML pages with standard headers and footers. + +

+Since mod_gridsite access +control within Apache itself, Grid authorization and +the associated verified credentials are available to all technologies +supported by Apache, including static file serving, SSI, CGI, PHP, mod_perl +and Java servlets via a connector to Tomcat. + +

+Operation of mod_gridsite can be configured using runtime directives +in Apache's standard httpd.conf configuration file. The module must first be +loaded with a LoadModule directive: + +

+LoadModule gridsite_module /PATH/TO/MODULES/mod_gridsite.so + +

+The module's behaviour is then controlled by GridSite... directives within +Apache <Directory ...> sections, allowing different directories to use +GridSite features in different ways. + +

GridSite directives

+ +
+
GridSiteIndexes on|off +
Determines whether GridSite generates HTML directory listings. These + have some advantages over standard Apache directory listings (eg the + displayed filenames are never truncated) and will include standard + headers and footers if GridSiteHtmlFormat is on. +
+ (Default: GridSiteIndexes off) +

+ +

GridSiteIndexHeader file +
If the named file is found in the directory being listed, the file + is included verbatim at the top of the listing and excluded from + the file-by-file listing. The file can either be HTML or plain text (in + which case browsers will be treat it as one HTML paragraph.) +
+ (Default: none) +

+ +

GridSiteHtmlFormat on|off +
Determines where HTML pages receive additional formatting before being + sent to the client. This includes the "Last modified", + "View page history", "Switch to HTTP(S)", + "Print View" and "Built with GridSite" footer + elements. If header and footer files are found, they will be used too. +
+ (Default: GridSiteHtmlFormat off) +

+ +

GridSiteHeadFile file
+ GridSiteFootFile file +
Set the filenames to be searched for as standard headers and footers + for HTML pages. For each HTML page, the directory of that page is tried + first, and then parent directories in ascending order until a header / + footer file is found. Header files are inserted in place of HTML + <body[ ...]> tags; footer files in place of </body>. (These + standard files should each include the appropriate body tag as a + replacement.) +
+ (Defaults: GridSiteHeadFile gridsitehead.txt, + GridSiteFootFile gridsitefoot.txt) +

+ +

GridSiteAuth on|off +
Enables GridSite access control features, using + GACL files. The files are named .gacl and are + per-directory. The current directory is tried and then parent + directories in ascending order until a .gacl file is found. +
+ (Default: GridSiteAuth off) +

+ +

GridSiteAdminList uri +
All members of the DN List with name "uri" receive the full set + of permissions, irrespective of per-directory .gacl files. People in + this group have full control over the whole site. +
+ (Default: none) +

+ +

GridSiteGSIProxyLimit limit +
When using GSI Proxy credentials, + proxies with delegation depth greater than "limit" will + be ignored by mod_gridsite authorization decisions. A limit of zero + implies only full X.509 + certificates (and no proxies) will be accepted. A limit of 1 implies + that only the initial proxy, usually created on the user's own machine, + is acceptable. Higher levels lead to proxies on remote machines, eg + used by running jobs, being accepted. +
+ (Default: GridSiteGSIProxyLimit 1) +

+ +

GridSiteMethods [GET] [PUT] [DELETE] +
Specifies which HTTP methods are supported by GridSite. GET (and HEAD) + are always supported. PUT and DELETE support is turned on by this + directive, subject to a positive statement that write permission is + allowed for the directory in question, by a GACL file. +
+ (Default: GridSite GET) +

+ +

GridSiteDNlists directory1[:directory2[:directory3]...] +
Sets up the DN List path used by GACL for + evaluating <dn-list> credentials. If this directive is not used, + then GACL will use the GRST_DN_LISTS variable from Apache's own + environment. If that is not set either, then /etc/grid-security/dn-lists + is searched. +
+ (Default: none) +

+ +

GridSiteDNlistsURI uri +
If GridSiteDNlistsURI is used, then the URI given appears to be + populated with all the DN lists on the current DN lists path which + match the current server. That is, for server https://example.org/ + with DN lists URI /dn-lists/, all DN lists with URLs starting + https://example.org/dn-lists/ will appear to be present in /dn-lists/, + irrespective of where in the path they are stored. +
+ (Default: none) +

+ +

GridSiteAdminURI uri +
GridSiteAdminURI gives the absolute URI on the server of the GridSite + Admin CGI program, which is used for file management, HTML and GACL + editing. This should be used in conjunction with the standard Apache + directive ScriptAlias to map that URI to the real-gridsite-admin.cgi + executable. For example: +
+ ScriptAlias /real-gridsite-admin.cgi + /PATH/TO/real-gridsite-admin.cgi +
+ This URI is always reached by an internal redirection from the value + set by GridSiteAdminFile, and is never visible to users. +
+ (Default: none) +

+ +

GridSiteAdminFile cgifilename +
If GridSiteAdminURI is set, then the cgifilename of GridSiteAdminFile + appears to be present in all directories when explicitly + requested (it does not appear in directory listings.) Requests for these + ghost CGI URIs are internally redirected to the value set by + GridSiteAdminURI. +
+ (Default: GridSiteAdminFile gridsite-admin.cgi) +

+ +

GridSiteEnvs on|off +
This makes mod_gridsite export several variables into the environment + of CGI programs and other dynamic content systems. The variable names + are listed below. For gridsite-admin.cgi mechanism to work, this switch + must be left in its default state of on. +
+ (Default: GridSiteEnvs on) +

+ +

GridSiteEditable [ext1 [ext2 [ext3] ...]]] +
A space-separated list of file extensions which can safely be edited + by the GridSite Text/HTML editor. The extensions are given without the + initial dot. +
+ (Default: GridSiteEditable txt shtml html htm css js php jsp) +

+ +

GridSiteHelpURI uri +
If set, gives the URI to use for "Website Help" links in HTML + page footers. +
+ (Default: none) +

+ +

GridSiteLink on|off +
Turns off the link in the HTML page footers which gives credit to + GridSite. +
+ (Default: GridSiteLink on) +

+ +

GridSiteUnzip path +
If "path" is set by this directive, then real-gridsite-admin.cgi + will offer to list the contents of .zip archives on the server. + Users with write access are able to unpack the contents into the same + directory as the .zip file. The value of "path" must point + to the location of the + unzip binary. +
+ (Default: none) +

+ +

+ +

Environment variables

+ +

+The following variables are present in the environment of CGI programs and +other dynamic content systems if the GridSiteEnvs on directive is +in effect. + +

+

+
GRST_PERM +
Numerical value of the permission bit-map obtained by comparing the + user with the GACL in force. (These should be tested using the + GRSTgaclPermHasXXXX functions from GACL.) +

+ +

GRST_ADMIN_LIST +
URI of the DN List, listing people with full admin and write access + to the whole site. +

+ +

GRST_GSIPROXY_LIMIT +
Maximum valid delegation level for GSI Proxies. +

+ +

GRST_DIR_PATH +
Absolute path in the local filesystem to the directory holding the + file being requested. +

+ +

GRST_HELP_URI +
URI of website help pages set by GridSiteHelpURI directive. +

+ +

GRST_ADMIN_FILE +
Filename of per-directory ghost gridsite-admin.cgi program. (This is + used by real-gridsite-admin.cgi to construct links in its pages.) +

+ +

GRST_EDITABLE +
Space-separated list of extensions which can safely be edited with a + Text/HTML editor. +

+ +

GRST_HEAD_FILE and GRST_FOOT_FILE +
Filenames of standard header and footer files. +

+ +

GRST_DN_LISTS +
DN lists search path. +

+ +

GRST_DN_LISTS_URI +
Directory of virtual URIs used to publish this site's DN Lists. +

+ +

GRST_UNZIP +
Full path to the unzip binary, used to list and unpack .zip files. +

+ +

GRST_NO_LINK +
If set, do not include credit links to GridSite in page footers. +

+ +

+ + diff --git a/org.gridsite.core/doc/urlencode.1 b/org.gridsite.core/doc/urlencode.1 new file mode 100644 index 0000000..47d2b91 --- /dev/null +++ b/org.gridsite.core/doc/urlencode.1 @@ -0,0 +1,46 @@ +.TH urlencode 1 "November 2003" urlencode "URLENCODE Manual" +.SH NAME +.B urlencode +\- convert strings to or from URL-encoded form +.SH SYNOPSIS +.B urlencode +[-m|-d] +.I string [string ...] +.SH DESCRIPTION +.B urlencode +encodes strings according to RFC 1738. + +That is, characters A-Z a-z 0-9 . _ +and - are passed through unmodified, but all other characters are +represented as %HH, where HH is their two-digit upper-case hexadecimal ASCII +representation. +For example, the URL http://www.gridpp.ac.uk/gridsite/ becomes +http%3A%2F%2Fwww.gridpp.ac.uk%2Fgridsite%2F + +.B urlencode +converts each character in all the strings given on the command line. If +multiple strings are given, they are concatenated with separating spaces +before conversion. + +.SH OPTIONS +.IP "-m" +Instead of full conversion, do GridSite "mild URL encoding" in which A-Z a-z +0-9 . = - _ @ and / are passed through unmodified. This results in slightly +more human-readable strings but the application must be prepared to create +or simulate the directories implied by any slashes. + +.IP "-d" +Do URL-decoding rather than encoding, according to RFC 1738. %HH and %hh +strings are converted and other characters are passed through unmodified, +with the exception that + is converted to space. + +.SH EXIT CODES +0 is always returned. + +.SH BUGS +Not enough beta testing (hint hint...) + +.SH AUTHOR +Andrew McNab + +urlencode is part of GridSite: http://www.gridpp.ac.uk/gridsite/ diff --git a/org.gridsite.core/doc/user.html b/org.gridsite.core/doc/user.html new file mode 100644 index 0000000..ae37cdd --- /dev/null +++ b/org.gridsite.core/doc/user.html @@ -0,0 +1,302 @@ +GridSite User Guide + +

GridSite User Guide

+ +

If you are setting up a GridSite-based website you may wish to use this +file as the basis of your end-user documentation. If so, copy all of the +files from the GridSite doc directory (probably +/usr/share/doc/gridsite-VERSION/) +to somewhere on your website like +/gridsite-doc/ and add GridSiteHelpURI /gridsite-doc/user.html +to the virtual server configuration in +httpd.conf - you should also look through the rest of the HTML source since +there are some comments you may find helpful. + +

+This Guide is intended for people using GridSite websites with conventional +web browsers, especially people with write access to areas of the site. + There is a separate +Administration Guide + with additional information for people managing access control and group +membership. This Guide assumes you are familiar with basic Web and HTML +concepts. Towards the end we discuss how to access servers with command +line tools like curl and htcp. + +

Reading from HTTP and HTTPS servers

+ +

+GridSite servers are usually accessible both via HTTP and via HTTPS. You can +always tell which version you are using by looking at whether the URL in your +browser's location window starts with "http://" or +"https://" HTTPS means that the connection to the server is +encrypted, that you can verify you're talking to the real server and not an +imposter, and gives you the option to authenticate to the site and perhaps +gain write access. + +

+ Simple browsing of the website via HTTP or HTTPS is reasonably + self-explanatory. If configured, additional links may appear in the footer + of each webpage with links to this help, + + and to switch between HTTP and HTTPS versions of the page. Pages may also + have a link to the page History, + + showing the dates of changes to that page and names of its authors. + +

+ When looking at HTTPS pages, you may find your browser reports it cannot + verify the server's certificate since it does not recognise the + Certification Authority (CA) it uses. You should attempt to load the CA's + root certificate into your browser to stop these warnings. (This means your + browser will be able to identify any servers using fake certificates which + you shouldn't trust.) How you obtain the CA Root Certificate from a + trust-worthy source depends on the CA. For example, the UK e-Science CA + lets you download it from their + website. + + +

Authenticating

+ +

+ To go beyond reading pages you need to obtain a user certificate and load it + into your web browser. How you do this again depends on the Certification + Authority you have access to (for most Grid projects, CAs are organised + on a national basis.) To use the UK e-Science CA example again, + from their website has links to + the procedure for applying for a certificate from within a web browser. + + +

+A user certificate usually has a version of your name and affiliation as its +Distinguished Name (DN) - for example, +"/C=UK/O=eScience/OU=Manchester/L=HEP/CN=Andrew McNab" + +

+Once you've obtained a user certificate in your name from your CA, you need +to make sure it is loaded into the browser you normally use to browse the +web. How you do this is different for different browsers and to some extent +for different CAs (but if you applied +for the CA through your browser, you may already have it there.) + +

+Browsers want the certificate and private key in the PKCS#12 format, which +is normally a single file with the extension ".p12". +Many programs which are based on OpenSSL, such as Globus and curl, prefer +the PEM (".pem") format for certificates, with separate +certificate and key files ("usercert.pem" and +"userkey.pem", for example.) If you only have the files in .pem +format and have access to openssl, you +can use its command line tools to convert PEM to PKCS#12: +

+openssl pkcs12 -in usercert.pem -inkey userkey.pem -export -out certkey.p12
+
+ +

+Be very careful not to accidentally overwrite .pem or .p12 files when +doing this kind of thing! In particular, if you lose your private key, you +cannot retrieve it from your CA. + +

+ Once your user certificate is loaded, you should be able to see your + certificate name appear when you look at an HTTPS GridSite page which has + the page footers enabled - for example, the "Switch to HTTP" link + present. If GridSite understands your user certificate, it displays a + "You are ..." line in the footer. (However, the Apache webserver + must also be set up with your CAs root certificate for this to work. The + GridPP HTTPS home page is set up + to recognise a good range of European and North American Grid CAs.) + + +

Authorization

+ +

+ Once users can prove their identity to the web server, it then becomes + possible to give them appropriate rights depending on that identity. + GridSite allows site administrators to specify these rights for individuals + and groups using +GACL + access control files. (The +Administration Guide + explains how to manage these files.) GACL defines who can + read files, who can list directories, + who can write or create files and who can modify the GACL policy files. To + get increased access to an area of a site, you need to contact the + administrator for that area and give the DN of your certificate (it's not + necessary to send any certificate files.) + +

Managing Directories and Files

+ +

+If you have list permission for the directory containing a page, you should +see an extra link "Manage Directory" in the page's set of footer +links, which allows you to browse the directory even if the normal +index.html is present. If page histories are available, this listing view +also has links to them. + +

+The real power of GridSite becomes available if you have write access to a +directory. In that case, the "Manage Directory" page has +additional links to Delete or Rename pages and other files, and to Edit HTML +and plain text files. An Edit link also appears in the footer links of HTML +pages. + +

+If you use the Edit function, you are presented with an HTML form containing +the current filename and the full HTML or plain text of the page for you to +edit. This allows you to maintain the content of the site "in +place" and to see the result of your changes immediately, in context. + +

+If you modify the filename in the form before saving, GridSite will make a +new file with that name, and the old file will still be present, unmodified. +(However, you cannot use this feature for creating a file in a different +directory.) +As you make changes, the history of the changes and your certificate DN are +recorded, and available in the history page for that file. + +

+ For people with write access, the "Manage Directory" page also has + options to upload a file from the computer your browser is running on, and to + create files and directories. If it's enabled, you can also view the + contents of WinZIP / PKZIP / .zip files, and unpack their contents into the + current directory. (This feature is very useful if you have several files + to upload at one time.) + + +

HTML Formatting in GridSite

+ +

+As well as providing access control and file management, GridSite provides +some simple formatting of HTML pages by adding standard headers and footers. +(If this isn't sufficient, GridSite will happily coexist with HTML +preprocessor languages like SSI, PHP and JSP.) + +

+ If HTML formatting is enabled + for the current directory, GridSite looks for the files gridsitehead.txt and + gridsitefoot.txt in that directory, or goes up through the parent + directories until they are found. + + +

+The <body> and </body> tags from the HTML file are replaced with +the contents of the gridsitehead.txt and gridsitefoot.txt files, which +should normally be chunks of HTML including a replacement <body> +or </body> tag. If either tag is absent from the original page, then +the header or footer is just added rather than being inserted in place of +the tag. (One consequence of this absence is that HTML header tags like +<title> can end up after a <body> tag, and can get ignored by +browsers - so always include <body> ... </body> in your pages.) + +

+This simple system is suprisingly flexible, and allows a variety of top and +bottom, or sidebar navigation layouts of pages. Since the <body ...> +tag is under full control of the author of the gridsitehead.txt file, +backgrounds, colour schemes and style sheets can easily be specified. + +

+For example: + +

+ + + + + + + + + +
SourceHTML
page.html<title>PAGE TITLE</title>
page.html
(replaced)
<body>
gridsitehead.txt<body text=blue>
+ Heading text
+ <table border=1>
<tr>
<td>Standard<br>
+ sidebar</td>
<td>
page.html<p>
Page content...
page.html
(replaced)
</body>
gridsitefoot.txt</td>
</tr>
+ </table>
Footer text
</body>
+ +

+produces pages with a layout like: + +

+ + + + +
Heading text
Standard
sidebar
Page content...
Footer text
+ +

Command line use

+ +

+GridSite adds support for the HTTP PUT and DELETE methods, and this makes it +easy to create or delete files from within programs and commands without +using a web browser and HTML forms. It is straightforward, although slightly +awkward, to use a standard HTTPS-aware client like +curl to upload files, but GridSite +provides htcp as a more convenient client program, which is easier to use +with GSI Proxies and X.509 user certificates, and has a syntax closer to the +familiar scp command. + +

+The following examples assume the GridSite server has GSI support and use a +GSI proxy as the client certificate. For non-GSI use, just skip the +grid-proxy-init stage, and replace the proxy +filename with $HOME/.globus/usercert.pem and $HOME/.globus/userkey.pem (or +wherever your PEM format certificate and key are stored.) + +

+First generate a GSI proxy with grid-proxy-init. This will create a proxy file +in /tmp/x509up_uXXXXX where XXXXX is your Unix UID (also given by id +-u.) The GSI proxy contains a +temporary private key and certificate signed by your long-term user +certificate. + +

+You should make sure you have a copy of the CA root certificates of the CA's +used by the servers you wish to talk to. These are usually installed in +/etc/grid-security/certificates as files like 01621954.0, and RPMs and tar +files for many common European and North American CAs are available from + +https://datagrid.in2p3.fr/distribution/datagrid/security/ + +

+To upload a file with curl: +

+curl --cert /tmp/x509up_u`id -n` --key /tmp/x509up_u`id -n` \
+     --capath /etc/grid-security/certificates \
+     --upload-file /tmp/new.file.txt https://server/new.file.txt
+
+ +

+The equivalent htcp command is: +

+htcp /tmp/new.file.txt https://server/new.file.txt
+
+since htcp looks for the GSI proxy and CA certificates automatically. htcp +can also be used to copy remote files to the local machine by reversing the +arguments. For more details, see the +htcp(1) man page. + +

+htcp also has options for deleting files, and doing short or long listings, +and these can also be accessed using the htrm, htls and htll commands (which +are normally symbolic links to htcp.) + +

+Directory indexes are based on parsing the index returned by the web server +and by using the HTTP HEAD method to obtain the file size and modification +times. + +

+All of the ht** commands can accept multiple source file arguments, and this +allows you to copy multiple files to or from the server. Shell wildcard +expansion on the local machine is especially useful: +

+htcp /tmp/new.*.txt https://server/
+
+ + diff --git a/org.gridsite.core/interface/gridsite-gacl.h b/org.gridsite.core/interface/gridsite-gacl.h new file mode 100644 index 0000000..2eec2fa --- /dev/null +++ b/org.gridsite.core/interface/gridsite-gacl.h @@ -0,0 +1,188 @@ +/* + Copyright (c) 2002-4, Andrew McNab, University of Manchester + All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + o Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + o Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +/*------------------------------------------------------------------------* + * For more about GridSite: http://www.gridpp.ac.uk/gridsite/ * + *------------------------------------------------------------------------*/ + +#ifndef HEADER_GACL_H +#define HEADER_GACL_H +#endif + +#ifndef GACL_LIB_VERSION +#define GACL_LIB_VERSION "x.x.x" +#endif + +typedef GRSTgaclCred GACLcred; + +typedef int GACLaction; +typedef unsigned int GACLperm; + +typedef GRSTgaclEntry GACLentry; + +typedef GRSTgaclAcl GACLacl; + +typedef GRSTgaclUser GACLuser; + +extern char *gacl_perm_syms[]; +extern GACLperm gacl_perm_vals[]; + +#define GACL_PERM_NONE GRST_PERM_NONE +#define GACL_PERM_READ GRST_PERM_READ +#define GACL_PERM_LIST GRST_PERM_LIST +#define GACL_PERM_WRITE GRST_PERM_WRITE +#define GACL_PERM_ADMIN GRST_PERM_ADMIN + +#define GACLhasNone(perm) (perm == 0) +#define GACLhasRead(perm) ((perm & GRST_PERM_READ) != 0) +#define GACLhasList(perm) ((perm & GRST_PERM_LIST) != 0) +#define GACLhasWrite(perm) ((perm & GRST_PERM_WRITE) != 0) +#define GACLhasAdmin(perm) ((perm & GRST_PERM_ADMIN) != 0) + +#define GACL_ACTION_ALLOW GRST_ACTION_ALLOW +#define GACL_ACTION_DENY GRST_ACTION_DENY + +#define GACL_ACL_FILE GRST_ACL_FILE +#define GACL_DN_LISTS GRST_DN_LISTS + +#define GACLinit() GRSTgaclInit() + +#define GACLnewCred(x) GRSTgaclCredNew((x)) +//GACLcred *GACLnewCred(char *); + +#define GACLaddToCred(x,y,z) GRSTgaclCredAddValue((x),(y),(z)) +//int GACLaddToCred(GACLcred *, char *, char *); + +#define GACLfreeCred(x) GRSTgaclCredFree((x)) +//int GACLfreeCred(GACLcred *); + +#define GACLaddCred(x,y) GRSTgaclEntryAddCred((x),(y)) +//int GACLaddCred(GACLentry *, GACLcred *); + +#define GACLdelCred(x,y) GRSTgaclEntryDelCred((x),(y)) +//int GACLdelCred(GACLentry *, GACLcred *); + +#define GACLprintCred(x,y) GRSTgaclCredPrint((x),(y)) +// int GACLprintCred(GACLcred *, FILE *); + + +#define GACLnewEntry() GRSTgaclEntryNew() +// GACLentry *GACLnewEntry(void); + +#define GACLfreeEntry(x) GRSTgaclEntryFree((x)) +// int GACLfreeEntry(GACLentry *); + +#define GACLaddEntry(x,y) GRSTgaclAclAddEntry((x),(y)) +// int GACLaddEntry(GACLacl *, GACLentry *); + +#define GACLprintEntry(x,y) GRSTgaclEntryPrint((x),(y)) +// int GACLprintEntry(GACLentry *, FILE *); + + +#define GACLprintPerm(x,y) GRSTgaclPermPrint((x),(y)) +//int GACLprintPerm(GACLperm, FILE *); + +#define GACLallowPerm(x,y) GRSTgaclEntryAllowPerm((x),(y)) +// int GACLallowPerm(GACLentry *, GACLperm); + +#define GACLunallowPerm(x,y) GRSTgaclEntryUnallowPerm((x),(y)) +//int GACLunallowPerm(GACLentry *, GACLperm); + +#define GACLdenyPerm(x,y) GRSTgaclEntryDenyPerm((x),(y)) +// int GACLdenyPerm(GACLentry *, GACLperm); + +#define GACLundenyPerm(x,y) GRSTgaclEntryUndenyPerm((x),(y)) +// int GACLundenyPerm(GACLentry *, GACLperm); + +#define GACLpermToChar(x) GRSTgaclPermToChar((x)) +// char *GACLpermToChar(GACLperm); + +#define GACLcharToPerm(x) GRSTgaclPermFromChar((x)) +// GACLperm GACLcharToPerm(char *); + +#define GACLnewAcl() GRSTgaclAclNew() +// GACLacl *GACLnewAcl(void); + +#define GACLfreeAcl(x) GRSTgaclAclFree((x)) +// int GACLfreeAcl(GACLacl *); + +#define GACLprintAcl(x,y) GRSTgaclAclPrint((x),(y)) +// int GACLprintAcl(GACLacl *, FILE *); + +#define GACLsaveAcl(x,y) GRSTgaclAclSave((y),(x)) +// int GACLsaveAcl(char *, GACLacl *); + +#define GACLloadAcl(x) GRSTgaclAclLoadFile((x)) +// GACLacl *GACLloadAcl(char *); + +#define GACLfindAclForFile(x) GRSTgaclFileFindAclname((x)) +// char *GACLfindAclForFile(char *); + +#define GACLloadAclForFile(x) GRSTgaclAclLoadforFile((x)) +// GACLacl *GACLloadAclForFile(char *); + +#define GACLisAclFile(x) GRSTgaclFileIsAcl((x)) +// int GACLisAclFile(char *); + + +#define GACLnewUser(x) GRSTgaclUserNew((x)) +// GACLuser *GACLnewUser(GACLcred *); + +#define GACLfreeUser(x) GRSTgaclUserFree((x)) +// int GACLfreeUser(GACLuser *); + +#define GACLuserAddCred(x,y) GRSTgaclUserAddCred((x),(y)) +// int GACLuserAddCred(GACLuser *, GACLcred *); + +#define GACLuserHasCred(x,y) GRSTgaclUserHasCred((x),(y)) +// int GACLuserHasCred(GACLuser *, GACLcred *); + +#define GACLuserFindCredType(x,y) GRSTgaclUserFindCredtype((x),(y)) +// GACLcred *GACLuserFindCredType(GACLuser *, char *); + +#define GACLtestDnList(x,y) GRSTgaclDNlistHasUser((x),(y)) +// int GACLtestDnList(char *, GACLuser *); + +#define GACLtestUserAcl(x,y) GRSTgaclAclTestUser((x),(y)) +// GACLperm GACLtestUserAcl(GACLacl *, GACLuser *); + +#define GACLtestExclAcl(x,y) GRSTgaclAclTestexclUser((x),(y)) +// GACLperm GACLtestExclAcl(GACLacl *, GACLuser *); + + +#define GACLurlEncode(x) GRSThttpUrlEncode((x)) +// char *GACLurlEncode(char *); + +#define GACLmildUrlEncode(x) GRSThttpUrlMildencode((x)) +// char *GACLmildUrlEncode(char *); + +GACLparseEntry *GRSTgaclEntryParse(xmlNodePtr cur); +// special function for legacy EDG LB service diff --git a/org.gridsite.core/interface/gridsite.h b/org.gridsite.core/interface/gridsite.h new file mode 100644 index 0000000..9742d15 --- /dev/null +++ b/org.gridsite.core/interface/gridsite.h @@ -0,0 +1,269 @@ +/* + Copyright (c) 2002-3, Andrew McNab, University of Manchester + All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + o Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + o Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +/*------------------------------------------------------------------------* + * For more about GridSite: http://www.gridpp.ac.uk/gridsite/ * + *------------------------------------------------------------------------*/ + +#ifndef HEADER_SSL_H +#include +#endif + +#ifndef HEADER_CRYPTO_H +#include +#endif + +#ifndef FALSE +#define FALSE (0) +#endif +#ifndef TRUE +#define TRUE (!FALSE) +#endif + +/// Everything ok (= OpenSSL X509_V_OK) +#define GRST_RET_OK 0 + +/// Failed for unspecified reason +#define GRST_RET_FAILED 1000 + +/// Failed to find certificate in some cert store / directory +#define GRST_RET_CERT_NOT_FOUND 1001 + +/// Bad signature +#define GRST_RET_BAD_SIGNATURE 1002 + +/// No such file or directory +#define GRST_RET_NO_SUCH_FILE 1003 + +#define GRST_PROXYCERTINFO_OID "1.3.6.1.4.1.3536.1.222" +#define GRST_VOMS_OID "1.3.6.1.4.1.8005.100.100.1" +#define GRST_VOMS_DIR "/etc/grid-security/vomsdir" + +typedef struct { char *name; + char *value; + struct _GRSTgaclNamevalue *next; } GRSTgaclNamevalue; + +typedef struct { char *type; + int delegation; + GRSTgaclNamevalue *firstname; + void *next; } GRSTgaclCred; + +typedef int GRSTgaclAction; +typedef unsigned int GRSTgaclPerm; + +typedef struct { GRSTgaclCred *firstcred; + GRSTgaclPerm allowed; + GRSTgaclPerm denied; + void *next; } GRSTgaclEntry; + +typedef struct { GRSTgaclEntry *firstentry; } GRSTgaclAcl; + +typedef struct { GRSTgaclCred *firstcred; + char *dnlists; } GRSTgaclUser; + +#define GRST_PERM_NONE 0 +#define GRST_PERM_READ 1 +#define GRST_PERM_EXEC 2 +#define GRST_PERM_LIST 4 +#define GRST_PERM_WRITE 8 +#define GRST_PERM_ADMIN 16 +#define GRST_PERM_ALL 31 + +/* DO NOT USE PermIsNone!! */ +#define GRSTgaclPermIsNone(perm) (perm == 0) + +#define GRSTgaclPermHasNone(perm) (perm == 0) +#define GRSTgaclPermHasRead(perm) ((perm & GRST_PERM_READ ) != 0) +#define GRSTgaclPermHasExec(perm) ((perm & GRST_PERM_EXEC ) != 0) +#define GRSTgaclPermHasList(perm) ((perm & GRST_PERM_LIST ) != 0) +#define GRSTgaclPermHasWrite(perm) ((perm & GRST_PERM_WRITE) != 0) +#define GRSTgaclPermHasAdmin(perm) ((perm & GRST_PERM_ADMIN) != 0) + +#define GRST_ACTION_ALLOW 0 +#define GRST_ACTION_DENY 1 + +#define GRST_HIST_PREFIX ".grsthist" +#define GRST_ACL_FILE ".gacl" +#define GRST_DN_LISTS "/etc/grid-security/dn-lists" +#define GRST_RECURS_LIMIT 9 + +int GRSTgaclInit(void); + +// #define GACLnewCred(x) GRSTgaclCredNew((x)) +GRSTgaclCred *GRSTgaclCredNew(char *); + +// #define GACLaddToCred(x,y,z) GRSTgaclCredAddValue((x),(y),(z)) +int GRSTgaclCredAddValue(GRSTgaclCred *, char *, char *); + +#define GRSTgaclCredSetDelegation(cred, level) ((cred)->delegation = (level)) +#define GRSTgaclCredGetDelegation(cred) ((cred)->delegation) + +//#define GACLfreeCred(x) GRSTgaclCredFree((x)) +int GRSTgaclCredFree(GRSTgaclCred *); + +// #define GACLaddCred(x,y) GRSTgaclEntryAddCred((x),(y)) +int GRSTgaclEntryAddCred(GRSTgaclEntry *, GRSTgaclCred *); + +// #define GACLdelCred(x,y) GRSTgaclEntryDelCred((x),(y)) +int GRSTgaclEntryDelCred(GRSTgaclEntry *, GRSTgaclCred *); + +// #define GACLprintCred(x,y) GRSTgaclCredPrint((x),(y)) +int GRSTgaclCredCredPrint(GRSTgaclCred *, FILE *); + + +// #define GACLnewEntry(x) GRSTgaclEntryNew((x)) +GRSTgaclEntry *GRSTgaclEntryNew(void); + +// #define GACLfreeEntry(x) GRSTgaclEntryFree((x)) +int GRSTgaclEntryFree(GRSTgaclEntry *); + +// #define GACLaddEntry(x,y) GRSTgaclAclAddEntry((x),(y)) +int GRSTgaclAclAddEntry(GRSTgaclAcl *, GRSTgaclEntry *); + +// #define GACLprintEntry(x,y) GRSTgaclEntryPrint((x),(y)) +int GRSTgaclEntryPrint(GRSTgaclEntry *, FILE *); + + +// #define GACLprintPerm(x,y) GRSTgaclPermPrint((x),(y)) +int GRSTgaclPermPrint(GRSTgaclPerm, FILE *); + +// #define GACLallowPerm(x,y) GRSTgaclEntryAllowPerm((x),(y)) +int GRSTgaclEntryAllowPerm(GRSTgaclEntry *, GRSTgaclPerm); + +// #define GACLunallowPerm(x,y) GRSTgaclEntryUnallowPerm((x),(y)) +int GRSTgaclEntryUnallowPerm(GRSTgaclEntry *, GRSTgaclPerm); + +// #define GACLdenyPerm(x,y) GRSTgaclEntryDenyPerm((x),(y)) +int GRSTgaclEntryDenyPerm(GRSTgaclEntry *, GRSTgaclPerm); + +// #define GACLundenyPerm(x,y) GRSTgaclEntryUndenyPerm((x),(y)) +int GRSTgaclUndenyPerm(GRSTgaclEntry *, GRSTgaclPerm); + +// #define GACLpermToChar(x) GRSTgaclPermToChar((x)) +char *GRSTgaclPermToChar(GRSTgaclPerm); + +// #define GACLcharToPerm(x) GRSTgaclPermFromChar((x)) +GRSTgaclPerm GRSTgaclPermFromChar(char *); + +// #define GACLnewAcl(x) GRSTgaclAclNew((x)) +GRSTgaclAcl *GRSTgaclAclNew(void); + +// #define GACLfreeAcl(x) GRSTgaclAclFree((x)) +int GRSTgaclAclFree(GRSTgaclAcl *); + +// #define GACLprintAcl(x,y) GRSTgaclAclPrint((x),(y)) +int GRSTgaclAclPrint(GRSTgaclAcl *, FILE *); + +// #define GACLsaveAcl(x,y) GRSTgaclAclSave((y),(x)) +int GRSTgaclAclSave(GRSTgaclAcl *, char *); + +// #define GACLloadAcl(x) GRSTgaclFileLoadAcl((x)) +GRSTgaclAcl *GRSTgaclAclLoadFile(char *); + +// #define GACLfindAclForFile(x) GRSTgaclFileFindAclname((x)) +char *GRSTgaclFileFindAclname(char *); + +// #define GACLloadAclForFile(x) GRSTgaclFileLoadAcl((x)) +GRSTgaclAcl *GRSTgaclAclLoadforFile(char *); + +// #define GACLisAclFile(x) GRSTgaclFileIsAcl((x)) +int GRSTgaclFileIsAcl(char *); + + +// #define GACLnewUser(x) GRSTgaclUserNew((x)) +GRSTgaclUser *GRSTgaclUserNew(GRSTgaclCred *); + +// #define GACLfreeUser(x) GRSTgaclUserFree((x)) +int GRSTgaclUserFree(GRSTgaclUser *); + +// #define GACLuserAddCred(x,y) GRSTgaclUserAddCred((x),(y)) +int GRSTgaclUserAddCred(GRSTgaclUser *, GRSTgaclCred *); + +// #define GACLuserHasCred(x,y) GRSTgaclUserHasCred((x),(y)) +int GRSTgaclUserHasCred(GRSTgaclUser *, GRSTgaclCred *); + +int GRSTgaclUserSetDNlists(GRSTgaclUser *, char *); + +// #define GACLuserFindCredType(x,y) GRSTgaclUserFindCredtype((x),(y)) +GRSTgaclCred *GRSTgaclUserFindCredtype(GRSTgaclUser *, char *); + +// #define GACLtestDnList(x,y) GRSTgaclDNlistHasUser((x),(y)) +int GRSTgaclDNlistHasUser(char *, GRSTgaclUser *); + +// #define GACLtestUserAcl(x,y) GRSTgaclAclTestUser((x),(y)) +GRSTgaclPerm GRSTgaclAclTestUser(GRSTgaclAcl *, GRSTgaclUser *); + +// #define GACLtestExclAcl(x,y) GRSTgaclAclTestexclUser((x),(y)) +GRSTgaclPerm GRSTgaclAclTestexclUser(GRSTgaclAcl *, GRSTgaclUser *); + + +char *GRSThttpUrlDecode(char *); + +// #define GACLurlEncode(x) GRSThttpUrlEncode((x)) +char *GRSThttpUrlEncode(char *); + +// #define GACLmildUrlEncode(x) GRSThttpMildUrlEncode((x)) +char *GRSThttpUrlMildencode(char *); + +int GRSTx509NameCmp(char *, char *); + +int GRSTx509KnownCriticalExts(X509 *); + +time_t GRSTasn1TimeToTimeT(char *); +int GRSTx509IsCA(X509 *); +int GRSTx509CheckChain(int *, X509_STORE_CTX *); +int GRSTx509VerifyCallback(int, X509_STORE_CTX *); + +int GRSTx509CheckVomsSig(unsigned char *, unsigned int, + unsigned char *, unsigned int, + char *, char *, char *); +int GRSTx509GetVomsCreds(int *, int, size_t, char *, X509 *, X509 *, char *); +GRSTgaclCred *GRSTx509CompactToCred(char *); +int GRSTx509CompactCreds(int *, int, size_t, char *, STACK_OF(X509) *, char *); + + +#define GRST_HEADFILE "gridsitehead.txt" +#define GRST_FOOTFILE "gridsitefoot.txt" +#define GRST_ADMIN_FILE "gridsite-admin.cgi" + +typedef struct { char *text; + void *next; } GRSThttpCharsList; + +typedef struct { size_t size; + GRSThttpCharsList *first; + GRSThttpCharsList *last; } GRSThttpBody; + +void GRSThttpBodyInit(GRSThttpBody *); +void GRSThttpPrintf(GRSThttpBody *, char *, ...); +int GRSThttpCopy(GRSThttpBody *, char *); +void GRSThttpWriteOut(GRSThttpBody *); +int GRSThttpPrintHeaderFooter(GRSThttpBody *, char *, char *); +char *GRSThttpGetCGI(char *); diff --git a/org.gridsite.core/project/build.properties b/org.gridsite.core/project/build.properties new file mode 100644 index 0000000..e69de29 diff --git a/org.gridsite.core/project/configure.properties.xml b/org.gridsite.core/project/configure.properties.xml new file mode 100644 index 0000000..82737d0 --- /dev/null +++ b/org.gridsite.core/project/configure.properties.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/org.gridsite.core/project/dependencies.properties b/org.gridsite.core/project/dependencies.properties new file mode 100644 index 0000000..2a7383b --- /dev/null +++ b/org.gridsite.core/project/dependencies.properties @@ -0,0 +1,9 @@ +################################################################### +# System dependencies +################################################################### + +org.glite.version = HEAD +org.glite.core.version = HEAD + +# Component dependencies tag = do not remove this line = + diff --git a/org.gridsite.core/project/gridsite.core.csf.xml b/org.gridsite.core/project/gridsite.core.csf.xml new file mode 100644 index 0000000..c8f21a7 --- /dev/null +++ b/org.gridsite.core/project/gridsite.core.csf.xml @@ -0,0 +1,255 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The org.glite and org.glite.wms-utils modules have been updated, please rerun the configuration file + + + + The org.glite and org.glite.wms-utils modules have been updated, please rerun the configuration file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.gridsite.core/project/properties.xml b/org.gridsite.core/project/properties.xml new file mode 100644 index 0000000..74f88dc --- /dev/null +++ b/org.gridsite.core/project/properties.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.gridsite.core/project/taskdefs.xml b/org.gridsite.core/project/taskdefs.xml new file mode 100644 index 0000000..9c35cef --- /dev/null +++ b/org.gridsite.core/project/taskdefs.xml @@ -0,0 +1,31 @@ + + + + + + + + \ No newline at end of file diff --git a/org.gridsite.core/project/version.properties b/org.gridsite.core/project/version.properties new file mode 100644 index 0000000..1eaa9d4 --- /dev/null +++ b/org.gridsite.core/project/version.properties @@ -0,0 +1,3 @@ +module.version=1.0.4 +module.build=1 +module.age=5 diff --git a/org.gridsite.core/src/Doxyfile b/org.gridsite.core/src/Doxyfile new file mode 100644 index 0000000..e47d005 --- /dev/null +++ b/org.gridsite.core/src/Doxyfile @@ -0,0 +1,993 @@ +# Doxyfile 1.2.18 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# General configuration options +#--------------------------------------------------------------------------- + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, +# Finnish, French, German, Greek, Hungarian, Italian, Japanese, Japanese-en +# (Japanese with english messages), Korean, Norwegian, Polish, Portuguese, +# Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish and Ukrainian. + +OUTPUT_LANGUAGE = English + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these class will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all inherited +# members of a class in the documentation of that class as if those members were +# ordinary class members. Constructors, destructors and assignment operators of +# the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. It is allowed to use relative paths in the argument list. + +STRIP_FROM_PATH = + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower case letters. If set to YES upper case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# users are adviced to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like the Qt-style comments (thus requiring an +# explict @brief command for a brief description. + +JAVADOC_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the DETAILS_AT_TOP tag is set to YES then Doxygen +# will output the detailed description near the top, like JavaDoc. +# If set to NO, the detailed description appears after the member +# documentation. + +DETAILS_AT_TOP = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# reimplements. + +INHERIT_DOCS = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consist of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. +# For instance some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java sources +# only. Doxygen will then generate output that is more tailored for Java. +# For instance namespaces will be presented as packages, qualified scopes +# will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx *.hpp +# *.h++ *.idl *.odl + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or directories +# that are symbolic links (a Unix filesystem feature) are excluded from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. + +EXCLUDE_PATTERNS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. + +INPUT_FILTER = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# If the REFERENCED_BY_RELATION tag is set to YES (the default) +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES (the default) +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = doxygen + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet + +HTML_STYLESHEET = doxygen.css + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output dir. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non empty doxygen will try to run +# the html help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the Html help documentation and to the tree view. + +TOC_EXPAND = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = YES + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be +# generated containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript and frames is required (for instance Mozilla, Netscape 4.0+, +# or Internet explorer 4.0+). Note that for large projects the tree generation +# can take a very long time. In such cases it is better to disable this feature. +# Windows users are probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = NO + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimised for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assigments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_XML = NO + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = NO + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_PREDEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_PREDEF_ONLY tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse the +# parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::addtions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES tag can be used to specify one or more tagfiles. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in Html, RTF and LaTeX) for classes with base or +# super classes. Setting the tag to NO turns the diagrams off. Note that this +# option is superceded by the HAVE_DOT option below. This is only a fallback. It is +# recommended to install and use dot, since it yield more powerful graphs. + +CLASS_DIAGRAMS = YES + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found on the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_WIDTH = 1024 + +# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_HEIGHT = 1024 + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermedate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::addtions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO + +# The CGI_NAME tag should be the name of the CGI script that +# starts the search engine (doxysearch) with the correct parameters. +# A script with this name will be generated by doxygen. + +CGI_NAME = search.cgi + +# The CGI_URL tag should be the absolute URL to the directory where the +# cgi binaries are located. See the documentation of your http daemon for +# details. + +CGI_URL = + +# The DOC_URL tag should be the absolute URL to the directory where the +# documentation is located. If left blank the absolute path to the +# documentation, with file:// prepended to it, will be used. + +DOC_URL = + +# The DOC_ABSPATH tag should be the absolute path to the directory where the +# documentation is located. If left blank the directory on the local machine +# will be used. + +DOC_ABSPATH = + +# The BIN_ABSPATH tag must point to the directory where the doxysearch binary +# is installed. + +BIN_ABSPATH = /usr/local/bin/ + +# The EXT_DOC_PATHS tag can be used to specify one or more paths to +# documentation generated for other projects. This allows doxysearch to search +# the documentation for these projects as well. + +EXT_DOC_PATHS = diff --git a/org.gridsite.core/src/Makefile b/org.gridsite.core/src/Makefile new file mode 100644 index 0000000..a2bba0b --- /dev/null +++ b/org.gridsite.core/src/Makefile @@ -0,0 +1,204 @@ +# +# Andrew McNab and Shiv Kaushal, University of Manchester. +# Copyright (c) 2002-4. All rights reserved. +# +# Redistribution and use in source and binary forms, with or +# without modification, are permitted provided that the following +# conditions are met: +# +# o Redistributions of source code must retain the above +# copyright notice, this list of conditions and the following +# disclaimer. +# o Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials +# provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +#----------------------------------------------------------------------------- +# For more information about GridSite: http://www.gridpp.ac.uk/gridsite/ +#----------------------------------------------------------------------------- + +include ../VERSION + +RPMCMD=$(shell if [ -x /usr/bin/rpmbuild ] ; then echo /usr/bin/rpmbuild; else echo rpm; fi) + +ifndef MYRPMDIR +export MYRPMDIR=$(shell pwd)/../RPMTMP +endif + +ifndef prefix +export prefix=/home/dimeglio/gridsite +endif + +ifndef MYCFLAGS +export MYCFLAGS=-I. -I../interface -I/usr/include/httpd -I/usr/include/apr-0 +endif + +ifndef MYLDFLAGS +export MYLDFLAGS=-L. +endif + +# +# Build +# + +build: libgridsite.so.$(VERSION) libgridsite.a htcp mod_gridsite.so \ + urlencode real-gridsite-admin.cgi apidoc + +libgridsite.so.$(VERSION): grst_x509.o grst_gacl.o grst_http.o + gcc -shared -Wl,-soname,libgridsite.so.$(MINOR_VERSION) \ + -o libgridsite.so.$(PATCH_VERSION) grst_x509.o grst_gacl.o grst_http.o + +libgridsite.a: grst_x509.o grst_gacl.o grst_http.o + ar src libgridsite.a grst_x509.o grst_gacl.o grst_http.o + +grst_x509.o: grst_x509.c ../interface/gridsite.h + gcc $(MYCFLAGS) -I/usr/include/openssl \ + -I/usr/kerberos/include -c grst_x509.c + +grst_gacl.o: grst_gacl.c ../interface/gridsite.h + gcc $(MYCFLAGS) -I/usr/include/openssl \ + -I/usr/kerberos/include `xml2-config --cflags` -c grst_gacl.c + +grst_http.o: grst_http.c ../interface/gridsite.h + gcc $(MYCFLAGS) -I/usr/include/openssl \ + -I/usr/kerberos/include -c grst_http.c + +urlencode: urlencode.c libgridsite.a + gcc -DVERSION=\"$(PATCH_VERSION)\" $(MYCFLAGS) \ + -o urlencode urlencode.c -L. \ + -I/usr/include/openssl -I/usr/kerberos/include -lgridsite + +htcp: htcp.c + gcc -DVERSION=\"$(PATCH_VERSION)\" -I. -o htcp htcp.c \ + `curl-config --cflags` `curl-config --libs` + +mod_gridsite.so: mod_gridsite.c mod_ssl-private.h + gcc $(MYCFLAGS) -shared -Wl,-soname=gridsite_module \ + -I/usr/include/openssl -I/usr/kerberos/include \ + -DVERSION=\"$(VERSION)\" -o mod_gridsite.so \ + mod_gridsite.c $(MYLDFLAGS) -lxml2 -lm -lz -lgridsite + +real-gridsite-admin.cgi: grst_admin_main.c grst_admin_gacl.c \ + grst_admin_file.c grst_admin.h + gcc $(MYCFLAGS) $(MYLDFLAGS) -o real-gridsite-admin.cgi \ + grst_admin_main.c \ + grst_admin_gacl.c \ + grst_admin_file.c \ + -I/usr/include/openssl -I/usr/kerberos/include \ + -DVERSION=\"$(VERSION)\" -lgridsite -lssl -lcrypto -lxml2 -lz -lm + +apidoc: ../interface/gridsite.h grst_x509.c grst_gacl.c grst_http.c \ + Doxyfile doxygen.css + doxygen Doxyfile + +gaclexample: gaclexample.c libgridsite.a + gcc -o gaclexample gaclexample.c -I. -L. \ + -I/usr/include/openssl -I/usr/kerberos/include -lgridsite \ + -lxml2 -lz -lm + +# +# Install +# + +install: + mkdir -p $(prefix)/include \ + $(prefix)/lib \ + $(prefix)/bin \ + $(prefix)/sbin \ + $(prefix)/share/man/man1 \ + $(prefix)/lib/httpd/modules \ + $(prefix)/share/doc/gridsite-$(PATCH_VERSION) + cp -f ../interface/gridsite.h $(prefix)/include + cp -f ../interface/gridsite-gacl.h $(prefix)/include + cp -f urlencode $(prefix)/bin + cp -f libgridsite.a $(prefix)/lib + cp -f real-gridsite-admin.cgi $(prefix)/sbin + cp -f libgridsite.so.$(PATCH_VERSION) $(prefix)/lib + ln -sf libgridsite.so.$(PATCH_VERSION) \ + $(prefix)/lib/libgridsite.so + ln -sf libgridsite.so.$(PATCH_VERSION) \ + $(prefix)/lib/libgridsite.so.$(MAJOR_VERSION) + ln -sf libgridsite.so.$(PATCH_VERSION) \ + $(prefix)/lib/libgridsite.so.$(MINOR_VERSION) + cp -f doxygen/index.html \ + $(prefix)/share/doc/gridsite-$(PATCH_VERSION)/doxygen-index.html + cp -f doxygen/* $(prefix)/share/doc/gridsite-$(PATCH_VERSION) + cp -f ../CHANGES ../README ../INSTALL ../LICENSE ../VERSION \ + $(prefix)/share/doc/gridsite-$(PATCH_VERSION) + cp -f ../doc/*.html ../doc/*.conf ../doc/*.1 ../doc/*.sh \ + $(prefix)/share/doc/gridsite-$(VERSION) + cp -f ../doc/*.1 $(prefix)/share/man/man1 + gzip -f $(prefix)/share/man/man1/*.1 + cd ../doc ; for i in *.1 ; do ../src/roffit < $$i \ + > $(prefix)/share/doc/gridsite-$(VERSION)/$$i.html ; done + cp -f htcp $(prefix)/bin + ln -sf htcp $(prefix)/bin/htls + ln -sf htcp $(prefix)/bin/htll + ln -sf htcp $(prefix)/bin/htrm + ln -sf htcp $(prefix)/bin/htmkdir + cp -f mod_gridsite.so $(prefix)/lib/httpd/modules + +# +# Distributions +# + +# source files tarball +dist: + mkdir -p ../gridsite-$(PATCH_VERSION)/src \ + ../gridsite-$(PATCH_VERSION)/doc \ + ../gridsite-$(PATCH_VERSION)/interface + cp -f ../VERSION ../README ../LICENSE ../CHANGES ../INSTALL \ + ../gridsite-$(PATCH_VERSION) + cp -f Makefile *.c *.h roffit gridsite.spec \ + Doxyfile doxygen.css doxyheader.html \ + ../gridsite-$(PATCH_VERSION)/src + cp -f ../doc/*.html ../doc/*.1 ../doc/*.conf ../doc/*.sh \ + ../gridsite-$(PATCH_VERSION)/doc + cp -f ../interface/*.h \ + ../gridsite-$(PATCH_VERSION)/interface + cd .. ; tar zcvf gridsite-$(PATCH_VERSION).src.tar.gz \ + gridsite-$(PATCH_VERSION) + rm -Rf ../gridsite-$(PATCH_VERSION) + + +# binary tarball distribution for htcp users +htcp-bin: htcp + mkdir -p ../htcp-bin-$(PATCH_VERSION)/bin \ + ../htcp-bin-$(PATCH_VERSION)/man/man1 + cp -f ../doc/README.htcp-bin ../htcp-bin-$(PATCH_VERSION) + cp -f htcp ../htcp-bin-$(PATCH_VERSION)/bin + cp -f ../doc/htcp.1 ../doc/htrm.1 ../doc/htls.1 ../doc/htll.1 \ + ../doc/htmkdir.1 ../htcp-bin-$(PATCH_VERSION)/man/man1 + ln -sf htcp ../htcp-bin-$(PATCH_VERSION)/bin/htls + ln -sf htcp ../htcp-bin-$(PATCH_VERSION)/bin/htll + ln -sf htcp ../htcp-bin-$(PATCH_VERSION)/bin/htrm + ln -sf htcp ../htcp-bin-$(PATCH_VERSION)/bin/htmkdir + cd ../htcp-bin-$(VERSION) ; tar zcvf ../htcp-$(VERSION).bin.tar.gz . + rm -Rf ../htcp-bin-$(PATCH_VERSION) + +# RPM targets: build and RPMs go into subdirectories of ../RPMTMP/ +rpm: dist gridsite.spec + rm -Rf $(MYRPMDIR)/BUILDROOT $(MYRPMDIR)/BUILD + mkdir -p $(MYRPMDIR)/SOURCES $(MYRPMDIR)/SPECS $(MYRPMDIR)/BUILD \ + $(MYRPMDIR)/SRPMS $(MYRPMDIR)/RPMS/i386 $(MYRPMDIR)/BUILDROOT + cp -f ../gridsite-$(PATCH_VERSION).src.tar.gz $(MYRPMDIR)/SOURCES + cp -f gridsite.spec $(MYRPMDIR)/SPECS + export MYPREFIX=/usr ; export MYVERSION=$(PATCH_VERSION) ; \ + $(RPMCMD) --define "_topdir $(MYRPMDIR)" \ + -ba --buildroot $(MYRPMDIR)/BUILDROOT gridsite.spec + diff --git a/org.gridsite.core/src/doxygen.css b/org.gridsite.core/src/doxygen.css new file mode 100644 index 0000000..97ebc25 --- /dev/null +++ b/org.gridsite.core/src/doxygen.css @@ -0,0 +1,49 @@ +H1 { text-align: center; } +CAPTION { font-weight: bold } +A.qindex {} +A.qindexRef {} +A.el { text-decoration: none; font-weight: bold } +A.elRef { font-weight: bold } +A.code { text-decoration: none; font-weight: normal; color: #4444ee } +A.codeRef { font-weight: normal; color: #4444ee } +A:hover { text-decoration: none; background-color: #f2f2ff } +DL.el { margin-left: -1cm } +DIV.fragment { width: 100%; border: none; background-color: #eeeeee } +DIV.ah { background-color: black; font-weight: bold; color: #ffffff; margin-bottom: 3px; margin-top: 3px } +TD.md { background-color: #f2f2ff; font-weight: bold; } +TD.mdname1 { background-color: #f2f2ff; font-weight: bold; color: #602020; } +TD.mdname { background-color: #f2f2ff; font-weight: bold; color: #602020; width: 600px; } +DIV.groupHeader { margin-left: 16px; margin-top: 12px; margin-bottom: 6px; font-weight: bold } +DIV.groupText { margin-left: 16px; font-style: italic; font-size: smaller } +XXBODY { background: white } +TD.indexkey { + background-color: #eeeeff; + font-weight: bold; + padding-right : 10px; + padding-top : 2px; + padding-left : 10px; + padding-bottom : 2px; + margin-left : 0px; + margin-right : 0px; + margin-top : 2px; + margin-bottom : 2px +} +TD.indexvalue { + background-color: #eeeeff; + font-style: italic; + padding-right : 10px; + padding-top : 2px; + padding-left : 10px; + padding-bottom : 2px; + margin-left : 0px; + margin-right : 0px; + margin-top : 2px; + margin-bottom : 2px +} +span.keyword { color: #008000 } +span.keywordtype { color: #604020 } +span.keywordflow { color: #e08000 } +span.comment { color: #800000 } +span.preprocessor { color: #806020 } +span.stringliteral { color: #002080 } +span.charliteral { color: #008080 } diff --git a/org.gridsite.core/src/doxyheader.html b/org.gridsite.core/src/doxyheader.html new file mode 100644 index 0000000..ad33993 --- /dev/null +++ b/org.gridsite.core/src/doxyheader.html @@ -0,0 +1 @@ +

GridSite Version 0.9.1 diff --git a/org.gridsite.core/src/gaclexample.c b/org.gridsite.core/src/gaclexample.c new file mode 100644 index 0000000..4cab5bc --- /dev/null +++ b/org.gridsite.core/src/gaclexample.c @@ -0,0 +1,147 @@ +/* + Copyright (c) 2002-3, Andrew McNab, University of Manchester + All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + o Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + o Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +/*------------------------------------------------------------------------* + * For more about GridSite: http://www.gridpp.ac.uk/gridsite/ * + *------------------------------------------------------------------------*/ + +/* + Example program using GACL + + Build with: + + gcc -o gaclexample gaclexample.c -L. -I. -lgridsite -lxml2 -lz -lm +*/ + +#include +#include +#include +#include + +int main() +{ + GRSTgaclCred *cred, *usercred; + GRSTgaclEntry *entry; + GRSTgaclAcl *acl1, *acl2; + GRSTgaclUser *user; + GRSTgaclPerm perm0, perm1, perm2; + FILE *fp; + + /* must initialise GACL before using it */ + + GRSTgaclInit(); + + /* build up an ACL, starting with a credential */ + + cred = GRSTgaclCredNew("person"); + + GRSTgaclCredAddValue(cred, "dn", "/O=Grid/CN=Mr Grid Person"); + + /* create an entry to put it in */ + + entry = GRSTgaclEntryNew(); + + /* add the credential to it */ + + GRSTgaclEntryAddCred(entry, cred); + + /* add another credential */ + + cred = GRSTgaclCredNew("dn-list"); + GRSTgaclCredAddValue(cred, "url", "example-dn-list"); + GRSTgaclEntryAddCred(entry, cred); + + fp = fopen("example-dn-list", "w"); + fputs("/O=Grid/CN=Mr Grid Person\n", fp); + fclose(fp); + + /* associate some permissions and denials to the credential */ + + GRSTgaclEntryAllowPerm( entry, GRST_PERM_READ); + GRSTgaclEntryAllowPerm( entry, GRST_PERM_WRITE); + GRSTgaclEntryAllowPerm( entry, GRST_PERM_ADMIN); + GRSTgaclEntryDenyPerm( entry, GRST_PERM_ADMIN); + GRSTgaclEntryDenyPerm( entry, GRST_PERM_LIST); + + perm0 = GRST_PERM_READ | GRST_PERM_WRITE; + + printf("test perm should be %d\n", perm0); + + /* create a new ACL and add the entry to it */ + + acl1 = GRSTgaclAclNew(); + + GRSTgaclAclAddEntry(acl1, entry); + + /* create a GRSTgaclUser to compare with the ACL */ + + usercred = GRSTgaclCredNew("person"); + + GRSTgaclCredAddValue(usercred, "dn", "/O=Grid/CN=Mr Grid Person"); + + user = GRSTgaclUserNew(usercred); + + GRSTgaclUserSetDNlists(user, getcwd(NULL, 0)); + printf("DN Lists dir %s\n", getcwd(NULL, 0)); + +// putenv("GRST_DN_LISTS=."); + + perm1 = GRSTgaclAclTestUser(acl1, user); + + printf("test /O=Grid/CN=Mr Grid Person in acl = %d\n", perm1); + + /* print and save the whole ACL */ + + GRSTgaclAclPrint(acl1, stdout); + + GRSTgaclAclSave(acl1, "example.gacl"); + + puts("gridacl.out saved"); + + puts(""); + + /* load the ACL back off the disk, print and test it */ + + acl2 = GRSTgaclAclLoadFile("example.gacl"); + + puts("gridacl.out loaded"); + + if (acl2 != NULL) GRSTgaclAclPrint(acl2, stdout); else puts("acl2 is NULL"); + + perm2 = GRSTgaclAclTestUser(acl2, user); + + printf("test /O=Grid/CN=Mr Grid Person in acl = %d\n", perm2); + + if (perm1 != perm0) return 1; + if (perm2 != perm0) return 2; + + return 0; +} diff --git a/org.gridsite.core/src/gridsite.spec b/org.gridsite.core/src/gridsite.spec new file mode 100644 index 0000000..a6633d3 --- /dev/null +++ b/org.gridsite.core/src/gridsite.spec @@ -0,0 +1,76 @@ +Name: gridsite +Version: %(echo ${MYVERSION:-1.0.x}) +Release: 1 +Summary: GridSite +Copyright: Modified BSD +Group: System Environment/Daemons +Source: %{name}-%{version}.src.tar.gz +Prefix: %(echo ${MYPREFIX:-/usr}) +URL: http://www.gridpp.ac.uk/gridsite/ +Vendor: GridPP +#Requires: libxml2,curl-ssl,mod_ssl +#Buildrequires: libxml2-devel,curl-ssl-devel,httpd-devel +Packager: Andrew McNab + +%description +GridSite adds GSI, VOMS and GACL support to Apache 2.0 (mod_gridsite), +a library for manipulating these technologies (libgridsite), and CGI +programs for interactive management of HTTP(S) servers (gridsite-admin.cgi) + +See %(echo ${MYPREFIX:-/usr})/share/doc/gridsite-%{version} and +http://www.gridpp.ac.uk/gridsite/ for details. + +%package -n htcp +Group: Applications/Internet +Summary: HTTP(S) read/write client +#Requires: curl-ssl + +%description -n htcp +htcp is a client to fetch files or directory listings from remote +servers using HTTP or HTTPS, or to put or delete files or directories +onto remote servers using HTTPS. htcp is similar to scp(1), but uses +HTTP/HTTPS rather than ssh as its transfer protocol. + +%prep + +%setup + +%build +cd src +make prefix=$RPM_BUILD_ROOT/%(echo ${MYPREFIX:-/usr}) + +%install +cd src +make install prefix=$RPM_BUILD_ROOT/%(echo ${MYPREFIX:-/usr}) + +%post +/sbin/ldconfig +ln -sf %(echo ${MYPREFIX:-/usr})/share/doc/gridsite-%{version} \ + %(echo ${MYPREFIX:-/usr})/share/doc/gridsite + +%postun +rm -f %(echo ${MYPREFIX:-/usr})/share/doc/gridsite + +%files +%attr(-, root, root) %(echo ${MYPREFIX:-/usr})/lib/libgridsite.so.%{version} +%attr(-, root, root) %(echo ${MYPREFIX:-/usr})/lib/libgridsite.so +%attr(-, root, root) %(echo ${MYPREFIX:-/usr})/bin/urlencode +%attr(-, root, root) %(echo ${MYPREFIX:-/usr})/share/doc/gridsite-%{version} +%attr(-, root, root) %(echo ${MYPREFIX:-/usr})/share/man/man1/urlencode.1.gz +%attr(-, root, root) %(echo ${MYPREFIX:-/usr})/include/gridsite.h +%attr(-, root, root) %(echo ${MYPREFIX:-/usr})/include/gridsite-gacl.h +%attr(-, root, root) %(echo ${MYPREFIX:-/usr})/lib/libgridsite.a +%attr(-, root, root) %(echo ${MYPREFIX:-/usr})/lib/httpd/modules/mod_gridsite.so +%attr(-, root, root) %(echo ${MYPREFIX:-/usr})/sbin/real-gridsite-admin.cgi + +%files -n htcp +%attr(-, root, root) %(echo ${MYPREFIX:-/usr})/bin/htcp +%attr(-, root, root) %(echo ${MYPREFIX:-/usr})/bin/htls +%attr(-, root, root) %(echo ${MYPREFIX:-/usr})/bin/htll +%attr(-, root, root) %(echo ${MYPREFIX:-/usr})/bin/htrm +%attr(-, root, root) %(echo ${MYPREFIX:-/usr})/bin/htmkdir +%attr(-, root, root) %(echo ${MYPREFIX:-/usr})/share/man/man1/htcp.1.gz +%attr(-, root, root) %(echo ${MYPREFIX:-/usr})/share/man/man1/htrm.1.gz +%attr(-, root, root) %(echo ${MYPREFIX:-/usr})/share/man/man1/htls.1.gz +%attr(-, root, root) %(echo ${MYPREFIX:-/usr})/share/man/man1/htll.1.gz +%attr(-, root, root) %(echo ${MYPREFIX:-/usr})/share/man/man1/htmkdir.1.gz diff --git a/org.gridsite.core/src/grst_admin.h b/org.gridsite.core/src/grst_admin.h new file mode 100644 index 0000000..4da3d11 --- /dev/null +++ b/org.gridsite.core/src/grst_admin.h @@ -0,0 +1,57 @@ +/* + Copyright (c) 2002-3, Andrew McNab and Shiv Kaushal, + University of Manchester. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + o Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + o Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +/*---------------------------------------------------------------------------* + * This program is part of GridSite: http://www.gridpp.ac.uk/gridsite/ * + *---------------------------------------------------------------------------*/ + +void GRSThttpError(char *); +void adminfooter(GRSThttpBody *, char *, char *, char *, char *); +int GRSTstrCmpShort(char *, char *); +char *makevfilename(char *, size_t, char *); + +/*CGI GACL - Edit interface functions*/ +void show_acl(int admin, GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file); +void new_entry_form(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file); +void new_entry(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file); +void del_entry(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file); +void edit_entry_form(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file); +void edit_entry(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file); +void add_cred_form(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file); +void add_cred(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file); +void del_cred(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file); +void del_entry_sure(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file); +void del_cred_sure(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file); + +/*Functions producing messages*/ +//void error(char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file); +void admin_continue(char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file, GRSThttpBody *bp); + diff --git a/org.gridsite.core/src/grst_admin_file.c b/org.gridsite.core/src/grst_admin_file.c new file mode 100644 index 0000000..fed6c42 --- /dev/null +++ b/org.gridsite.core/src/grst_admin_file.c @@ -0,0 +1,1571 @@ +/* + Copyright (c) 2002-3, Andrew McNab, University of Manchester + All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + o Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + o Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +/*---------------------------------------------------------------------------* + * This program is part of GridSite: http://www.gridpp.ac.uk/gridsite/ * + *---------------------------------------------------------------------------*/ + +#ifndef VERSION +#define VERSION "x.x.x" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// when porting: remember that sendfile() is very OS-specific! +#include + +#include + +#include "grst_admin.h" + +char *storeuploadfile(char *boundary, int *bufferused) +{ +// rewrite this to copy whole POSTed stdin HTTP body to disk then +// mmap() and pick apart? How to deal with 100MB uploaded files, say? + + char *filebuffer = NULL; + int bufferlen = 0, c, boundarylen; + + *bufferused = 0; + boundarylen = strlen(boundary); + + while ((c = getchar()) != EOF) + { + if (*bufferused > 1024*1024*100) return NULL; + + ++(*bufferused); + + if (*bufferused > bufferlen) + { + bufferlen = bufferlen + 1000; + filebuffer = realloc(filebuffer, (size_t) bufferlen); + } + + filebuffer[*bufferused - 1] = c; + + if ( (*bufferused >= boundarylen + 4) && + (boundary[boundarylen-1] == c) && + (boundary[boundarylen-2] == filebuffer[*bufferused - 2]) && + (strncmp(boundary, &filebuffer[*bufferused - boundarylen], + boundarylen) == 0)) + { + *bufferused = *bufferused - boundarylen - 4; + + if (filebuffer == NULL) return strdup(""); + else return filebuffer; + } + } + + return NULL; +} + +void uploadfile(char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, + char *dir_uri, char *admin_file) +{ + char *boundary, *p, oneline[200], *filename = NULL, + tmpfilename[256], *filebuffer = NULL, *filepath, + *vfile, *dir_path_vfile; + int mimestate, bufferused = 0, itworked = 0; + FILE *fp; + GRSThttpBody bp; + +#define MIMESTUNKNOWN 1 +#define MIMESTUPLOAD 2 +#define MIMESTFILENM 3 + + if (!GRSTgaclPermHasWrite(perm)) GRSThttpError("403 Forbidden"); + + p = getenv("CONTENT_TYPE"); + boundary = &p[30]; + + mimestate = MIMESTUNKNOWN; + + while (fgets(oneline, sizeof(oneline), stdin) != NULL) + { + if (*oneline == 13) // MIME has CR/LF line breaks, CR=13 + { + if (mimestate == MIMESTUPLOAD) + { + filebuffer = storeuploadfile(boundary, &bufferused); + mimestate = MIMESTUNKNOWN; + } + else if (mimestate == MIMESTFILENM) + { + fgets(tmpfilename, sizeof(tmpfilename), stdin); + if (*tmpfilename != 13) + { + p = index(tmpfilename, 13); + *p = '\0'; + filename = strdup(tmpfilename); + } + mimestate = MIMESTUNKNOWN; + } + } + else if (GRSTstrCmpShort(oneline, + "Content-Disposition: form-data; name=\"uploadfile\"; filename=\"") + == 0) + { + mimestate = MIMESTUPLOAD; + if (filename == NULL) + { + filename = strdup(&oneline[61]); + + p = rindex(&oneline[61], '\\'); + if (p != NULL) { ++p ; filename = p; } + + p = rindex(&oneline[61], '/'); + if (p != NULL) { ++p ; filename = p; } + + p = index(filename, '"'); + if (p != NULL) *p = '\0'; + } + } + else if (GRSTstrCmpShort(oneline, + "Content-Disposition: form-data; name=\"file\"") == 0) + { + mimestate = MIMESTFILENM; + } + } + + if ((filebuffer != NULL) && (bufferused >= 0)) + { + if (filename == NULL) GRSThttpError("403 Forbidden"); + else if ((index(filename, '/') != NULL) || + (strcmp(filename, GRST_ACL_FILE) == 0)) + { + puts("Status: 403 Forbidden filename\nContent-Type: text/html"); + + GRSThttpBodyInit(&bp); + + GRSThttpPrintf(&bp,"Forbidden filename %s\n", filename); + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE); + + GRSThttpPrintf(&bp, "

Forbidden filename %s

\n", + filename); + + GRSThttpPrintf(&bp, + "

New file names cannot include slashes " + "or use the reserved ACL name, %s\n", GRST_ACL_FILE); + + GRSThttpPrintf(&bp,"

" + "Return to " + "directory listing\n", dir_uri, admin_file); + + adminfooter(&bp, dn, help_uri, dir_uri, admin_file); + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_FOOTFILE); + + GRSThttpWriteOut(&bp); + return; + } + else + { + vfile = makevfilename(filename, bufferused, dn); + asprintf(&dir_path_vfile, "%s/%s", dir_path, vfile); + + fp = fopen(dir_path_vfile, "w"); + if (fp != NULL) + { + if ((fwrite(filebuffer, + sizeof(char), bufferused, fp) == bufferused) && + (fclose(fp) == 0)) + { + asprintf(&filepath, "%s/%s", dir_path, filename); + + unlink(filepath); /* this can fail ok */ + + itworked = (link(dir_path_vfile, filepath) == 0); + } + } + } + + free((void *) filebuffer); + } + + if (itworked) + { + printf("Status: 302 Moved Temporarily\nContent-Length: 0\n" + "Location: %s%s?cmd=managedir\n\n", dir_uri, admin_file); + return; + } + + puts("Status: 500 Failed trying to upload\nContent-Type: text/html"); + + GRSThttpBodyInit(&bp); + + GRSThttpPrintf(&bp, "Failed to upload\n"); + + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE); + + GRSThttpPrintf(&bp, "

Failed to upload

\n"); + + GRSThttpPrintf(&bp, "

GridSite considers you are authorized " + "to upload the file, but the upload failed. This is " + "probably a web server or operating system level " + "misconfiguration. Consult the site administrator."); + + GRSThttpPrintf(&bp,"

" + "Return to " + "directory listing\n", dir_uri, admin_file); + + adminfooter(&bp, dn, help_uri, dir_uri, admin_file); + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_FOOTFILE); + + GRSThttpWriteOut(&bp); +} + +void deletefileaction(char *dn, GRSTgaclPerm perm, char *help_uri, + char *dir_path, char *file, char *dir_uri, + char *admin_file) +{ + int fd, numfiles; + char *dir_path_file, *dir_path_vfile, *p, *vfile, *dnlistsuri, + *fulluri, *server_name, *realfile; + struct stat statbuf; + GRSThttpBody bp; + struct dirent *subdirfile_ent; + DIR *subDIR; + + if (((strcmp(file, GRST_ACL_FILE) != 0) && !GRSTgaclPermHasWrite(perm)) || + ((strcmp(file, GRST_ACL_FILE) == 0) && !GRSTgaclPermHasAdmin(perm))) + GRSThttpError("403 Forbidden"); + + dnlistsuri = getenv("GRST_DN_LISTS_URI"); + if (dnlistsuri == NULL) dnlistsuri = getenv("REDIRECT_GRST_DN_LISTS_URI"); + + if ((dnlistsuri != NULL) && + (strncmp(dnlistsuri, dir_uri, strlen(dnlistsuri)) == 0)) + realfile = GRSThttpUrlEncode(file); + else if (index(file, '/') != NULL) GRSThttpError("403 Forbidden"); + else realfile = file; + + dir_path_file = malloc(strlen(dir_path) + strlen(realfile) + 2); + + strcpy(dir_path_file, dir_path); + strcat(dir_path_file, "/"); + strcat(dir_path_file, realfile); + + if ((stat(dir_path_file, &statbuf) == 0) && S_ISDIR(statbuf.st_mode)) + { + subDIR = opendir(dir_path_file); + if (subDIR == NULL) numfiles = 99; /* stop deletion */ + else + { + numfiles = 0; + while ((subdirfile_ent = readdir(subDIR)) != NULL) + if (subdirfile_ent->d_name[0] != '.') ++numfiles; + else if (strncmp(subdirfile_ent->d_name, + GRST_ACL_FILE, + sizeof(GRST_ACL_FILE)) == 0) ++numfiles; + closedir(subDIR); + } + + if (numfiles == 0) + { + vfile = makevfilename(file, 0, dn); + dir_path_vfile = malloc(strlen(dir_path) + strlen(vfile) + 2); + strcpy(dir_path_vfile, dir_path); + strcat(dir_path_vfile, "/"); + strcat(dir_path_vfile, vfile); + + if (rename(dir_path_file, dir_path_vfile) == 0) + { + printf("Status: 302 Moved Temporarily\nContent-Length: 0\n" + "Location: %s%s?cmd=managedir\n\n", dir_uri, admin_file); + return; + } + } + } + else if (unlink(dir_path_file) == 0) + { + if (strcmp(file, GRST_ACL_FILE) != 0) + { + vfile = makevfilename(file, 0, dn); + dir_path_file = malloc(strlen(dir_path) + strlen(vfile) + 2); + strcpy(dir_path_file, dir_path); + strcat(dir_path_file, "/"); + strcat(dir_path_file, vfile); + + fd = open(dir_path_file, O_WRONLY | O_CREAT); + if (fd != -1) close(fd); + } + + printf("Status: 302 Moved Temporarily\nContent-Length: 0\n" + "Location: %s%s?cmd=managedir\n\n", dir_uri, admin_file); + + return; + } + + puts("Status: 500 Failed trying to delete\nContent-Type: text/html"); + + GRSThttpBodyInit(&bp); + + GRSThttpPrintf(&bp, "Error deleting %s%s\n", dir_uri, file); + + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE); + + GRSThttpPrintf(&bp, "

Error deleting %s%s

\n", + dir_uri, file); + + GRSThttpPrintf(&bp, "

GridSite considers you are authorized " + "to delete %s, but the delete failed. This is " + "probably a web server or operating system level " + "misconfiguration. Consult the site administrator.", + file); + + GRSThttpPrintf(&bp,"

" + "Return to " + "directory listing\n", dir_uri, admin_file); + + adminfooter(&bp, dn, help_uri, dir_uri, admin_file); + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_FOOTFILE); + + GRSThttpWriteOut(&bp); +} + +void deletefileform(char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, + char *file, char *dir_uri, char *admin_file) +{ + GRSThttpBody bp; + + if (!GRSTgaclPermHasWrite(perm)) GRSThttpError("403 Forbidden"); + + puts("Status: 200 OK\nContent-Type: text/html"); + + GRSThttpBodyInit(&bp); + + GRSThttpPrintf(&bp, "Delete %s\n", file); + + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE); + + GRSThttpPrintf(&bp, "

Delete %s

\n", file); + + GRSThttpPrintf(&bp,"
\n",dir_uri,admin_file); + GRSThttpPrintf(&bp,"

Do you really want to delete %s?", file); + GRSThttpPrintf(&bp,"

\n", file); + GRSThttpPrintf(&bp,"\n", file); + GRSThttpPrintf(&bp,"\n"); + GRSThttpPrintf(&bp,"
\n"); + + GRSThttpPrintf(&bp,"

Or " + "return to " + "directory listing\n", dir_uri, admin_file); + + adminfooter(&bp, dn, help_uri, dir_uri, admin_file); + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_FOOTFILE); + + GRSThttpWriteOut(&bp); +} + +void renameform(char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, + char *file, char *dir_uri, char *admin_file) +{ + GRSThttpBody bp; + + if (!GRSTgaclPermHasWrite(perm)) GRSThttpError("403 Forbidden"); + + puts("Status: 200 OK\nContent-Type: text/html"); + + GRSThttpBodyInit(&bp); + + GRSThttpPrintf(&bp, "Rename %s\n", file); + + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE); + + GRSThttpPrintf(&bp, "

Rename %s%s

\n", dir_uri, file); + + GRSThttpPrintf(&bp,"
\n",dir_uri,admin_file); + GRSThttpPrintf(&bp,"

What do you want to rename %s to?

", file); + GRSThttpPrintf(&bp,"\n", file); + GRSThttpPrintf(&bp,"

New name: \n", file); + GRSThttpPrintf(&bp,"\n"); + GRSThttpPrintf(&bp,"\n"); + GRSThttpPrintf(&bp,"

\n"); + + GRSThttpPrintf(&bp,"

Or " + "return to " + "directory listing\n", dir_uri, admin_file, dir_uri); + + adminfooter(&bp, dn, help_uri, dir_uri, admin_file); + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_FOOTFILE); + + GRSThttpWriteOut(&bp); +} + +void editfileaction(char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, + char *file, char *dir_uri, char *admin_file) +{ + char *pagetext, *dir_path_file, *vfile, *dir_path_vfile, + *dnlistsuri, *server_name, *fulluri, *realfile; + FILE *fp; + GRSThttpBody bp; + + if (!GRSTgaclPermHasWrite(perm) || (strcmp(file, GRST_ACL_FILE) == 0)) + GRSThttpError("403 Forbidden"); + + dnlistsuri = getenv("GRST_DN_LISTS_URI"); + if (dnlistsuri == NULL) dnlistsuri = getenv("REDIRECT_GRST_DN_LISTS_URI"); + + if ((dnlistsuri != NULL) && + (strncmp(dnlistsuri, dir_uri, strlen(dnlistsuri)) == 0)) + { + realfile = GRSThttpUrlEncode(file); + + if (realfile[0] == '.') GRSThttpError("403 Forbidden"); + } + else if (index(file, '/') != NULL) GRSThttpError("403 Forbidden"); + else realfile = file; + + asprintf(&dir_path_file, "%s/%s", dir_path, realfile); + + pagetext = GRSThttpGetCGI("pagetext"); + vfile = makevfilename(file, strlen(pagetext), dn); + asprintf(&dir_path_vfile, "%s/%s", dir_path, vfile); + + fp = fopen(dir_path_vfile, "w"); + if (fp == NULL) + { + puts("Status: 500 Failed trying to write\nContent-Type: text/html"); + + GRSThttpBodyInit(&bp); + + GRSThttpPrintf(&bp,"Error writing %s%s\n", dir_uri, file); + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE); + + GRSThttpPrintf(&bp, "

Error writing %s%s

\n", + dir_uri, file); + + GRSThttpPrintf(&bp, + "

GridSite considers you are authorized " + "to write the file, but the write failed. This is " + "probably a web server or operating system level " + "misconfiguration. Consult the site administrator."); + + GRSThttpPrintf(&bp,"

" + "Return to " + "directory listing\n", dir_uri, admin_file); + + adminfooter(&bp, dn, help_uri, dir_uri, admin_file); + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_FOOTFILE); + + GRSThttpWriteOut(&bp); + return; + } + + fwrite(pagetext, strlen(pagetext), sizeof(char), fp); + + fclose(fp); + + unlink(dir_path_file); + + if (link(dir_path_vfile,dir_path_file) != 0) GRSThttpError("403 Forbidden"); + + if ((strlen(file) > 7) && (strcmp(&file[strlen(file) - 5], ".html") == 0)) + printf("Status: 302 Moved Temporarily\nContent-Length: 0\n" + "Location: %s%s\n\n", dir_uri, file); + else printf("Status: 302 Moved Temporarily\nContent-Length: 0\n" + "Location: %s%s?cmd=managedir\n\n", dir_uri, admin_file); +} + +void create_acl(char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, + char *file, char *dir_uri, char *admin_file) +{ + int fd; + char *tmpgacl, *newgacl; + GRSTgaclAcl *acl; + FILE *fp; + GRSThttpBody bp; + + if (!GRSTgaclPermHasAdmin(perm)) GRSThttpError("403 Forbidden"); + + asprintf(&tmpgacl, "%s/.tmp.XXXXXX", dir_path); + asprintf(&newgacl, "%s/%s", dir_path, GRST_ACL_FILE); + + if (((acl = GRSTgaclAclLoadforFile(dir_path)) != NULL) && + ((fd = mkstemp(tmpgacl)) != -1) && + ((fp = fdopen(fd, "w+")) != NULL) && + GRSTgaclAclPrint(acl, fp) && + (fclose(fp) == 0) && + (rename(tmpgacl, newgacl) == 0)) + { + printf("Status: 302 Moved Temporarily\nContent-Length: 0\n" + "Location: %s%s?cmd=managedir\n\n", dir_uri, admin_file); + + free(tmpgacl); + free(newgacl); + return; + } + + puts("Status: 500 Failed trying to create\nContent-Type: text/html"); + + GRSThttpBodyInit(&bp); + + GRSThttpPrintf(&bp,"Error creating %s%s\n", dir_uri, + GRST_ACL_FILE); + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE); + + GRSThttpPrintf(&bp, "

Error creating %s%s

\n", + dir_uri, GRST_ACL_FILE); + + GRSThttpPrintf(&bp, "

GridSite considers you are authorized " + "to create it, but the create failed. This is " + "probably a web server or operating system level " + "misconfiguration. Consult the site administrator."); + + GRSThttpPrintf(&bp,"

" + "Return to " + "directory listing\n", dir_uri, admin_file); + + adminfooter(&bp, dn, help_uri, dir_uri, admin_file); + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_FOOTFILE); + + GRSThttpWriteOut(&bp); + + free(tmpgacl); + free(newgacl); +} + +void renameaction(char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, + char *file, char *dir_uri, char *admin_file) +{ + int len; + char *dir_path_file, *vfile, *dir_path_vfile, + *dnlistsuri, *newfile, *dir_path_newfile; + struct stat statbuf; + FILE *fp; + GRSThttpBody bp; + + if (!GRSTgaclPermHasWrite(perm) || (strcmp(file, GRST_ACL_FILE) == 0)) + GRSThttpError("403 Forbidden"); + + if (index(file, '/') != NULL) GRSThttpError("403 Forbidden"); + + dir_path_file = malloc(strlen(dir_path) + strlen(file) + 2); + strcpy(dir_path_file, dir_path); + strcat(dir_path_file, "/"); + strcat(dir_path_file, file); + + if (stat(dir_path_file, &statbuf) != 0) GRSThttpError("404 Not Found"); + + newfile = GRSThttpGetCGI("newfile"); + + if ((strcmp(newfile, GRST_ACL_FILE) == 0) || + (strcmp(newfile, file) == 0)) GRSThttpError("403 Forbidden"); + + dir_path_newfile = malloc(strlen(dir_path) + strlen(newfile) + 2); + strcpy(dir_path_newfile, dir_path); + strcat(dir_path_newfile, "/"); + strcat(dir_path_newfile, newfile); + + vfile = makevfilename(newfile, statbuf.st_size, dn); + dir_path_vfile = malloc(strlen(dir_path) + strlen(vfile) + 2); + strcpy(dir_path_vfile, dir_path); + strcat(dir_path_vfile, "/"); + strcat(dir_path_vfile, vfile); + + unlink(dir_path_newfile); /* just in case */ + + if ((link(dir_path_file, dir_path_vfile ) == 0) && + (link(dir_path_file, dir_path_newfile) == 0) && + (unlink(dir_path_file) == 0)) + { + printf("Status: 302 Moved Temporarily\nContent-Length: 0\n" + "Location: %s\n\n", dir_uri); + return; + } + + puts("Status: 500 Failed trying to rename\nContent-Type: text/html"); + + GRSThttpBodyInit(&bp); + + GRSThttpPrintf(&bp,"Error renaming %s%s\n", dir_uri, file); + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE); + + GRSThttpPrintf(&bp, "

Error renaming %s%s

\n", + dir_uri, file); + + GRSThttpPrintf(&bp, "

GridSite considers you are authorized " + "to rename it, but the rename failed. This is " + "probably a web server or operating system level " + "misconfiguration. Consult the site administrator."); + + GRSThttpPrintf(&bp,"

" + "Return to " + "directory listing\n", dir_uri, admin_file); + + adminfooter(&bp, dn, help_uri, dir_uri, admin_file); + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_FOOTFILE); + + GRSThttpWriteOut(&bp); +} + +void newdirectory(char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, + char *file, char *dir_uri, char *admin_file) +{ + int len; + char *dir_path_file, *vfile, *dir_path_vfile, *filedup; + FILE *fp; + GRSThttpBody bp; + + if ((file[0] == '\0') || + !GRSTgaclPermHasWrite(perm) || (strcmp(file, GRST_ACL_FILE) == 0)) + GRSThttpError("403 Forbidden"); + + filedup = strdup(file); + if (filedup[strlen(filedup)-1] == '/') filedup[strlen(filedup)-1] = '\0'; + if (index(filedup, '/') != NULL) GRSThttpError("403 Forbidden"); + + dir_path_file = malloc(strlen(dir_path) + strlen(file) + 2); + strcpy(dir_path_file, dir_path); + strcat(dir_path_file, "/"); + strcat(dir_path_file, file); + + if (mkdir(dir_path_file, 0751) == 0) + { + printf("Status: 302 Moved Temporarily\nContent-Length: 0\n" + "Location: %s%s?cmd=managedir\n\n", dir_uri, admin_file); + return; + } + + puts("Status: 500 Failed trying to create\nContent-Type: text/html"); + + GRSThttpBodyInit(&bp); + + GRSThttpPrintf(&bp,"Error create %s%s\n", dir_uri, file); + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE); + + GRSThttpPrintf(&bp, "

Error creating directory %s%s

\n", + dir_uri, file); + + GRSThttpPrintf(&bp, + "

GridSite considers you are authorized " + "to create the directory, but the creation failed. This " + "is probably a web server or operating system level " + "misconfiguration. Consult the site administrator."); + + GRSThttpPrintf(&bp,"

" + "Return to " + "parent directory listing\n", dir_uri, admin_file); + + adminfooter(&bp, dn, help_uri, dir_uri, admin_file); + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_FOOTFILE); + + GRSThttpWriteOut(&bp); +} + +void editdnlistaction(char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, + char *file, char *dir_uri, char *admin_file) +{ + int numdn = 0, ifd, ofd, numdnlines = 0, i, found; + char *dir_path_file, *dir_path_tmpfile, *realfile, + *dnlistsuri, *server_name, *fulldiruri, *p, oneline[513], + **dnlines, name[81], *add; + FILE *ofp; + struct stat statbuf; + GRSThttpBody bp; + + if (!GRSTgaclPermHasWrite(perm)) GRSThttpError("403 Forbidden"); + + dnlistsuri = getenv("GRST_DN_LISTS_URI"); + if (dnlistsuri == NULL) dnlistsuri = getenv("REDIRECT_GRST_DN_LISTS_URI"); + + server_name = getenv("SERVER_NAME"); + + if ((server_name == NULL) || + (dnlistsuri == NULL) || + (strncmp(dnlistsuri, dir_uri, strlen(dnlistsuri)) != 0)) + GRSThttpError("403 Forbidden"); + + asprintf(&fulldiruri, "https://%s%s", server_name, dir_uri); + + if ((strncmp(fulldiruri, file, strlen(fulldiruri)) != 0) && + ((strncmp(fulldiruri, file, strlen(fulldiruri) - 1) != 0) || + (strlen(fulldiruri) - 1 != strlen(file)))) + { + puts("Status: 403 Forbidden\nContent-Type: text/html"); + + GRSThttpBodyInit(&bp); + + GRSThttpPrintf(&bp,"Error writing %s\n", file); + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE); + + GRSThttpPrintf(&bp, "

Error writing %s to %s

\n", + file, dir_uri); + + GRSThttpPrintf(&bp, "

You cannot create a DN List " + "with that prefix in this directory. Please see the " + "the GridSite User's Guide for an explanation."); + + GRSThttpPrintf(&bp,"

" + "Return to " + "directory listing\n", dir_uri, admin_file); + + adminfooter(&bp, dn, help_uri, dir_uri, admin_file); + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_FOOTFILE); + + GRSThttpWriteOut(&bp); + return; + } + + p = GRSThttpGetCGI("numdn"); + if ((p == NULL) || (sscanf(p, "%d", &numdn) != 1)) + GRSThttpError("500 No number of DNs"); + + if (numdn > 0) + { + dnlines = malloc(sizeof(char *) * numdn); + + for (i=1; i <= numdn; ++i) + { + sprintf(name, "dn%d", i); + p = GRSThttpGetCGI(name); + + if (*p != '\0') + { + dnlines[numdnlines] = p; + ++numdnlines; + } + } + } + + add = GRSThttpGetCGI("add"); + + realfile = GRSThttpUrlEncode(file); + + dir_path_file = malloc(strlen(dir_path) + strlen(realfile) + 2); + strcpy(dir_path_file, dir_path); + strcat(dir_path_file, "/"); + strcat(dir_path_file, realfile); + + dir_path_tmpfile = malloc(strlen(dir_path) + 13); + strcpy(dir_path_tmpfile, dir_path); + strcat(dir_path_tmpfile, "/.tmp.XXXXXX"); + + if (((ofd = mkstemp(dir_path_tmpfile)) != -1) && + ((ofp = fdopen(ofd, "w")) != NULL)) + { + if (*add != '\0') + { + fputs(add, ofp); + fputc('\n', ofp); + } + + for (i=0; i < numdnlines; ++i) + { + fputs(dnlines[i], ofp); + fputc('\n', ofp); + } + + if ((fclose(ofp) == 0) && + ((stat(dir_path_file, &statbuf) != 0) || + (unlink(dir_path_file) == 0)) && + (rename(dir_path_tmpfile, dir_path_file) == 0)) + { + printf("Status: 302 Moved Temporarily\nContent-Length: 0\n" + "Location: %s%s?cmd=managedir\n\n", dir_uri, admin_file); + return; + } + } + + puts("Status: 500 Failed trying to write\nContent-Type: text/html"); + + GRSThttpBodyInit(&bp); + + GRSThttpPrintf(&bp,"Error writing %s%s\n", dir_uri, file); + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE); + + GRSThttpPrintf(&bp, "

Error writing %s%s

\n", + dir_uri, file); + + GRSThttpPrintf(&bp, "

GridSite considers you are authorized " + "to write the file, but the write failed. This is " + "probably a web server or operating system level " + "misconfiguration. Consult the site administrator."); + + GRSThttpPrintf(&bp,"

" + "Return to " + "directory listing\n", dir_uri, admin_file); + + adminfooter(&bp, dn, help_uri, dir_uri, admin_file); + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_FOOTFILE); + + GRSThttpWriteOut(&bp); + + /* try to clean up */ + if (stat(dir_path_tmpfile, &statbuf) == 0) unlink(dir_path_tmpfile); +} + +void printfile(char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, + char *file, char *dir_uri, char *admin_file) +{ + int fd; + char *dir_path_file; + struct stat statbuf; + + if (!GRSTgaclPermHasRead(perm)) GRSThttpError("403 Forbidden"); + + if (index(file, '/') != NULL) GRSThttpError("403 Forbidden"); + + dir_path_file = malloc(strlen(dir_path) + strlen(file) + 2); + + strcpy(dir_path_file, dir_path); + strcat(dir_path_file, "/"); + strcat(dir_path_file, file); + + fd = open(dir_path_file, O_RDONLY); + if (fd == -1) GRSThttpError("500 Internal server error"); + + if ((fstat(fd, &statbuf) != 0) || + !S_ISREG(statbuf.st_mode)) GRSThttpError("403 Forbidden"); + + printf("Status: 200 OK\nContent-Type: text/html\nContent-Length: %d\n\n", + statbuf.st_size); + + fflush(stdout); + + sendfile(1, fd, 0, statbuf.st_size); +} + +void filehistory(char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, + char *file, char *dir_uri, char *admin_file) +{ + int fd, n, i, j, enclen, num = 0; + char *encodedfile, *p, *dndecoded, modified[99], *vfile, *q, + *encdn; + time_t file_time; + size_t file_size; + struct stat statbuf; + struct dirent **namelist; + struct tm file_tm; + GRSThttpBody bp; + + if (!GRSTgaclPermHasRead(perm)) GRSThttpError("403 Forbidden"); + + if (index(file, '/') != NULL) GRSThttpError("403 Forbidden"); + + puts("Status: 200 OK\nContent-Type: text/html"); + + GRSThttpBodyInit(&bp); + GRSThttpPrintf(&bp, "History of %s%s\n", dir_uri, file); + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE); + GRSThttpPrintf(&bp, + "

History of %s%s

\n", + dir_uri, file, dir_uri, file); + + asprintf(&vfile, "%s/%s", dir_path, file); + if (stat(vfile, &statbuf) == 0) + { + localtime_r((const time_t *) &(statbuf.st_mtime), &file_tm); + strftime(modified, sizeof(modified), + "%a %e %b %Y %k:%M", &file_tm); + + GRSThttpPrintf(&bp, "

Last modified: %s\n", modified); + } + free(vfile); + + encodedfile = GRSThttpUrlEncode(file); + for (p=encodedfile; *p != '\0'; ++p) if (*p == '%') *p = '='; + enclen = strlen(encodedfile); + + n = scandir(dir_path, &namelist, 0, alphasort); + + if (n > 0) + { + for (i = n - 1; i >= 0; --i) + { + if ((strncmp(namelist[i]->d_name, GRST_HIST_PREFIX, + sizeof(GRST_HIST_PREFIX) - 1) == 0) && + ((namelist[i]->d_name)[sizeof(GRST_HIST_PREFIX) - 1] == ':') && + (strncmp(&((namelist[i]->d_name)[sizeof(GRST_HIST_PREFIX)]), + encodedfile, enclen) == 0) && + ((namelist[i]->d_name)[sizeof(GRST_HIST_PREFIX)+enclen] == ':')) + { + if (num == 0) GRSThttpPrintf(&bp, + "

\n" + "" + "\n"); + + ++num; + + p = index(namelist[i]->d_name, ':'); + p = index(&p[1], ':'); + sscanf(&p[1], "%X:", &file_time); + p = index(&p[1], ':'); /* skip over microseconds time */ + p = index(&p[1], ':'); + sscanf(&p[1], "%X:", &file_size); + p = index(&p[1], ':'); + + encdn = strdup(&p[1]); + q = index(encdn, ':'); + if (q != NULL) *q = '\0'; + + for (q=encdn; *q != '\0'; ++q) if (*q == '=') *q = '%'; + dndecoded = GRSThttpUrlDecode(encdn); + + localtime_r((const time_t *) &file_time, &file_tm); + strftime(modified, sizeof(modified), + "%a %e %b %Y %k:%M", &file_tm); + + GRSThttpPrintf(&bp, + "\n", + modified, file_size, dndecoded); + + free(dndecoded); + + asprintf(&vfile, "%s/%s", dir_path, namelist[i]->d_name); + if ((stat(vfile, &statbuf) == 0) && (statbuf.st_size > 0)) + { + GRSThttpPrintf(&bp, "\n", + dir_uri, admin_file, dir_uri, namelist[i]->d_name); + else GRSThttpPrintf(&bp, "%s%s\">View\n", + dir_uri, namelist[i]->d_name); + } + else GRSThttpPrintf(&bp, ""); + + free(vfile); + } + } + } + + if (num > 0) GRSThttpPrintf(&bp, "
DateSize afterChanged by
%s%d%sView
 
\n"); + else GRSThttpPrintf(&bp, "

No history for this file\n"); + + if (GRSTgaclPermHasList(perm)) + adminfooter(&bp, dn, help_uri, dir_uri, admin_file); + else adminfooter(&bp, dn, help_uri, dir_uri, NULL); + + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_FOOTFILE); + GRSThttpWriteOut(&bp); +} + +void ziplist(char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, + char *file, char *dir_uri, char *admin_file) +{ + char *shellcmd, *unzip, oneline[129]; + FILE *fp; + GRSThttpBody bp; + + if (!GRSTgaclPermHasRead(perm)) GRSThttpError("403 Forbidden"); + + if (index(file, '/') != NULL) GRSThttpError("403 Forbidden"); + + puts("Status: 200 OK\nContent-Type: text/html"); + + GRSThttpBodyInit(&bp); + GRSThttpPrintf(&bp, "Contents of %s%s\n", dir_uri, file); + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE); + GRSThttpPrintf(&bp, + "

Contents of ZIP file %s%s

\n", + dir_uri, file, dir_uri, file); + + unzip = getenv("GRST_UNZIP"); + if (unzip == NULL) unzip = getenv("REDIRECT_GRST_UNZIP"); + + if (unzip != NULL) + { + GRSThttpPrintf(&bp, "
\n");
+      asprintf(&shellcmd, "cd %s ; %s -Z %s", dir_path, unzip, file);
+      fp = popen(shellcmd, "r");
+  
+      while (fgets(oneline, sizeof(oneline), fp) != NULL)           
+                          GRSThttpPrintf(&bp, "%s", oneline);         
+      pclose(fp);
+      GRSThttpPrintf(&bp, "
\n"); + + if (GRSTgaclPermHasWrite(perm)) + GRSThttpPrintf(&bp, + "

" + " in %s" + "" + "
" + "

(All files are placed in the same directory and files " + "beginning with "." are ignored.)

\n", + dir_uri, admin_file, dir_uri, file); + } + else GRSThttpPrintf(&bp, "

unzip path not defined!\n"); + + if (GRSTgaclPermHasList(perm)) + adminfooter(&bp, dn, help_uri, dir_uri, admin_file); + else adminfooter(&bp, dn, help_uri, dir_uri, NULL); + + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_FOOTFILE); + GRSThttpWriteOut(&bp); +} + +void unzipfile(char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, + char *file, char *dir_uri, char *admin_file) +{ + char *shellcmd, *unzip, oneline[129]; + FILE *fp; + GRSThttpBody bp; + + if (!GRSTgaclPermHasWrite(perm)) GRSThttpError("403 Forbidden"); + + if (index(file, '/') != NULL) GRSThttpError("403 Forbidden"); + + puts("Status: 200 OK\nContent-Type: text/html"); + + GRSThttpBodyInit(&bp); + GRSThttpPrintf(&bp, "Unzipping %s%s\n", dir_uri, file); + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE); + GRSThttpPrintf(&bp, + "

Unzipping %s%s

\n", + dir_uri, file, dir_uri, file); + + unzip = getenv("GRST_UNZIP"); + if (unzip == NULL) unzip = getenv("REDIRECT_GRST_UNZIP"); + + if (unzip != NULL) + { + GRSThttpPrintf(&bp, "
\n");
+      asprintf(&shellcmd, "cd %s ; %s -jo %s -x '.*'", dir_path, unzip, file);
+      fp = popen(shellcmd, "r");
+  
+      while (fgets(oneline, sizeof(oneline), fp) != NULL)           
+                          GRSThttpPrintf(&bp, "%s", oneline);         
+      pclose(fp);
+      GRSThttpPrintf(&bp, "
\n"); + + if (GRSTgaclPermHasList(perm)) + GRSThttpPrintf(&bp, "

" + "Back to " + "directory", dir_uri, admin_file); + } + else GRSThttpPrintf(&bp, "

unzip path not defined!\n"); + + if (GRSTgaclPermHasList(perm)) + adminfooter(&bp, dn, help_uri, dir_uri, admin_file); + else adminfooter(&bp, dn, help_uri, dir_uri, NULL); + + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_FOOTFILE); + GRSThttpWriteOut(&bp); +} + +void editfileform(char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, + char *file, char *dir_uri, char *admin_file) +{ + int fd, rawpagesize, i, c; + char *dir_path_file, *rawpage, *p; + FILE *fp = NULL; + struct stat statbuf; + GRSThttpBody bp; + + if (!GRSTgaclPermHasWrite(perm)) GRSThttpError("403 Forbidden"); + + if (index(file, '/') != NULL) GRSThttpError("403 Forbidden"); + + dir_path_file = malloc(strlen(dir_path) + strlen(file) + 2); + + strcpy(dir_path_file, dir_path); + strcat(dir_path_file, "/"); + strcat(dir_path_file, file); + + fd = open(dir_path_file, O_RDONLY); + if (fd != -1) + { + fp = fdopen(fd, "r"); + if (fp == NULL) GRSThttpError("500 File open failed!"); + + if ((fstat(fd, &statbuf) != 0) || + !S_ISREG(statbuf.st_mode)) GRSThttpError("500 Not a regular file!"); + } + + puts("Status: 200 OK\nContent-Type: text/html"); + + GRSThttpBodyInit(&bp); + + GRSThttpPrintf(&bp, "Edit file %s\n", file); + + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE); + + GRSThttpPrintf(&bp, "

Edit file %s

\n", file); + + GRSThttpPrintf(&bp,"
\n",dir_uri,admin_file); + GRSThttpPrintf(&bp,"

\n"); + GRSThttpPrintf(&bp,"

File name: \n", file); + GRSThttpPrintf(&bp,"\n"); + GRSThttpPrintf(&bp,"

\n"); + GRSThttpPrintf(&bp, "

\n"); + GRSThttpPrintf(&bp, "

\n"); + + if (fp != NULL) fclose(fp); + + adminfooter(&bp, dn, help_uri, dir_uri, admin_file); + + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_FOOTFILE); + GRSThttpWriteOut(&bp); +} + +void editdnlistform(char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, + char *file, char *dir_uri, char *admin_file) +{ + int fd, i, c, numdn = 0; + char *dir_path_file, *rawpage, *p, *dnlistsuri, *server_name, *fulluri, + *realfile, oneline[513]; + FILE *fp = NULL; + struct stat statbuf; + GRSThttpBody bp; + + dnlistsuri = getenv("GRST_DN_LISTS_URI"); + if (dnlistsuri == NULL) dnlistsuri = getenv("REDIRECT_GRST_DN_LISTS_URI"); + + if (!GRSTgaclPermHasWrite(perm) || + (dnlistsuri == NULL) || + (strncmp(dnlistsuri, dir_uri, strlen(dnlistsuri)) != 0)) + GRSThttpError("403 Forbidden"); + + realfile = GRSThttpUrlEncode(file); + + dir_path_file = malloc(strlen(dir_path) + strlen(realfile) + 2); + + strcpy(dir_path_file, dir_path); + strcat(dir_path_file, "/"); + strcat(dir_path_file, realfile); + + fd = open(dir_path_file, O_RDONLY); + if (fd != -1) /* we dont mind open failing, but it must work if it doesnt */ + { + fp = fdopen(fd, "r"); + if (fp == NULL) GRSThttpError("500 File open failed!"); + + if ((fstat(fd, &statbuf) != 0) || + !S_ISREG(statbuf.st_mode)) GRSThttpError("500 Not a regular file!"); + } + + puts("Status: 200 OK\nContent-Type: text/html"); + + GRSThttpBodyInit(&bp); + + GRSThttpPrintf(&bp, "Edit DN List %s\n", file); + + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE); + + GRSThttpPrintf(&bp, "

Edit DN List

\n"); + + GRSThttpPrintf(&bp,"
\n",dir_uri,admin_file); + GRSThttpPrintf(&bp,"

\n"); + GRSThttpPrintf(&bp,"

List URL: \n", file, strlen(file)); + GRSThttpPrintf(&bp,"\n"); + + if (fp != NULL) + { + GRSThttpPrintf(&bp, "

\n" + "\n"); + + while (fgets(oneline, sizeof(oneline), fp) != NULL) + { + ++numdn; + + p = rindex(oneline, '\n'); + if (p != NULL) *p = '\0'; + + GRSThttpPrintf(&bp, "" + "\n", numdn, oneline, oneline); + } + + GRSThttpPrintf(&bp,"
Keep?Name
%s
\n"); + } + + GRSThttpPrintf(&bp,"\n", numdn); + + GRSThttpPrintf(&bp, "

Add new DN: \n"); + + GRSThttpPrintf(&bp,"

\n"); + GRSThttpPrintf(&bp, "

\n"); + + if (fp != NULL) fclose(fp); + + adminfooter(&bp, dn, help_uri, dir_uri, admin_file); + + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_FOOTFILE); + GRSThttpWriteOut(&bp); +} + +void managedir(char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, + char *dir_uri, char *admin_file) +{ + int n, is_dnlists_dir = 0, enclen, numfiles, encprefixlen; + char *d_namepath, modified[99], *absaclpath, *editable, *p, *unzip, + *dnlistsuri, *d_name, *server_name, *fulluri, *encfulluri, + *encprefix, *dnlistsprefix; + GRSThttpBody bp; + struct tm mtime_tm; + struct stat statbuf; + struct dirent **namelist, *subdirfile_ent; + DIR *subDIR; + + if (((!GRSTgaclPermHasWrite(perm)) && + (!GRSTgaclPermHasList(perm))) || + (stat(dir_path, &statbuf) != 0) || !S_ISDIR(statbuf.st_mode)) + GRSThttpError("403 Forbidden"); + + editable = getenv("GRST_EDITABLE"); + if (editable == NULL) editable = getenv("REDIRECT_GRST_EDITABLE"); + + unzip = getenv("GRST_UNZIP"); + if (unzip == NULL) unzip = getenv("REDIRECT_GRST_UNZIP"); + + dnlistsuri = getenv("GRST_DN_LISTS_URI"); + if (dnlistsuri == NULL) dnlistsuri = getenv("REDIRECT_GRST_DN_LISTS_URI"); + + if (dnlistsuri && (strncmp(dnlistsuri, dir_uri, strlen(dnlistsuri)) == 0)) + { + is_dnlists_dir = 1; + server_name = getenv("SERVER_NAME"); + + asprintf(&fulluri, "https://%s%s", server_name, dir_uri); + encfulluri = GRSThttpUrlEncode(fulluri); + enclen = strlen(encfulluri); + + asprintf(&dnlistsprefix, "https://%s%s", server_name, dnlistsuri); + encprefix = GRSThttpUrlEncode(dnlistsprefix); + encprefixlen = strlen(encprefix); + } + + printf("Status: 200 OK\nContent-Type: text/html\n"); + + GRSThttpBodyInit(&bp); + + GRSThttpPrintf(&bp,"Manage directory %s\n", dir_uri); + + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE); + + GRSThttpPrintf(&bp, "

Manage directory %s

\n\n", dir_uri); + + if (dir_uri[1] != '\0') + GRSThttpPrintf(&bp, + "\n", admin_file); + + if (GRSTgaclPermHasList(perm) || GRSTgaclPermHasAdmin(perm)) + { + absaclpath = malloc(strlen(dir_path) + sizeof(GRST_ACL_FILE) + 1); + strcpy(absaclpath, dir_path); + strcat(absaclpath, "/"); + strcat(absaclpath, GRST_ACL_FILE); + + if (stat(absaclpath, &statbuf) == 0) /* ACL exists in THIS directory */ + { + localtime_r(&(statbuf.st_mtime), &mtime_tm); + strftime(modified, sizeof(modified), + "", + &mtime_tm); + + if (!is_dnlists_dir) + { + GRSThttpPrintf(&bp, + "" + "%s\n", + GRST_ACL_FILE, + GRST_ACL_FILE, + statbuf.st_size, modified); + + GRSThttpPrintf(&bp, + "", + dir_uri, admin_file, GRST_ACL_FILE); + } + else GRSThttpPrintf(&bp, + "" + "%s\n", + GRST_ACL_FILE, + statbuf.st_size, modified); + + if (GRSTgaclPermHasAdmin(perm)) + GRSThttpPrintf(&bp, + "" + "", + dir_uri, admin_file, + dir_uri, admin_file, GRST_ACL_FILE); + else if (GRSTgaclPermHasRead(perm)) + GRSThttpPrintf(&bp, + "" + "", dir_uri, admin_file); + else GRSThttpPrintf(&bp, "\n"); + + GRSThttpPrintf(&bp, "\n"); + } + else if (GRSTgaclPermHasAdmin(perm)) + GRSThttpPrintf(&bp, "\n" + "\n" + "\n", + dir_uri, admin_file); + } + + if (GRSTgaclPermHasList(perm)) + { + n = scandir(dir_path, &namelist, 0, alphasort); + while (n--) + { + if (namelist[n]->d_name[0] != '.') + { + d_namepath = malloc(strlen(dir_path) + + strlen(namelist[n]->d_name) + 2); + strcpy(d_namepath, dir_path); + strcat(d_namepath, "/"); + strcat(d_namepath, namelist[n]->d_name); + stat(d_namepath, &statbuf); + + if (S_ISDIR(statbuf.st_mode)) + { + subDIR = opendir(d_namepath); + + if (subDIR == NULL) numfiles = 99; /* stop deletion */ + else + { + numfiles = 0; + while ((subdirfile_ent = readdir(subDIR)) != NULL) + if (subdirfile_ent->d_name[0] != '.') ++numfiles; + else if (strncmp(subdirfile_ent->d_name, + GRST_ACL_FILE, + sizeof(GRST_ACL_FILE)) == 0) ++numfiles; + + closedir(subDIR); + } + } + + free(d_namepath); + + localtime_r(&(statbuf.st_mtime), &mtime_tm); + strftime(modified, sizeof(modified), + "", + &mtime_tm); + + if (S_ISDIR(statbuf.st_mode)) + { + GRSThttpPrintf(&bp, + "" + "%s\n", + dir_uri, namelist[n]->d_name, admin_file, + namelist[n]->d_name, + statbuf.st_size, modified); + + if (numfiles == 0) + GRSThttpPrintf(&bp, + "\n", + dir_uri, admin_file, namelist[n]->d_name); + else GRSThttpPrintf(&bp, "\n"); + + GRSThttpPrintf(&bp, "\n"); + } + else if (is_dnlists_dir) + { + if ((strlen(namelist[n]->d_name) <= encprefixlen) || + (strncmp(namelist[n]->d_name, encprefix, + encprefixlen) != 0)) continue; + + d_name = GRSThttpUrlDecode(namelist[n]->d_name); + + GRSThttpPrintf(&bp, "" + "%s" + "", + d_name, d_name, + statbuf.st_size, modified); + + if (GRSTgaclPermHasWrite(perm)) + GRSThttpPrintf(&bp, "" + "" + "" + "" + "\n", + dir_uri, admin_file, d_name); + else GRSThttpPrintf(&bp, "\n"); + + if (GRSTgaclPermHasWrite(perm)) + GRSThttpPrintf(&bp, "" + "" + "" + "" + "\n", + dir_uri, admin_file, d_name); + else GRSThttpPrintf(&bp, "\n"); + + GRSThttpPrintf(&bp, ""); + } + else /* regular directory, not DN Lists */ + { + d_name = namelist[n]->d_name; + + GRSThttpPrintf(&bp, + "" + "%s", + dir_uri, d_name, + d_name, + statbuf.st_size, modified); + + GRSThttpPrintf(&bp, + "", + dir_uri, admin_file, d_name); + + p = rindex(namelist[n]->d_name, '.'); + + if ((unzip != NULL) && + (p != NULL) && + (strcasecmp(&p[1], "zip") == 0) && + GRSTgaclPermHasRead(perm)) + GRSThttpPrintf(&bp, + "\n", + dir_uri, admin_file, d_name); + else if ((p != NULL) && + (strstr(editable, &p[1]) != NULL) && + GRSTgaclPermHasWrite(perm)) + GRSThttpPrintf(&bp, + "\n", + dir_uri, admin_file, d_name); + else GRSThttpPrintf(&bp, ""); + + if (GRSTgaclPermHasWrite(perm)) + GRSThttpPrintf(&bp, + "\n", dir_uri, admin_file, d_name); + else + GRSThttpPrintf(&bp, "\n"); + + if (GRSTgaclPermHasWrite(perm)) + GRSThttpPrintf(&bp, + "\n", dir_uri, admin_file, d_name); + else + GRSThttpPrintf(&bp, ""); + } + } + + free(namelist[n]); + } + + free(namelist); + } + + if (GRSTgaclPermHasWrite(perm)) + { + if (is_dnlists_dir) + { + GRSThttpPrintf(&bp, "\n" + "" + "\n" + "\n", + dir_uri, admin_file, fulluri, strlen(fulluri)+8); + + GRSThttpPrintf(&bp, "\n" + "\n" + "\n", + dir_uri, admin_file); + } + else + { + GRSThttpPrintf(&bp, "\n" + "\n" + "" + "\n" + "\n" + "\n", + dir_uri, admin_file); + + GRSThttpPrintf(&bp, + "\n" + "\n" + "" + "" + "\n" + "" + "\n" + "\n", dir_uri, admin_file); + } + } + + GRSThttpPrintf(&bp, "
[Parent " + "directory]
%R%e %b %y
%s%ld" + "History
%s%ldEditDeleteView    
%R%e %b %y
" + "%s/%ld " + "Delete  
%s%ld 
 
  
%s%ld" + "History" + "List" + "Edit " + "Delete " + "Rename
 
New list name: " + "\n" + "
New directory: " + "\n" + "

New name:\n" + "

Upload file:New name: " + "
Local name:
\n"); + + if (!is_dnlists_dir) adminfooter(&bp, dn, help_uri, dir_uri, NULL); + + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_FOOTFILE); + GRSThttpWriteOut(&bp); +} + diff --git a/org.gridsite.core/src/grst_admin_gacl.c b/org.gridsite.core/src/grst_admin_gacl.c new file mode 100644 index 0000000..318277e --- /dev/null +++ b/org.gridsite.core/src/grst_admin_gacl.c @@ -0,0 +1,968 @@ +/* + Copyright (c) 2003, Shiv Kaushal, University of Manchester + All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + o Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + o Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +/*---------------------------------------------------------------------------* +* This program is part of GridSite: http://www.gridpp.ac.uk/authz/gridsite/ * +*---------------------------------------------------------------------------*/ + +#include +#include +#include +#include +#include +#include + +extern char *grst_perm_syms[]; +extern int grst_perm_vals[]; + +#include "grst_admin.h" + +// CGI GACL Editor interface functions +void show_acl(int admin, GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file); +void new_entry_form(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file); +void new_entry(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file); +void del_entry(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file); +void edit_entry_form(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file); +void edit_entry(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file); +void add_cred_form(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file); +void add_cred(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file); +void del_cred(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file); +void del_entry_sure(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file); +void del_cred_sure(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file); +void admin_continue(char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file, GRSThttpBody *bp); + +// Functions for producing HTML output +void StartHTML(GRSThttpBody *bp, char *dir_uri, char* dir_path); +void StartForm(GRSThttpBody *bp, char* dir_uri, char* dir_path, char* admin_file, int timestamp, char* target_function); +void EndForm(GRSThttpBody *bp); +void GRSTgaclCredTableStart(GRSThttpBody *bp); +void GRSTgaclCredTableAdd(GRSTgaclUser *user, GRSTgaclEntry *entry, GRSTgaclCred *cred, GRSTgaclNamevalue *namevalue, int cred_no, int entry_no, int admin, int timestamp, GRSThttpBody *bp, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file); +void GRSTgaclCredTableEnd(GRSTgaclEntry* entry, int entry_no, int admin, int timestamp, GRSThttpBody *bp, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file); + +// ACL Manipulation functions +int GACLentriesInAcl(GRSTgaclAcl *acl); +int GRSTgaclCredsInEntry(GRSTgaclEntry *entry); +void check_acl_save(char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file, GRSTgaclUser* user, GRSTgaclAcl *acl, GRSThttpBody *bp); +void GACLeditGetPerms(GRSTgaclEntry *entry); +GRSTgaclEntry *GACLreturnEntry(GRSTgaclAcl *acl, int entry_no); +GRSTgaclCred *GACLreturnCred(GRSTgaclEntry *entry, int cred_no); + +void StringHTMLEncode (char* string, GRSThttpBody *bp); + +void revert_acl(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file); + +/*****************************************/ +/********** FUNCTIONS FOLLOW *************/ +/*****************************************/ + +void show_acl(int admin, GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file){ + // Shows the contents of the ACL. Gives edit 'buttons' if (int admin) == 1 + GRSTgaclAcl *acl; + GRSTgaclEntry *entry; + GRSTgaclCred *cred; + GRSTgaclNamevalue *namevalue; + int entry_no, cred_no, allow, deny,timestamp; + GRSThttpBody bp; + char* AclFilename; + struct stat file_info; + int history_mode=0; + + if (admin==2){ + history_mode=1; + admin=0; + } + + /*double-check access permision*/ + if (!GRSTgaclPermHasAdmin(perm)) admin=0; + + StartHTML(&bp, dir_uri, dir_path); + + /* Load ACL from file and get timestamp*/ + if (history_mode==1) { + AclFilename=malloc(strlen(dir_path)+strlen(file)+2); + strcpy(AclFilename, dir_path); + strcat(AclFilename, "/"); + strcat(AclFilename, file); + } + else AclFilename=GRSTgaclFileFindAclname(dir_path); + + if (AclFilename==NULL){ + GRSThttpPrintf ( &bp,"The ACL was not found !!!
\n"); + admin_continue(dn, perm, help_uri, dir_path, file, dir_uri, admin_file, &bp); + return; + } + + stat(GRSTgaclFileFindAclname(dir_path), &file_info); + timestamp=file_info.st_mtime; + acl = GRSTgaclAclLoadFile(AclFilename); + + if (acl==NULL){ + GRSThttpPrintf ( &bp,"The ACL was found but could not be loaded - it could be incorrectly formatted
\n"); + adminfooter(&bp, dn, help_uri, dir_uri, NULL); + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_FOOTFILE); + GRSThttpWriteOut(&bp); + return; + } + + if (admin) GRSThttpPrintf (&bp,"New Entry
\n", dir_uri, admin_file, dir_uri, timestamp ); + + // Start with the first entry in the list and work through + entry=acl->firstentry; + entry_no=1; + while (entry!=NULL){ + + GRSThttpPrintf (&bp,"
Entry %d:\n", entry_no); + if (admin){ + GRSThttpPrintf (&bp,"Edit Entry ", dir_uri, admin_file, entry_no, dir_uri, timestamp ); + GRSThttpPrintf (&bp,"Delete Entry ",dir_uri, admin_file, entry_no, dir_uri, timestamp ); + GRSThttpPrintf (&bp,"

\n"); + } + + GRSTgaclCredTableStart(&bp); + + // Start with the first credential in the entry and work through + cred=entry->firstcred; + cred_no=1; + while (cred!=NULL){ + namevalue=cred->firstname; + GRSTgaclCredTableAdd(user, entry, cred, namevalue, cred_no, entry_no, admin, timestamp, &bp, dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + // Change to next credential + cred=cred->next; + cred_no++; + } + + GRSTgaclCredTableEnd (entry, entry_no, admin, timestamp, &bp, dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + // Change to next entry + entry=entry->next; + entry_no++; + } + + if (!admin && GRSTgaclPermHasAdmin(perm) && !history_mode) //Print a link for admin mode, if not in admin mode but the user has admin permissions + GRSThttpPrintf (&bp,"Admin Mode", dir_uri, admin_file, dir_uri, timestamp ); + if (history_mode==1 && GRSTgaclDNlistHasUser(getenv("REDIRECT_GRST_ADMIN_LIST"), user)){ + StartForm(&bp, dir_uri, dir_path, admin_file, timestamp, "revert_acl"); +//GRSThttpPrintf (&bp,"Revert to this Version", dir_uri, admin_file, dir_uri, timestamp, file ); + GRSThttpPrintf (&bp, "\n", file); + // Revert Button + GRSThttpPrintf (&bp, "

\n\n"); + } + + adminfooter(&bp, dn, help_uri, dir_uri, NULL); + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_FOOTFILE); GRSThttpWriteOut(&bp); return; +} + + +void new_entry_form(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm,char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file){ + // Presents the user with a form asking for details required to create a new entry + GRSThttpBody bp; + int timestamp=atol(GRSThttpGetCGI("timestamp")); + GRSTgaclCred* cred; + GRSTgaclEntry *entry; + GRSTgaclNamevalue* namevalue; + + if (!GRSTgaclPermHasAdmin(perm)) GRSThttpError ("403 Forbidden"); + + StartHTML(&bp, dir_uri, dir_path); + StartForm(&bp, dir_uri, dir_path, admin_file, timestamp, "new_entry"); + GRSThttpPrintf (&bp, "NEW ENTRY IN ACL FOR %s

\n", dir_uri); + + GRSTgaclCredTableStart(&bp); + GRSTgaclCredTableAdd(user, entry,cred, namevalue, 0, 0, 0, timestamp, &bp, dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + GRSTgaclCredTableEnd (entry, 0, 0, timestamp, &bp, dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + + /*Submit and reset buttons - submit button sends the data in the form back to the script & new_entry() to be called*/ + EndForm(&bp); + admin_continue(dn, perm, help_uri, dir_path, file, dir_uri, admin_file, &bp); + return; +} + +void new_entry(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file){ + // Processes the information entered into the form from new_entry_form() and adds a new entry to the ACL + GRSTgaclAcl *acl; + GRSTgaclEntry *entry; + GRSTgaclCred *cred; + char *type, *value; + GRSThttpBody bp; + if (!GRSTgaclPermHasAdmin(perm)) GRSThttpError ("403 Forbidden"); + + // Get new credential info and perform checks + type=GRSThttpGetCGI("type"); + value=GRSThttpGetCGI("cred0_value"); + + if (strcmp(type, "not_chosen")==0){ + GRSThttpError ("500 Invalid input - credential type not chosen"); + return; + } + + // Create the credential + cred=GRSTgaclCredNew(type); + if (strcmp(type, "person")==0) GRSTgaclCredAddValue(cred,"dn", value); + else if (strcmp(type, "dn-list")==0) GRSTgaclCredAddValue(cred, "url", value); + else if (strcmp(type, "voms")==0) GRSTgaclCredAddValue(cred, "fqan", value); + else if (strcmp(type, "dns")==0) GRSTgaclCredAddValue(cred, "hostname", value); + else if (strcmp(type, "any-user")==0) {} // namevalue not entered for any-user credential + else{ + GRSThttpError ("500 Invalid input - credential type not valid"); + return; + } + + // Create and empty entry, add the credential and get permissions + entry = GRSTgaclEntryNew(); + GRSTgaclEntryAddCred(entry, cred); + GACLeditGetPerms(entry); + + // Load the ACL, add the entry and save + acl = GRSTgaclAclLoadFile(GRSTgaclFileFindAclname(dir_path)); + GRSTgaclAclAddEntry(acl, entry); + check_acl_save(dn, perm, help_uri, dir_path, file, dir_uri, admin_file, user, acl, &bp); + return; +} + +void del_entry(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file){ + // Deletes the entry denoted by the GCI variable "entry_no"*/ + int entry_no; + GRSTgaclAcl *acl; + GRSTgaclEntry *previous, *entry; + GRSThttpBody bp; + + if (!GRSTgaclPermHasAdmin(perm)) GRSThttpError ("403 Forbidden"); + + // Load the ACL + acl = GRSTgaclAclLoadFile(GRSTgaclFileFindAclname(dir_path)); + + // Get the number of the entry to be deleted and check okay to delete + entry_no=atol(GRSThttpGetCGI("entry_no")); + if(GACLentriesInAcl(acl)<=1){ + StartHTML(&bp, dir_uri, dir_path); + GRSThttpPrintf (&bp, "ERROR: Cannot delete all entries from the ACL
\n"); + admin_continue(dn, perm, help_uri, dir_path, file, dir_uri, admin_file, &bp); + return; + } + + // Get pointer to entry and previous entry + entry = GACLreturnEntry(acl, entry_no); + if (entry_no!=1) previous = GACLreturnEntry(acl, entry_no-1); + + if(entry==NULL || entry_no<1 || entry_no>GACLentriesInAcl(acl) ){ + GRSThttpError ("500 Unable to read entry from ACL file"); + return; + } + + // Perform deletion from the list by changing pointers + if (entry_no==1) acl->firstentry=entry->next; + else if (entry_no==GACLentriesInAcl(acl)) previous->next=NULL; + else previous->next=entry->next; + + // Save ACL and exit + check_acl_save(dn, perm, help_uri, dir_path, file, dir_uri, admin_file, user, acl, &bp); + + return; +} + + +void edit_entry_form(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file){ + // Presents the user with an editable form containing details of entry denoted by CGI variable entry_no*/ + int entry_no, cred_no, i, admin=0, timestamp=atol(GRSThttpGetCGI("timestamp")); + GRSTgaclAcl *acl; + GRSTgaclEntry *entry; + GRSTgaclCred *cred; + GRSTgaclNamevalue *namevalue; + // struct _GACLnamevalue *namevalue; + GRSThttpBody bp; + + if (!GRSTgaclPermHasAdmin(perm)) GRSThttpError ("403 Forbidden"); + + // Load ACL from file + acl = GRSTgaclAclLoadFile(GRSTgaclFileFindAclname(dir_path)); + + // Get pointer to the entry and check okay + entry_no=atol(GRSThttpGetCGI("entry_no")); + entry = GACLreturnEntry(acl, entry_no); + if(entry==NULL || entry_no<1 || entry_no>GACLentriesInAcl(acl) ){ + GRSThttpError ("500 Unable to read from ACL file"); + return; + } + + StartHTML(&bp, dir_uri, dir_path); + GRSThttpPrintf (&bp, "EDITING ENTRY %d IN ACL FOR %s

\n", entry_no, dir_uri); + + // Start with first credential in the entry and display them in order*/ + cred=entry->firstcred; + cred_no=1; + StartForm(&bp, dir_uri, dir_path, admin_file, timestamp, "edit_entry"); + GRSThttpPrintf (&bp, "\n", entry_no); + + GRSTgaclCredTableStart(&bp); + + while (cred!=NULL){ + // Start with the first namevalue in the credential + namevalue=cred->firstname; + GRSTgaclCredTableAdd(user, entry, cred, namevalue, cred_no, entry_no, admin, timestamp, &bp, dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + // Change to next credential + cred=cred->next; + cred_no++; + } + GRSTgaclCredTableEnd (entry, entry_no, admin, timestamp, &bp, dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + EndForm(&bp); + + admin_continue(dn, perm, help_uri, dir_path, file, dir_uri, admin_file, &bp); + return; +} + + +void edit_entry(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file){ + //Processes the information entered into the form from edit_entry_form() and updates the entry corresponding to entry_no*/ + int entry_no, cred_no, i; + GRSTgaclAcl *acl; + GRSTgaclEntry *entry; + GRSTgaclCred *cred; + GRSTgaclNamevalue *namevalue; + char variable[30]; + GRSThttpBody bp; + + if (!GRSTgaclPermHasAdmin(perm)) GRSThttpError ("403 Forbidden"); + + // Load the ACL + acl = GRSTgaclAclLoadFile(GRSTgaclFileFindAclname(dir_path)); + + // Get pointer to the entry and perform checks + entry_no=atol(GRSThttpGetCGI("entry_no")); + entry = GACLreturnEntry(acl, entry_no); + if(entry==NULL || entry_no<1 || entry_no>GACLentriesInAcl(acl) ){ + GRSThttpError ("500 Unable to read from ACL file"); + return; + } + + // Start with the first credential and update each one + cred=entry->firstcred; + cred_no=1; + + while (cred!=NULL){ + if (strcmp(cred->type, "any-user")!=0){ + namevalue=cred->firstname; + sprintf(variable, "cred%d_value", cred_no); + namevalue->value=GRSThttpGetCGI(variable); + } + //Change to next credential*/ + cred=cred->next; + cred_no++; + } + + // Update permissions + GACLeditGetPerms(entry); + check_acl_save(dn, perm, help_uri, dir_path, file, dir_uri, admin_file, user, acl, &bp); + return; +} + + +void add_cred_form(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file){ + // Presents the user with a form asking for details required to create a new credential in the entry denoted by entry_no + GRSThttpBody bp; + int timestamp=atol(GRSThttpGetCGI("timestamp")), entry_no=atol(GRSThttpGetCGI("entry_no")); + GRSTgaclAcl *acl; + GRSTgaclEntry* entry; + GRSTgaclCred* cred; + GRSTgaclNamevalue* namevalue; + + if (!GRSTgaclPermHasAdmin(perm)) GRSThttpError ("403 Forbidden"); + + acl = GRSTgaclAclLoadFile(GRSTgaclFileFindAclname(dir_path)); // Load the ACL + + //Get pointer to the entry and perform checks + entry = GACLreturnEntry(acl, entry_no); + if(entry==NULL || entry_no<1 || entry_no>GACLentriesInAcl(acl) ){ + GRSThttpError ("500 Unable to read from ACL file"); + return; + } + + + if (strcmp(GRSThttpGetCGI("cmd"), "add_cred_form")==0){ //if not a new entry check to see if cred exists + cred=entry->firstcred; + while (cred!=NULL) { + if (strcmp (cred->type, "any-user")==0) { + StartHTML(&bp, dir_uri, dir_path); + GRSThttpPrintf (&bp, "ERROR: AND-ing \"any-user\" credential with other credential does not make sense
\n"); + admin_continue(dn, perm, help_uri, dir_path, file, dir_uri, admin_file, &bp); + return; + } + cred=cred->next; + } + } + + StartHTML(&bp, dir_uri, dir_path); + GRSThttpPrintf (&bp, " NEW CREDENTIAL IN ENTRY %d OF ACL FOR %s

\n", entry_no, dir_uri); + StartForm(&bp, dir_uri, dir_path, admin_file, timestamp, "add_cred"); + + GRSThttpPrintf (&bp, " \n", entry_no); + + GRSTgaclCredTableStart(&bp); + GRSTgaclCredTableAdd(user, entry, cred, namevalue, 0, 0, 0, timestamp, &bp, dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + GRSTgaclCredTableEnd (entry, 0, 0, timestamp, &bp, dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + + EndForm(&bp); + admin_continue(dn, perm, help_uri, dir_path, file, dir_uri, admin_file, &bp); + return; +} + + +void add_cred(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file){ + // Processes the information entered into the form [add_cred_form()]and adds a new credential to the entry corresponding to entry_no + int entry_no; + GRSTgaclAcl *acl; + GRSTgaclEntry *entry; + GRSTgaclCred *cred; + GRSThttpBody bp; + char *type, *value; + + if (!GRSTgaclPermHasAdmin(perm)) GRSThttpError ("403 Forbidden"); + + acl = GRSTgaclAclLoadFile(GRSTgaclFileFindAclname(dir_path));// Load the ACL + + // Get pointer to the entry and perform checks + entry_no=atol(GRSThttpGetCGI("entry_no")); + entry = GACLreturnEntry(acl, entry_no); + if(entry==NULL || entry_no<1 || entry_no>GACLentriesInAcl(acl)){ + GRSThttpError ("500 Unable to read from ACL file"); + return; + } + + // Create new credential and add it to entry + type=GRSThttpGetCGI("type"); + value=GRSThttpGetCGI("cred0_value"); + cred=GRSTgaclCredNew(type); + if (strcmp(type, "person") ==0) GRSTgaclCredAddValue(cred,"dn", value); + else if (strcmp(type, "dn-list") ==0) GRSTgaclCredAddValue(cred, "url", value); + else if (strcmp(type, "voms") ==0) GRSTgaclCredAddValue(cred, "fqan", value); + else if (strcmp(type, "dns") ==0) GRSTgaclCredAddValue(cred, "hostname", value); + else if (strcmp(type, "any-user")==0) {}// namevalue not entered for any-user credential + else{ + GRSThttpError ("500 Credential type not valid"); + return; + } + GRSTgaclEntryAddCred(entry, cred); + + check_acl_save(dn, perm, help_uri, dir_path, file, dir_uri, admin_file, user, acl, &bp); + return; +} + + +void del_cred(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file){ + // Deletes the credential denoted by the GCI variable "cred_no", in the entry denoted by "entry_no" + int entry_no, cred_no; + GRSTgaclAcl *acl; + GRSTgaclEntry *entry; + GRSTgaclCred *previous, *cred; + GRSThttpBody bp; + + if (!GRSTgaclPermHasAdmin(perm)) GRSThttpError ("403 Forbidden"); + + acl = GRSTgaclAclLoadFile(GRSTgaclFileFindAclname(dir_path)); + + // Get pointer to the entry and perform checks + entry_no=atol(GRSThttpGetCGI("entry_no")); + entry = GACLreturnEntry(acl, entry_no); + if(entry==NULL || entry_no<1 || entry_no>GACLentriesInAcl(acl) ){ + GRSThttpError ("500 Unable to read from ACL file"); + return; + } + // Get pointer the the credential and perform checks + cred_no=atol(GRSThttpGetCGI("cred_no")); + cred=GACLreturnCred(entry, cred_no); + if(entry==NULL || entry_no<1 || cred_no>GRSTgaclCredsInEntry(entry)){ + GRSThttpError ("500 Unable to read from ACL file"); + return; + } + // Get pointer to previous credential - if needed + if (cred_no!=1) previous = GACLreturnCred(entry, cred_no-1); + + // Perform deletion from the list by changing pointers + if (cred_no==1) entry->firstcred=cred->next; + else if (cred_no==GRSTgaclCredsInEntry(entry)) previous->next=NULL; + else previous->next=cred->next; + + check_acl_save(dn, perm, help_uri, dir_path, file, dir_uri, admin_file, user, acl, &bp); + return; +} + +void admin_continue(char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file, GRSThttpBody *bp){ + // Single line printed out to forward users back to show_acl in admin mode + // Should ALWAYS called from another function so no HTML header required + // Should ALWAYS be the end of a page + GRSThttpPrintf (bp, "\n
Click Here to return to the editor", dir_uri,admin_file,dir_uri, time(NULL)); + adminfooter(bp, dn, help_uri, dir_uri, NULL); + GRSThttpPrintHeaderFooter(bp, dir_path, GRST_FOOTFILE); + GRSThttpWriteOut(bp); + return; +} + + +void del_entry_sure(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file){ + // Prints out entry denoted by entry_no and asks if the user really wants to delete it + GRSTgaclAcl *acl; + GRSTgaclEntry *entry; + GRSTgaclCred *cred; + GRSTgaclNamevalue *namevalue; + int entry_no, cred_no, allow, deny, i, timestamp; + GRSThttpBody bp; + + if (!GRSTgaclPermHasAdmin(perm)) GRSThttpError ("403 Forbidden"); + + acl = GRSTgaclAclLoadFile(GRSTgaclFileFindAclname(dir_path));// Load ACL from file + + if (acl==NULL){ + GRSThttpError ("500 Unable to read from ACL file"); + return; + } + + // Get pointer to the entry and check okay + entry_no=atol(GRSThttpGetCGI("entry_no")); + entry = GACLreturnEntry(acl, entry_no); + if(entry==NULL || entry_no<1 || entry_no>GACLentriesInAcl(acl) ){ + GRSThttpError ("500 Unable to read from ACL file"); + return; + } + + StartHTML(&bp, dir_uri, dir_path); + GRSThttpPrintf (&bp, "

Do you really want to delete the following entry?



\n"); + GRSThttpPrintf (&bp,"
Entry %d:
\n", entry_no); + + // Print the entry out + // Start with the first credential in the entry and work through + cred=entry->firstcred; + cred_no=1; + + GRSTgaclCredTableStart(&bp); + while (cred!=NULL){ + // Start with the first namevalue in the credential + namevalue=cred->firstname; + GRSTgaclCredTableAdd(user, entry, cred, namevalue, cred_no, entry_no, 0, 0, &bp, dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + // Change to next credential + cred=cred->next; + cred_no++; + } + + GRSTgaclCredTableEnd (entry, entry_no, 0, 0, &bp, dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + + StartForm(&bp, dir_uri, dir_path, admin_file, atol(GRSThttpGetCGI("timestamp")), "del_entry"); + GRSThttpPrintf (&bp, "\n", entry_no); + GRSThttpPrintf (&bp, "

\n\n"); + + admin_continue(dn, perm, help_uri, dir_path, file, dir_uri, admin_file, &bp); + return; +} + +void del_cred_sure(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file){ + // Prints out credential denoted by entry_no/cred_no and asks if the user really wants to delete it + GRSTgaclAcl *acl; + GRSTgaclEntry *entry; + GRSTgaclCred *cred; + GRSTgaclNamevalue *namevalue; + int entry_no, cred_no, allow, deny, timestamp, i; + GRSThttpBody bp; + + if (!GRSTgaclPermHasAdmin(perm)) GRSThttpError ("403 Forbidden"); + + acl = GRSTgaclAclLoadFile(GRSTgaclFileFindAclname(dir_path));// Load ACL from file + + if (acl==NULL){ + GRSThttpError ("500 Unable to read from ACL file"); + return; + } + + // Get pointer to the entry and check okay + entry_no=atol(GRSThttpGetCGI("entry_no")); + entry = GACLreturnEntry(acl, entry_no); + if(entry==NULL || entry_no<1 || entry_no>GACLentriesInAcl(acl) ){ + GRSThttpError ("500 Unable to read from ACL file"); + return; + } + + // Get pointer to the credential and check okay + cred_no=atol(GRSThttpGetCGI("cred_no")); + cred=GACLreturnCred(entry, cred_no); + if(entry==NULL || entry_no<1 || cred_no>GRSTgaclCredsInEntry(entry)){ + GRSThttpError ("500 Unable to read from ACL file"); + return; + } + + if(GRSTgaclCredsInEntry(entry)<=1){ + del_entry_sure(user, dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + return; + } + + StartHTML(&bp, dir_uri, dir_path); + GRSThttpPrintf (&bp, "

Do you really want to delete the following credential from entry %d?



", entry_no); + + // Print the credential out + GRSTgaclCredTableStart(&bp); + GRSTgaclCredTableAdd(user, entry, cred, cred->firstname, cred_no, entry_no, 0, 0, &bp, dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + GRSTgaclCredTableEnd (entry, entry_no, 0, 0, &bp, dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + GRSThttpPrintf (&bp,"
\n"); + + // Yes Button + StartForm(&bp, dir_uri, dir_path, admin_file, atol(GRSThttpGetCGI("timestamp")), "del_cred"); + GRSThttpPrintf (&bp, "\n", entry_no); + GRSThttpPrintf (&bp, "\n", cred_no); + GRSThttpPrintf (&bp, "

\n\n"); + + admin_continue(dn, perm, help_uri, dir_path, file, dir_uri, admin_file, &bp); + return; +} + + +int GACLentriesInAcl(GRSTgaclAcl *acl){ + // Returns the number of entries in acl + GRSTgaclEntry *entry; + int number; + + entry=acl->firstentry; + number=0; + + while (entry!=NULL) + { + number++; + entry=entry->next; + } + + return number; +} + +int GRSTgaclCredsInEntry(GRSTgaclEntry *entry){ + // Returns the number of credentials in entry + int number; + GRSTgaclCred *cred; + + cred=entry->firstcred; + number=0; + + while (cred!=NULL) + { + number++; + cred=cred->next; + } + + return number; +} + + +void GACLeditGetPerms(GRSTgaclEntry *entry){ + // Updates the permissions entry using permissions from a form produced using GRSTgaclCredTableEnd + int i; + char buf[30]; + + + for (i=0; grst_perm_syms[i]!=NULL; i++) /* Print the list of allowed permissions*/ + { + sprintf (buf, "allow_%s", grst_perm_syms[i]); // Update allowed + if (strcmp (GRSThttpGetCGI(buf), "ON") == 0 ) GRSTgaclEntryAllowPerm(entry, grst_perm_vals[i]); else GRSTgaclEntryUnallowPerm(entry, grst_perm_vals[i]); + + sprintf (buf, "deny_%s", grst_perm_syms[i]); // Update denied + if (strcmp (GRSThttpGetCGI(buf), "ON") == 0 ) GRSTgaclEntryDenyPerm(entry, grst_perm_vals[i]); else GRSTgaclEntryUndenyPerm(entry, grst_perm_vals[i]); + + } + + return; +} + +GRSTgaclEntry *GACLreturnEntry(GRSTgaclAcl *acl, int entry_no){ + // Returns a pointer to entry in ACL denoted by entry_no, returns NULL if not found + int number; + GRSTgaclEntry *entry; + + if (acl==NULL) return NULL; + + entry=acl->firstentry; + number=1; + + while (entry!=NULL) + { + if (number==entry_no) return entry; + number++; + entry=entry->next; + } + + return NULL; +} + + +GRSTgaclCred *GACLreturnCred(GRSTgaclEntry *entry, int cred_no){ + // Returns a pointer to credential denoted by cred_no in entry, returns NULL if not found + int number; + GRSTgaclCred *cred; + + if (entry==NULL) return NULL; + + cred=entry->firstcred; + number=1; + + while (cred!=NULL) + { + if (number==cred_no) return cred; + number++; + cred=cred->next; + } + + return NULL; +} +void StartHTML(GRSThttpBody *bp, char *dir_uri, char* dir_path){ + //Start HTML output and insert page title + printf("Status: 200 OK\nContent-Type: text/html\n"); + GRSThttpBodyInit(bp); + GRSThttpPrintf(bp, "Access Control List for %s\n", dir_uri); + GRSThttpPrintHeaderFooter(bp, dir_path, GRST_HEADFILE); + return; +} +void StartForm(GRSThttpBody *bp, char* dir_uri, char* dir_path, char* admin_file, int timestamp, char* target_function){ + // Starts an HTML form with gridsite admin as the target and target_function as the value of cmd. + // Also inputs the dir_uri and the timestamp + GRSThttpPrintf (bp, "
\n", dir_uri, admin_file, dir_uri); + GRSThttpPrintf (bp, " \n", target_function); + GRSThttpPrintf (bp, " \n", timestamp); + return; +} + +void EndForm(GRSThttpBody *bp){ + GRSThttpPrintf (bp, "

\n"); + GRSThttpPrintf (bp, "
\n"); + return; +} + +void GRSTgaclCredTableStart(GRSThttpBody *bp){ + //Starts an HTML table of credentials by setting the column widths and inputting the headings + GRSThttpPrintf (bp,""); + GRSThttpPrintf (bp,""); + return; +} + +void GRSTgaclCredTableAdd(GRSTgaclUser *user, GRSTgaclEntry *entry, GRSTgaclCred *cred, GRSTgaclNamevalue *namevalue, int cred_no, int entry_no, int admin, int timestamp, GRSThttpBody *bp, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file){ + // Adds the credential "cred" to a table started byGRSTgaclCredTableStart allowing the user to edit if appropriate + char* cmd = GRSThttpGetCGI("cmd"); + int edit_values=0, new_cred=0, allow_new_person=1; + int site_admin=GRSTgaclDNlistHasUser(getenv("REDIRECT_GRST_ADMIN_LIST"), user); + + if (strcmp(cmd, "new_entry_form")==0 || strcmp(cmd, "add_cred_form")==0) new_cred=1; + if (new_cred || strcmp(cmd, "edit_entry_form")==0) edit_values=1; + + if (new_cred) { /*Print out type and descriptor*/ + if (strcmp(cmd, "add_cred_form")==0){ /*if not a new entry check to see if cred exists.*/ + cred=entry->firstcred; + while (cred!=NULL) {if (strcmp (cred->type, "person")==0) allow_new_person=0; cred=cred->next;} + } + //create dummy credential for the user to edit + cred=GRSTgaclCredNew("new"); + GRSTgaclCredAddValue(cred, "", ""); + namevalue=cred->firstname; + //Drop down list of types + GRSThttpPrintf(bp,""); + GRSThttpPrintf(bp,""); + } + + else { //Print out type and descriptor for existing cred + + GRSThttpPrintf(bp,""); +} + +void GRSTgaclCredTableEnd(GRSTgaclEntry* entry, int entry_no, int admin, int timestamp, GRSThttpBody *bp, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file){ + // Finishes off a table of credentials by inputting "Add Credential" link and a list of premissions in the final row + int i, blank_perms, edit_perms, show_perms; + char* cmd = GRSThttpGetCGI("cmd"); + + if (strcmp(cmd, "add_cred_form")==0 ||strcmp(cmd, "del_cred_sure")==0) show_perms=0; else show_perms=1; + if (strcmp(cmd, "edit_entry_form")==0 || strcmp(cmd, "new_entry_form")==0) edit_perms=1; else edit_perms=0; + if (strcmp(cmd, "new_entry_form")==0) blank_perms=1; else blank_perms=0; + + // If showing the last row is not required then exit + if (show_perms==0){GRSThttpPrintf (bp,"
Credential No.TypeValue
New"); + GRSThttpPrintf (bp, "
%d", cred_no); + if (admin) GRSThttpPrintf (bp,"(Delete)", dir_uri,admin_file,dir_uri, entry_no, cred_no, timestamp); + GRSThttpPrintf(bp, "%s ", cred->type); + } + + if (strcmp(cred->type, "any-user")==0) GRSThttpPrintf (bp, " "); /* Do not print out namevalue for any-user credential*/ + else{ + if (edit_values){ // Place namevalue in an editable box if appropriate + GRSThttpPrintf (bp, "value, bp); + GRSThttpPrintf (bp, "\">"); + } + else if (strcmp(cred->type, "dn-list")==0){ + GRSThttpPrintf(bp, "value, bp); + GRSThttpPrintf(bp, " \">"); + StringHTMLEncode(namevalue->value, bp); + GRSThttpPrintf(bp, ""); + } + else { GRSThttpPrintf(bp, " "); StringHTMLEncode(namevalue->value, bp);} + + } + //Print out warning symbol if cred being printed relates to current user - but NOT for users in site admin list + if (GRSTgaclUserHasCred(user, cred) && !site_admin) GRSThttpPrintf(bp, " <--"); + GRSThttpPrintf(bp, "

\n"); return;} + + GRSThttpPrintf (bp,""); + + if (admin) GRSThttpPrintf (bp,"Add Credential", dir_uri,admin_file,dir_uri, entry_no, timestamp); + + GRSThttpPrintf (bp, "\n "); + + if (blank_perms) entry->allowed=entry->denied=GRST_PERM_NONE; + + // Show Permissions - will produce a list or a list of check boxes depending on whether the permissions are to be edited or not + GRSThttpPrintf (bp, "Allowed: "); + for (i=0; grst_perm_syms[i]!=NULL; i++) /* Print the list of allowed permissions*/ + { + if ( entry->allowed & grst_perm_vals[i]){ + if (edit_perms) GRSThttpPrintf (bp, "%s   \n", grst_perm_syms[i],grst_perm_syms[i]); + else GRSThttpPrintf(bp,"%s ", grst_perm_syms[i]); if (strcmp(grst_perm_syms[i], "none")==0) break; + } + else if (strcmp(grst_perm_syms[i], "none")!=0 && edit_perms) GRSThttpPrintf (bp, "%s   \n", grst_perm_syms[i],grst_perm_syms[i]); + } + + if (edit_perms) GRSThttpPrintf (bp, "

"); + GRSThttpPrintf (bp, "Denied: "); + for (i=0; grst_perm_syms[i]!=NULL; i++) /* Print the list of denied permissions*/ + { + if ( entry->denied & grst_perm_vals[i]) + { + if (edit_perms) GRSThttpPrintf (bp, "%s   \n", grst_perm_syms[i],grst_perm_syms[i]); + else GRSThttpPrintf(bp,"%s ", grst_perm_syms[i]); + if (strcmp(grst_perm_syms[i], "none")==0) break; + } + else if (strcmp(grst_perm_syms[i], "none")!=0 && edit_perms) GRSThttpPrintf (bp, "%s   \n", grst_perm_syms[i],grst_perm_syms[i]); + } + + GRSThttpPrintf (bp, ""); + GRSThttpPrintf (bp,"
\n"); + GRSThttpPrintf (bp,"\n"); +} + +void check_acl_save(char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file, GRSTgaclUser* user, GRSTgaclAcl *acl, GRSThttpBody *bp){ + // Checks if the acl for the current directory has been changed, check the current user's permissions. + // If all is okay the ACl is saved -> returns 1 else returns 0 + struct stat file_info; + GRSTgaclPerm new_perm; + char *vfile, *dir_path_vfile, *dir_path_file; + FILE *fp; + + + /*Check ACL has not been modified*/ + stat(GRSTgaclFileFindAclname(dir_path), &file_info); + if (atol(GRSThttpGetCGI("timestamp"))!=file_info.st_mtime){ + StartHTML(bp, dir_uri, dir_path); + GRSThttpPrintf (bp, "ERROR: CANNOT SAVE CHANGES

The ACL has been modified since it was last viewed\n

"); + admin_continue(dn, perm, help_uri, dir_path, file, dir_uri, admin_file, bp); + return; + } + + // check users permissions in the new ACL + + if (!GRSTgaclDNlistHasUser(getenv("REDIRECT_GRST_ADMIN_LIST"), user)) + { + new_perm = GRSTgaclAclTestUser(acl, user); + if (new_perm != perm){ + StartHTML(bp, dir_uri, dir_path); + if (!GRSTgaclPermHasAdmin(new_perm)){//Check that user still has Admin permissions - if not then exit without saving the new ACL + GRSThttpPrintf (bp, "ERROR: CANNOT SAVE CHANGES\n\n

You cannot deny yourself admin access from within the editor\n"); + admin_continue(dn, perm, help_uri, dir_path, file, dir_uri, admin_file, bp); + return; + } + //Functions to inform of other permission changes come next + GRSThttpPrintf (bp, "WARNING: OPERATION CHANGED YOUR PERMISSIONS!\n\n

You still have Admin permissions

\n"); + admin_continue(dn, perm, help_uri, dir_path, file, dir_uri, admin_file, bp); + return; + } + } + // ACL not modified, notified of permission changes - can now save + + dir_path_file=GRSTgaclFileFindAclname(dir_path); + vfile=makevfilename(".gacl", file_info.st_size, dn); // Make temporary file name + dir_path_vfile = malloc(strlen(dir_path) + strlen(vfile) + 2); + strcpy(dir_path_vfile, dir_path); + strcat(dir_path_vfile, "/"); + strcat(dir_path_vfile, vfile); + + GRSTgaclAclSave(acl, dir_path_vfile); // save the new ACL to the temporary file + unlink(dir_path_file); + if (link (dir_path_vfile,dir_path_file)!=0) GRSThttpError("403 Forbidden"); + + printf ("Status: 302 Moved Temporarily\n Content Length: 0\nLocation: %s%s?cmd=admin_acl\n\n", dir_uri, admin_file); + return; +} + +void StringHTMLEncode (char* string, GRSThttpBody *bp){ + + char* current_char; + char* tmp; + int n; + tmp=malloc(2); + + *(tmp+1)='\0'; + current_char=string; + while(*current_char != '\0'){ + + if (*current_char == '<') GRSThttpPrintf (bp,"<"); + else if (*current_char == '>') GRSThttpPrintf (bp,">"); + else if (*current_char == '&') GRSThttpPrintf (bp,"&"); + else if (*current_char == '\'') GRSThttpPrintf (bp,"'"); + else if (*current_char == '"') GRSThttpPrintf (bp,"""); + else{ + *tmp=*current_char; + GRSThttpPrintf(bp, "%s", tmp); + + } + current_char++; + } + return; +} + +void revert_acl(GRSTgaclUser *user, char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, char *file, char *dir_uri, char *admin_file){ + char *AclFilename; + GRSTgaclAcl *acl; + GRSThttpBody bp; + // Load the old ACL, add the entry and save + AclFilename=malloc(strlen(dir_path)+strlen(file)+2); + strcpy(AclFilename, dir_path); + strcat(AclFilename, "/"); + strcat(AclFilename, file); + + acl = GRSTgaclAclLoadFile(AclFilename); + check_acl_save(dn, perm, help_uri, dir_path, file, dir_uri, admin_file, user, acl, &bp); + return; +} diff --git a/org.gridsite.core/src/grst_admin_main.c b/org.gridsite.core/src/grst_admin_main.c new file mode 100644 index 0000000..9390dd1 --- /dev/null +++ b/org.gridsite.core/src/grst_admin_main.c @@ -0,0 +1,365 @@ +/* + Andrew McNab and Shiv Kaushal, University of Manchester. + Copyright (c) 2002-3. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + o Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + o Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +/*---------------------------------------------------------------------------* + * This program is part of GridSite: http://www.gridpp.ac.uk/gridsite/ * + *---------------------------------------------------------------------------*/ + +#ifndef VERSION +#define VERSION "x.x.x" +#endif + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// when porting: remember that sendfile() is very OS-specific! +#include + +#include + +#include "grst_admin.h" + +/* + + GridSite human/interactive management interface. This should produce + a CGI executable, usually ./sbin/real-gridsite-admin.cgi, which is + called from HTML forms either by GET or POST methods or both (ie input + present in both QUERY_STRING and the stdin of the CGI process.) + + The CGI name/value pairs used are: + + cmd = edit, managedir, print, history + file = short name of file, without path + + If real-gridsite-admin.cgi is run by an internal redirection inside + mod_gridsite (as should ALWAYS be the case) then the environment + variable REDIRECT_GRST_DIR_PATH will be set to the full path of + the directory holding the file in question. This respects any complex + URI -> file path mapping done by Apache. + +*/ + +void GRSThttpError(char *status) +{ + printf("Status: %s\n", status); + printf("Server-CGI: GridSite Admin %s\n", VERSION); + printf("Content-Length: %d\n", 2 * strlen(status) + 58); + puts("Content-Type: text/html\n"); + + printf("%s\n", status); + printf("

%s

\n", status); + + exit(0); +} + +void adminfooter(GRSThttpBody *bp, char *dn, char *help_uri, char *dir_uri, + char *admin_file) +{ + GRSThttpPrintf(bp, "

\n"); + + if (dn != NULL) GRSThttpPrintf(bp, "


You are %s
\n", dn); + else GRSThttpPrintf(bp, "
\n"); + + if (admin_file != NULL) + GRSThttpPrintf(bp, "" + "Manage directory .\n", + dir_uri, admin_file); + else GRSThttpPrintf(bp, "" + "Back to directory .\n", dir_uri); + + if (help_uri != NULL) + GRSThttpPrintf(bp, "Website Help .\n", help_uri); + + if ((getenv("GRST_NO_LINK") == NULL) && + (getenv("REDIRECT_GRST_NO_LINK") == NULL)) + GRSThttpPrintf(bp, "Built with " + "GridSite %s\n", + VERSION); + + GRSThttpPrintf(bp, "
\n"); +} + +int GRSTstrCmpShort(char *long_s, char *short_s) +{ + while (*short_s != '\0') + { + if (*long_s > *short_s) return +1; + if (*long_s < *short_s) return -1; + + ++long_s; + ++short_s; + } + + return 0; +} + +char *makevfilename(char *publicname, size_t size, char *dn) +{ + int i; + char *ext, *vfilename, *encpublicname, *encdn, *p; + struct timeval tv_now; + + gettimeofday(&tv_now, NULL); + + ext = rindex(publicname, '.'); + if (ext == NULL) ext = ""; + + encpublicname = GRSThttpUrlEncode(publicname); + for (p=encpublicname; *p != '\0'; ++p) if (*p == '%') *p = '='; + + encdn = GRSThttpUrlEncode(dn); + for (p=encdn; *p != '\0'; ++p) if (*p == '%') *p = '='; + + /* we used zero-padding for times so + alphanumeric sorting will sort chronologically too */ + + asprintf(&vfilename, "%s:%s:%08X:%05X:%X:%s:%s", GRST_HIST_PREFIX, + encpublicname, tv_now.tv_sec, tv_now.tv_usec, size, encdn, ext); + + return vfilename; +} + +void justheader(char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, + char *dir_uri, char *admin_file) +{ + GRSThttpBody bp; + + puts("Status: 200 OK\nContent-Type: text/html"); + + GRSThttpBodyInit(&bp); + + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE); + + GRSThttpWriteOut(&bp); +} + +void justfooter(char *dn, GRSTgaclPerm perm, char *help_uri, char *dir_path, + char *dir_uri, char *admin_file) +{ + GRSThttpBody bp; + + puts("Status: 200 OK\nContent-Type: text/html"); + + GRSThttpBodyInit(&bp); + + if (GRSTgaclPermHasList(perm) || GRSTgaclPermHasWrite(perm) + || GRSTgaclPermHasAdmin(perm)) + adminfooter(&bp, dn, help_uri, dir_uri, admin_file); + + GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_FOOTFILE); + + GRSThttpWriteOut(&bp); +} + +int main() +{ + int gsiproxylimit_i = 1; + char *cmd, *dir_uri, *file, *dir_path, *admin_file, *dn = NULL, + *help_uri, *p, *content_type, *request_uri, *button, + *grst_cred_0, *gsiproxylimit, *dn_lists; + GRSTgaclCred *cred; + GRSTgaclUser *user = NULL; + GRSTgaclAcl *acl; + GRSTgaclPerm perm = GRST_PERM_NONE; + + help_uri = getenv("REDIRECT_GRST_HELP_URI"); /* can be NULL */ + admin_file = getenv("REDIRECT_GRST_ADMIN_FILE"); + dir_path = getenv("REDIRECT_GRST_DIR_PATH"); + request_uri = getenv("REQUEST_URI"); + + if ((dir_path == NULL) || (admin_file == NULL) || (request_uri == NULL)) + { + puts("Status: 500 Internal Server Error\nContent-type: text/plain\n\n" + "REDIRECT_GRST_DIR_PATH or REDIRECT_GRST_ADMIN_FILE " + "or REQUEST_URI missing"); + return; + } + + GRSTgaclInit(); + + grst_cred_0 = getenv("GRST_CRED_0"); + + if ((grst_cred_0 != NULL) && (cred = GRSTx509CompactToCred(grst_cred_0))) + { + gsiproxylimit = getenv("REDIRECT_GRST_GSIPROXY_LIMIT"); + if (gsiproxylimit != NULL) sscanf(gsiproxylimit, "%d", &gsiproxylimit_i); + + if (GRSTgaclCredGetDelegation(cred) <= gsiproxylimit_i) + { + user = GRSTgaclUserNew(cred); + + if ((p = index(grst_cred_0, ' ')) && + (p = index(++p, ' ')) && + (p = index(++p, ' ')) && + (p = index(++p, ' '))) dn = &p[1]; + } + } + else if ((dn = getenv("SSL_CLIENT_S_DN")) != NULL) + { + cred = GRSTgaclCredNew("person"); + GRSTgaclCredAddValue(cred, "dn", dn); + user = GRSTgaclUserNew(cred); + } + + dn_lists = getenv("REDIRECT_GRST_DN_LISTS"); + if (dn_lists == NULL) dn_lists = getenv("GRST_DN_LISTS"); + if (dn_lists != NULL) GRSTgaclUserSetDNlists(user, dn_lists); + + if (GRSTgaclDNlistHasUser(getenv("REDIRECT_GRST_ADMIN_LIST"), + user)) perm = GRST_PERM_ALL; + else + { + p = getenv("REMOTE_HOST"); + if (p != NULL) + { + cred = GRSTgaclCredNew("dns"); + GRSTgaclCredAddValue(cred, "hostname", p); + + if (user == NULL) user = GRSTgaclUserNew(cred); + else GRSTgaclUserAddCred(user, cred); + } + + acl = GRSTgaclAclLoadforFile(dir_path); + if (acl != NULL) perm = GRSTgaclAclTestUser(acl, user); + } + + /* we're relying on being a CGI with all this un-free()ed strdup()ing */ + + dir_uri = strdup(request_uri); + p = rindex(dir_uri, '?'); + if (p != NULL) *p = '\0'; + p = rindex(dir_uri, '/'); + if (p != NULL) p[1] = '\0'; + + content_type = getenv("CONTENT_TYPE"); + + if ((content_type != NULL) && + (GRSTstrCmpShort(content_type, "multipart/form-data; boundary=") == 0)) + { + uploadfile(dn, perm, help_uri, dir_path, dir_uri, admin_file); + return 0; + } + + cmd = GRSThttpGetCGI("cmd"); + file = GRSThttpGetCGI("file"); + button = GRSThttpGetCGI("button"); + + /* file and directory functions in grst_admin_file.c */ + + if (strcmp(cmd, "header") == 0) + justheader(dn, perm, help_uri, dir_path, dir_uri, admin_file); + else if (strcmp(cmd, "footer") == 0) + justfooter(dn, perm, help_uri, dir_path, dir_uri, admin_file); + else if (strcmp(cmd, "managedir") == 0) + managedir(dn, perm, help_uri, dir_path, dir_uri, admin_file); + else if (strcmp(cmd, "print") == 0) + printfile(dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + else if (strcmp(cmd, "history") == 0) + filehistory(dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + else if (strcmp(cmd, "editdnlist") == 0) + editdnlistform(dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + else if (strcmp(cmd, "edit") == 0) + { + if ((strcasecmp(button, "new directory") == 0) || + (strcasecmp(button, "Create") == 0)) + newdirectory(dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + else + editfileform(dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + } + else if (strcmp(cmd, "editaction") == 0) + editfileaction(dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + else if (strcmp(cmd, "editdnlistaction") == 0) + editdnlistaction(dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + else if (strcmp(cmd, "delete") == 0) + deletefileform(dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + else if (strcmp(cmd, "deleteaction") == 0) + deletefileaction(dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + else if (strcmp(cmd, "rename") == 0) + renameform(dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + else if (strcmp(cmd, "renameaction") == 0) + renameaction(dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + else if (strcmp(cmd, "ziplist") == 0) + ziplist(dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + else if (strcmp(cmd, "unzipfile") == 0) + unzipfile(dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + else if (strcmp(cmd, "create_acl") == 0) + create_acl(dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + + /* GACL functions in grst_admin_gacl.c */ + + else if (strcmp(cmd, "show_acl") == 0) + show_acl(0, user, dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + else if (strcmp(cmd, "admin_acl") == 0) + show_acl(1, user, dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + else if (strcmp(cmd, "acl_history") == 0) + show_acl(2, user, dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + else if (strcmp(cmd, "revert_acl") == 0) + revert_acl(user, dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + //show_acl(2, user, dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + else if (strcmp(cmd,"new_entry_form")==0) + new_entry_form(user, dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + else if (strcmp(cmd,"new_entry")==0) + new_entry(user, dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + else if (strcmp(cmd,"del_entry_sure")==0) + del_entry_sure(user, dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + else if (strcmp(cmd,"del_entry")==0) + del_entry(user, dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + else if (strcmp(cmd,"edit_entry_form")==0) + edit_entry_form(user, dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + else if (strcmp(cmd,"edit_entry")==0) + edit_entry(user, dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + else if (strcmp(cmd,"add_cred_form")==0) + add_cred_form(user, dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + else if (strcmp(cmd,"add_cred")==0) + add_cred(user, dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + else if (strcmp(cmd,"del_cred_sure")==0) + del_cred_sure(user, dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + else if (strcmp(cmd,"del_cred")==0) + del_cred(user, dn, perm, help_uri, dir_path, file, dir_uri, admin_file); + + /* you what? */ + + else GRSThttpError("500 Internal Server Error"); +} diff --git a/org.gridsite.core/src/grst_gacl.c b/org.gridsite.core/src/grst_gacl.c new file mode 100644 index 0000000..3c99002 --- /dev/null +++ b/org.gridsite.core/src/grst_gacl.c @@ -0,0 +1,1136 @@ +/* + Copyright (c) 2002-3, Andrew McNab, University of Manchester + All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + o Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + o Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ +/*------------------------------------------------------------------------* + * For more information about GridSite: http://www.gridpp.ac.uk/gridsite/ * + *------------------------------------------------------------------------*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include + +#include +#include +#include + +#include "gridsite.h" + +/* * + * Global variables, shared by all GACL functions by private to libgacl * + * */ + +char *grst_perm_syms[] = { "none", + "read", + "exec", + "list", + "write", + "admin", + NULL }; + +GRSTgaclPerm grst_perm_vals[] = { GRST_PERM_NONE, + GRST_PERM_READ, + GRST_PERM_EXEC, + GRST_PERM_LIST, + GRST_PERM_WRITE, + GRST_PERM_ADMIN, + -1 }; + +int GRSTgaclInit(void) +{ + xmlInitParser(); + + LIBXML_TEST_VERSION + + xmlKeepBlanksDefault(0); + + return 1; +} + +/* * + * Functions to manipulate GRSTgaclCred structures * + * */ + +GRSTgaclCred *GRSTgaclCredNew(char *type) +/* + GRSTgaclCredNew - allocate a new GRSTgaclCred structure, and return + it's pointer or NULL on (malloc) error. +*/ +{ + GRSTgaclCred *newcred; + + if (type == NULL) return NULL; + + newcred = malloc(sizeof(GRSTgaclCred)); + if (newcred == NULL) return NULL; + + newcred->type = strdup(type); + newcred->delegation = 0; + newcred->firstname = NULL; + newcred->next = NULL; + + return newcred; +} + +int GRSTgaclCredAddValue(GRSTgaclCred *cred, char *rawname, char *rawvalue) +/* + GRSTgaclCredAddValue - add a name/value pair to a GRSTgaclCred +*/ +{ + int i; + char *name, *value; + GRSTgaclNamevalue *p; + + name = strdup(rawname); + + /* no leading or trailing space in value */ + + value = rawvalue; + while ((*value != '\0') && isspace(*value)) ++value; + + value = strdup(value); + + for (i=strlen(value) - 1; (i >= 0) && isspace(value[i]); --i) value[i]='\0'; + + if (cred->firstname == NULL) + { + cred->firstname = malloc(sizeof (GRSTgaclNamevalue)); + (cred->firstname)->name = name; + (cred->firstname)->value = value; + (cred->firstname)->next = NULL; + } + else + { + p = cred->firstname; + + while (p->next != NULL) p = (GRSTgaclNamevalue *) p->next; + + p->next = malloc(sizeof(GRSTgaclNamevalue)); + ((GRSTgaclNamevalue *) p->next)->name = name; + ((GRSTgaclNamevalue *) p->next)->value = value; + ((GRSTgaclNamevalue *) p->next)->next = NULL; + } + + return 1; +} + +static int GRSTgaclNamevalueFree(GRSTgaclNamevalue *p) +{ + if (p == NULL) return 1; + + if (p->next != NULL) + GRSTgaclNamevalueFree((GRSTgaclNamevalue *) p->next); + if (p->name != NULL) free(p->name); + if (p->value != NULL) free(p->value); + free(p); + + return 1; +} + +int GRSTgaclCredFree(GRSTgaclCred *cred) +/* + GRSTgaclCredFree - free memory structures of a GRSTgaclCred, + returning 1 always! +*/ +{ + if (cred == NULL) return 1; + + GRSTgaclNamevalueFree(cred->firstname); + if (cred->type != NULL) free(cred->type); + free(cred); + + return 1; +} + +static int GRSTgaclCredsFree(GRSTgaclCred *firstcred) +/* + GRSTgaclCredsFree - free a cred and all the creds in its *next chain +*/ +{ + if (firstcred == NULL) return 0; + + if (firstcred->next != NULL) GRSTgaclCredsFree(firstcred->next); + + return GRSTgaclCredsFree(firstcred); +} + +static int GRSTgaclCredInsert(GRSTgaclCred *firstcred, GRSTgaclCred *newcred) +/* + GRSTgaclCredInsert - insert a cred in the *next chain of firstcred + + FOR THE MOMENT THIS JUST APPENDS! +*/ +{ + if (firstcred == NULL) return 0; + + if (firstcred->next == NULL) + { + firstcred->next = newcred; + return 1; + } + + return GRSTgaclCredInsert(firstcred->next, newcred); +} + +int GRSTgaclEntryAddCred(GRSTgaclEntry *entry, GRSTgaclCred *cred) +/* + GRSTaddCred - add a new credential to an existing entry, returning 1 + on success or 0 on error +*/ +{ + if (entry == NULL) return 0; + + if (entry->firstcred == NULL) + { + entry->firstcred = cred; + return 1; + } + else return GRSTgaclCredInsert(entry->firstcred, cred); +} + +static int GRSTgaclCredRemoveCred(GRSTgaclCred *firstcred, GRSTgaclCred *oldcred) +/* + (Private) + + GRSTgaclCredRemoveCred - remove a cred in the *next chain of firstcred + and relink the chain +*/ +{ + if (firstcred == NULL) return 0; + +// yeah, I know +} + +int GRSTgaclEntryDelCred(GRSTgaclEntry *entry, GRSTgaclCred *cred) +/* + GRSTgaclEntryDelCred - remove a new cred from an entry, returning 1 + on success (or absense) or 0 on error. +*/ +{ + if (entry == NULL) return 0; + + return GRSTgaclCredRemoveCred(entry->firstcred, cred); +} + +int GRSTgaclCredPrint(GRSTgaclCred *cred, FILE *fp) +/* + GRSTgaclCredPrint - print a credential and any name-value pairs is contains +*/ +{ + char *q; + GRSTgaclNamevalue *p; + + if (cred->firstname != NULL) + { + fprintf(fp, "<%s>\n", cred->type); + + p = cred->firstname; + + do { + fprintf(fp, "<%s>", p->name); + + for (q=p->value; *q != '\0'; ++q) + if (*q == '<') fputs("<", fp); + else if (*q == '>') fputs(">", fp); + else if (*q == '&') fputs("&" , fp); + else if (*q == '\'') fputs("'", fp); + else if (*q == '"') fputs(""", fp); + else fputc(*q, fp); + + fprintf(fp, "\n", p->name); + + p = (GRSTgaclNamevalue *) p->next; + + } while (p != NULL); + + fprintf(fp, "\n", cred->type); + } + else fprintf(fp, "<%s/>\n", cred->type); + + return 1; +} + +/* * + * Functions to manipulate GRSTgaclEntry structures * + * */ + +GRSTgaclEntry *GRSTgaclEntryNew(void) +/* + GRSTgaclEntryNew - allocate space for a new entry, returning its pointer + or NULL on failure. +*/ +{ + GRSTgaclEntry *newentry; + + newentry = (GRSTgaclEntry *) malloc(sizeof(GRSTgaclEntry)); + if (newentry == NULL) return NULL; + + newentry->firstcred = NULL; + newentry->allowed = 0; + newentry->denied = 0; + newentry->next = NULL; + + return newentry; +} + +int GRSTgaclEntryFree(GRSTgaclEntry *entry) +/* + GRSTgaclEntryFree - free up space used by an entry (always returns 1) +*/ +{ + int i; + + if (entry == NULL) return 1; + + GRSTgaclCredsFree(entry->firstcred); + + free(entry); + + return 1; +} + +static int GRSTgaclEntriesFree(GRSTgaclEntry *entry) +/* + GRSTgaclEntriesFree - free up entry and all entries linked to in its *next + chain +*/ +{ + if (entry == NULL) return 0; + + if (entry->next != NULL) GRSTgaclEntriesFree(entry->next); + + return GRSTgaclEntryFree(entry); +} + +static int GRSTgaclEntryInsert(GRSTgaclEntry *firstentry, GRSTgaclEntry *newentry) +/* + GRSTgaclEntryInsert - insert an entry in the *next chain of firstentry + + FOR THE MOMENT THIS JUST APPENDS +*/ +{ + if (firstentry == NULL) return 0; + + if (firstentry->next == NULL) + { + firstentry->next = newentry; + return 1; + } + + return GRSTgaclEntryInsert(firstentry->next, newentry); +} + +int GRSTgaclAclAddEntry(GRSTgaclAcl *acl, GRSTgaclEntry *entry) +/* + GRSTgaclAclAddEntry - add a new entry to an existing acl, returning 1 + on success or 0 on error +*/ +{ + if (acl == NULL) return 0; + + if (acl->firstentry == NULL) + { + acl->firstentry = entry; + return 1; + } + else return GRSTgaclEntryInsert(acl->firstentry, entry); +} + +int GRSTgaclEntryPrint(GRSTgaclEntry *entry, FILE *fp) +{ + GRSTgaclCred *cred; + GRSTgaclPerm i; + + fputs("\n", fp); + + for (cred = entry->firstcred; cred != NULL; cred = cred->next) + GRSTgaclCredPrint(cred, fp); + + if (entry->allowed) + { + fputs("", fp); + + for (i=GRST_PERM_READ; i <= GRST_PERM_ADMIN; ++i) + if ((entry->allowed) & i) GRSTgaclPermPrint(i, fp); + + fputs("\n", fp); + } + + + if (entry->denied) + { + fputs("", fp); + + for (i=GRST_PERM_READ; i <= GRST_PERM_ADMIN; ++i) + if (entry->denied & i) GRSTgaclPermPrint(i, fp); + + fputs("\n", fp); + } + + fputs("\n", fp); + + return 1; +} + +/* * + * Functions to manipulate GRSTgaclPerm items * + * */ + +int GRSTgaclPermPrint(GRSTgaclPerm perm, FILE *fp) +{ + GRSTgaclPerm i; + + for (i=GRST_PERM_READ; grst_perm_syms[i] != NULL; ++i) + if (perm == grst_perm_vals[i]) + { + fprintf(fp, "<%s/>", grst_perm_syms[i]); + return 1; + } + + return 0; +} + +int GRSTgaclEntryAllowPerm(GRSTgaclEntry *entry, GRSTgaclPerm perm) +{ + entry->allowed = entry->allowed | perm; + + return 1; +} + +int GRSTgaclEntryUnallowPerm(GRSTgaclEntry *entry, GRSTgaclPerm perm) +{ + entry->allowed = entry->allowed & ~perm; + + return 1; +} + +int GRSTgaclEntryDenyPerm(GRSTgaclEntry *entry, GRSTgaclPerm perm) +{ + entry->denied = entry->denied | perm; + + return 1; +} + +int GRSTgaclEntryUndenyPerm(GRSTgaclEntry *entry, GRSTgaclPerm perm) +{ + entry->denied = entry->denied & ~perm; + + return 1; +} + +char *GRSTgaclPermToChar(GRSTgaclPerm perm) +/* + GRSTgaclPermToChar - return char * or NULL corresponding to most significant + set bit of perm. +*/ +{ + char *p = NULL; + GRSTgaclPerm i; + + for (i=0; grst_perm_syms[i] != NULL; ++i) + if (perm & grst_perm_vals[i]) p = grst_perm_syms[i]; + + return p; +} + +GRSTgaclPerm GRSTgaclPermFromChar(char *s) +/* + GRSTgaclPermToChar - return access perm corresponding to symbol s[] +*/ +{ + GRSTgaclPerm i; + + for (i=0; grst_perm_syms[i] != NULL; ++i) + if (strcasecmp(grst_perm_syms[i], s) == 0) return grst_perm_vals[i]; + + return -1; +} + +/* * + * Functions to manipulate GRSTgaclAcl structures * + * */ + +GRSTgaclAcl *GRSTgaclAclNew(void) +/* + GRSTgaclAclNew - allocate a new acl and return its pointer (or NULL + on failure.) +*/ +{ + GRSTgaclAcl *newacl; + + newacl = (GRSTgaclAcl *) malloc(sizeof(GRSTgaclAcl)); + if (newacl == NULL) return NULL; + + newacl->firstentry = NULL; + + return newacl; +} + +int GRSTgaclAclFree(GRSTgaclAcl *acl) +/* + GRSTgaclAclFree - free up space used by *acl. Always returns 1. +*/ +{ + if (acl == NULL) return 1; + + GRSTgaclEntriesFree(acl->firstentry); + + return 1; +} + +int GRSTgaclAclPrint(GRSTgaclAcl *acl, FILE *fp) +{ + GRSTgaclEntry *entry; + + fputs("\n", fp); + + for (entry = acl->firstentry; entry != NULL; entry = entry->next) + GRSTgaclEntryPrint(entry, fp); + + fputs("\n", fp); + + return 1; +} + +int GRSTgaclAclSave(GRSTgaclAcl *acl, char *filename) +{ + int ret; + FILE *fp; + + fp = fopen(filename, "w"); + if (fp == NULL) return 0; + + fputs("\n", fp); + + ret = GRSTgaclAclPrint(acl, fp); + + fclose(fp); + + return ret; +} + +/* * + * Functions for loading and parsing XML using libxml * + * */ + +// need to check these for libxml memory leaks? - what needs to be freed? + +static GRSTgaclCred *GRSTgaclCredParse(xmlNodePtr cur) +/* + GRSTgaclCredParse - parse a credential stored in the libxml structure cur, + returning it as a pointer or NULL on error. +*/ +{ + xmlNodePtr cur2; + GRSTgaclCred *cred; + + cred = GRSTgaclCredNew((char *) cur->name); + + cred->firstname = NULL; + cred->next = NULL; + + for (cur2 = cur->xmlChildrenNode; cur2 != NULL; cur2=cur2->next) + { + GRSTgaclCredAddValue(cred, (char *) cur2->name, + (char *) xmlNodeGetContent(cur2)); + } + + return cred; +} + +static GRSTgaclEntry *GRSTgaclEntryParse(xmlNodePtr cur) +/* + GRSTgaclEntryParse - parse an entry stored in the libxml structure cur, + returning it as a pointer or NULL on error. +*/ +{ + int i; + xmlNodePtr cur2; + GRSTgaclEntry *entry; + GRSTgaclCred *cred; + GRSTgaclPerm perm; + + if (xmlStrcmp(cur->name, (const xmlChar *) "entry") != 0) return NULL; + + cur = cur->xmlChildrenNode; + + entry = GRSTgaclEntryNew(); + + while (cur != NULL) + { + if (xmlStrcmp(cur->name, (const xmlChar *) "allow") == 0) + { + for (cur2 = cur->xmlChildrenNode; cur2 != NULL; cur2=cur2->next) + for (i=0; grst_perm_syms[i] != NULL; ++i) + if (xmlStrcmp(cur2->name, + (const xmlChar *) grst_perm_syms[i]) == 0) + GRSTgaclEntryAllowPerm(entry, grst_perm_vals[i]); + } + else if (xmlStrcmp(cur->name, (const xmlChar *) "deny") == 0) + { + for (cur2 = cur->xmlChildrenNode; cur2 != NULL; cur2=cur2->next) + for (i=0; grst_perm_syms[i] != NULL; ++i) + if (xmlStrcmp(cur2->name, + (const xmlChar *) grst_perm_syms[i]) == 0) + GRSTgaclEntryDenyPerm(entry, grst_perm_vals[i]); + } + else if ((cred = GRSTgaclCredParse(cur)) != NULL) + { + if (!GRSTgaclEntryAddCred(entry, cred)) + { + GRSTgaclCredFree(cred); + GRSTgaclEntryFree(entry); + return NULL; + } + } + else /* I cannot parse this - give up rather than get it wrong */ + { + GRSTgaclEntryFree(entry); + return NULL; + } + + cur=cur->next; + } + + return entry; +} + +GRSTgaclAcl *GRSTgaclAclLoadFile(char *filename) +{ + xmlDocPtr doc; + xmlNodePtr cur; + GRSTgaclAcl *acl; + GRSTgaclEntry *entry; + + doc = xmlParseFile(filename); + if (doc == NULL) return NULL; + + cur = xmlDocGetRootElement(doc); + + if (xmlStrcmp(cur->name, (const xmlChar *) "gacl")) + { + free(doc); + free(cur); + return NULL; + } + + cur = cur->xmlChildrenNode; + + acl = GRSTgaclAclNew(); + + while (cur != NULL) + { + entry = GRSTgaclEntryParse(cur); + if (entry == NULL) + { + GRSTgaclAclFree(acl); + xmlFreeDoc(doc); + return NULL; + } + + GRSTgaclAclAddEntry(acl, entry); + + cur=cur->next; + } + + xmlFreeDoc(doc); + return acl; +} + +int GRSTgaclFileIsAcl(char *pathandfile) +/* Return 1 if filename in *pathandfile starts GRST_ACL_FILE + Return 0 otherwise. */ +{ + char *filename; + + filename = rindex(pathandfile, '/'); + if (filename == NULL) filename = pathandfile; + else filename++; + + return (strncmp(filename, GRST_ACL_FILE, sizeof(GRST_ACL_FILE) - 1) == 0); +} + +char *GRSTgaclFileFindAclname(char *pathandfile) +/* Return malloc()ed ACL filename that governs the given file or directory + (for directories, the ACL file is in the directory itself), or NULL if none + can be found. */ +{ + char *path, *p; + struct stat statbuf; + + path = malloc(strlen(pathandfile) + sizeof(GRST_ACL_FILE) + 1); + strcpy(path, pathandfile); + + if (stat(path, &statbuf) == 0) + { + if (!S_ISDIR(statbuf.st_mode)) /* can strip this / off straightaway */ + { + p = rindex(path, '/'); + if (p != NULL) *p = '\0'; + } + } + + while (path[0] != '\0') + { + strcat(path, "/"); + strcat(path, GRST_ACL_FILE); + + if (stat(path, &statbuf) == 0) return path; + + p = rindex(path, '/'); + *p = '\0'; /* strip off the / we added for ACL */ + + p = rindex(path, '/'); + if (p == NULL) break; /* must start without / and we there now ??? */ + + *p = '\0'; /* strip off another layer of / */ + } + + free(path); + return NULL; +} + +GRSTgaclAcl *GRSTgaclAclLoadforFile(char *pathandfile) +/* Return ACL that governs the given file or directory (for directories, + the ACL file is in the directory itself.) */ +{ + char *path; + GRSTgaclAcl *acl; + + path = GRSTgaclFileFindAclname(pathandfile); + + if (path != NULL) + { + acl = GRSTgaclAclLoadFile(path); + free(path); + return acl; + } + + return NULL; +} + +/* * + * Functions to create and query GACLuser * + * */ + +GRSTgaclUser *GRSTgaclUserNew(GRSTgaclCred *cred) +{ + GRSTgaclUser *user; + + if (cred == NULL) return NULL; + + user = malloc(sizeof(GRSTgaclUser)); + + if (user != NULL) user->firstcred = cred; + + user->dnlists = NULL; + + return user; +} + +int GRSTgaclUserFree(GRSTgaclUser *user) +{ + if (user == NULL) return 1; + + if (user->firstcred != NULL) GRSTgaclCredsFree(user->firstcred); + + if (user->dnlists != NULL) free(user->dnlists); + + free(user); + + return 1; +} + +int GRSTgaclUserAddCred(GRSTgaclUser *user, GRSTgaclCred *cred) +{ + GRSTgaclCred *crediter; + + if ((user == NULL) || (cred == NULL)) return 0; + + if (user->firstcred == NULL) + { + user->firstcred = cred; + cred->next = NULL; /* so cannot be used to add whole lists */ + return 1; + } + + crediter = user->firstcred; + + while (crediter->next != NULL) crediter = crediter->next; + + crediter->next = cred; + cred->next = NULL; /* so cannot be used to add whole lists */ + + return 1; +} + +int GRSTgaclUserHasCred(GRSTgaclUser *user, GRSTgaclCred *cred) +/* test if the user has the given credential */ +{ + GRSTgaclCred *crediter; + GRSTgaclNamevalue *usernamevalue, *crednamevalue; + + if (cred == NULL) return 0; + + if (strcmp(cred->type, "any-user") == 0) return 1; + + if (user == NULL) return 0; + + if (strcmp(cred->type, "dn-list") == 0) + { + if ((cred->firstname == NULL) || + (strcmp((cred->firstname)->name, "url") != 0) || + ((cred->firstname)->next != NULL)) return 0; + + return GRSTgaclDNlistHasUser((cred->firstname)->value, user); + } + + if (strcmp(cred->type, "dns") == 0) + { + if ((user->firstcred == NULL) || + ((user->firstcred)->firstname == NULL) || + (cred->firstname == NULL) || + (strcmp((cred->firstname)->name, "hostname") != 0) || + ((cred->firstname)->next != NULL)) return 0; + + for (crediter=user->firstcred; + crediter != NULL; + crediter = crediter->next) + if (strcmp(crediter->type, "dns") == 0) + { + if ((crediter->firstname == NULL) || + (strcmp((crediter->firstname)->name, "hostname") != 0)) return 0; + + return (fnmatch((cred->firstname)->value, + (crediter->firstname)->value, FNM_CASEFOLD) == 0); + } + + return 0; + } + + if (strcmp(cred->type, "auth-user") == 0) + { + if ((user->firstcred == NULL) || + ((user->firstcred)->firstname == NULL)) return 0; + + for (crediter=user->firstcred; + crediter != NULL; + crediter = crediter->next) + if (strcmp(crediter->type, "person") == 0) return 1; + + return 0; + } + + for (crediter=user->firstcred; crediter != NULL; crediter = crediter->next) + { + if (strcmp(crediter->type, cred->type) != 0) continue; + + if ((crediter->firstname == NULL) && + (cred->firstname == NULL)) return 1; + + if ((crediter->firstname == NULL) || + (cred->firstname == NULL)) continue; + + usernamevalue = crediter->firstname; + crednamevalue = cred->firstname; + + for (;;) + { + if (strcmp(usernamevalue->name,crednamevalue->name) != 0) break; + + if (strcmp(cred->type, "person") == 0) + { + if (GRSTx509NameCmp(usernamevalue->value, + crednamevalue->value) != 0) break; + } + else if (strcmp(usernamevalue->value, + crednamevalue->value) != 0) break; + + /* ok if cred list runs out before user's cred list */ + if (crednamevalue->next == NULL) return 1; + + /* but not ok if more names to match which user doesn't have */ + if (usernamevalue->next == NULL) break; + + crednamevalue = (GRSTgaclNamevalue *) crednamevalue->next; + usernamevalue = (GRSTgaclNamevalue *) usernamevalue->next; + } + } + + return 0; +} + +GRSTgaclCred *GRSTgaclUserFindCredtype(GRSTgaclUser *user, char *type) +/* find the first credential of a given type for this user */ +{ + GRSTgaclCred *cred; + + if (user == NULL) return NULL; + + cred = user->firstcred; + + while (cred != NULL) + { + if (strcmp(cred->type, type) == 0) return cred; + + cred = cred->next; + } + + return NULL; +} + +int GRSTgaclUserSetDNlists(GRSTgaclUser *user, char *dnlists) +{ + if ((user == NULL) || (dnlists == NULL)) return 0; + + if (user->dnlists != NULL) free(user->dnlists); + + user->dnlists = strdup(dnlists); + + return 1; +} + +/* * + * Functions to test for access perm of an individual * + * */ + +static char *recurse4file(char *dir, char *file, int recurse_level) +/* try to find file[] in dir[]. try subdirs if not found. + return full path to first found version or NULL on failure */ +{ + char *fullfilename, *fulldirname; + struct stat statbuf; + DIR *dirDIR; + struct dirent *file_ent; + + /* try to find in current directory */ + + asprintf(&fullfilename, "%s/%s", dir, file); + if (stat(fullfilename, &statbuf) == 0) return fullfilename; + free(fullfilename); + + /* maybe search in subdirectories */ + + if (recurse_level >= GRST_RECURS_LIMIT) return NULL; + + dirDIR = opendir(dir); + + if (dirDIR == NULL) return NULL; + + while ((file_ent = readdir(dirDIR)) != NULL) + { + if (file_ent->d_name[0] == '.') continue; + + asprintf(&fulldirname, "%s/%s", dir, file_ent->d_name); + + if ((stat(fulldirname, &statbuf) == 0) && + S_ISDIR(statbuf.st_mode) && + ((fullfilename = recurse4file(fulldirname, file, + recurse_level + 1)) != NULL)) + { + closedir(dirDIR); + return fullfilename; + } + + free(fulldirname); + } + + closedir(dirDIR); + + return NULL; +} + +int GRSTgaclDNlistHasUser(char *listurl, GRSTgaclUser *user) +{ + char *dn_lists_dirs, *dn_list_ptr, *enclisturl, *filename, *dirname, + line[512], *p; + FILE *fp; + GRSTgaclCred *cred; + + if ((listurl == NULL) || (user == NULL)) return 0; + + enclisturl = GRSThttpUrlEncode(listurl); + + if (user->dnlists != NULL) p = user->dnlists; + else p = getenv("GRST_DN_LISTS"); + + if (p == NULL) p = GRST_DN_LISTS; + dn_lists_dirs = strdup(p); /* we need to keep this for free() later! */ + dn_list_ptr = dn_lists_dirs; /* copy, for naughty function strsep() */ + + while ((dirname = strsep(&dn_list_ptr, ":")) != NULL) + { + filename = recurse4file(dirname, enclisturl, 0); + if (filename == NULL) continue; + + fp = fopen(filename, "r"); + free(filename); + + if (fp == NULL) continue; + + while (fgets(line, sizeof(line), fp) != NULL) + { + p = index(line, '\n'); + if (p != NULL) *p = '\0'; + + cred = user->firstcred; + + while (cred != NULL) + { + if ((strcmp(cred->type, "person") == 0) && + (cred->firstname != NULL) && + (strcmp("dn", (cred->firstname)->name) == 0) && + (GRSTx509NameCmp(line, (cred->firstname)->value) == 0)) + { + fclose(fp); + free(dn_lists_dirs); + free(enclisturl); + return 1; + } + + cred = cred->next; + } + } + + fclose(fp); + } + + free(dn_lists_dirs); + free(enclisturl); + + return 0; +} + +GRSTgaclPerm GRSTgaclAclTestUser(GRSTgaclAcl *acl, GRSTgaclUser *user) +/* + GACLgaclAclTestUser - return bit fields depending on access perms user has + for given acl. All zero for no access. If *user is + NULL, matching to "any-user" will still work. +*/ +{ + int flag, onlyanyuser; + GRSTgaclPerm allowperms = 0, denyperms = 0, allowed; + GRSTgaclEntry *entry; + GRSTgaclCred *cred, *usercred; + + if (acl == NULL) return 0; + + for (entry = acl->firstentry; entry != NULL; entry = entry->next) + { + flag = 1; /* begin by assuming this entry applies to us */ + onlyanyuser = 1; /* begin by assuming just */ + + /* now go through creds, checking they all do apply to us */ + + for (cred = entry->firstcred; cred != NULL; cred = cred->next) + if (!GRSTgaclUserHasCred(user, cred)) flag = 0; + else if (strcmp(cred->type, "any-user") != 0) onlyanyuser = 0; + + if (!flag) continue; /* flag false if a subtest failed */ + + /* does apply to us, so we remember this entry's perms */ + + /* we dont allow Write or Admin on the basis of any-user alone */ + + allowed = entry->allowed; + + if (onlyanyuser) + allowed = entry->allowed & ~GRST_PERM_WRITE & ~GRST_PERM_ADMIN; + else allowed = entry->allowed; + + allowperms = allowperms | allowed; + denyperms = denyperms | entry->denied; + } + + return (allowperms & (~ denyperms)); + /* for each perm type, any deny we saw kills any allow */ +} + +GRSTgaclPerm GRSTgaclAclTestexclUser(GRSTgaclAcl *acl, GRSTgaclUser *user) +/* + GRSTgaclAclTestexclUser - + return bit fields depending on ALLOW perms OTHER users + have for given acl. All zero if they have no access. + (used for testing if a user has exclusive access) +*/ +{ + int flag; + GRSTgaclPerm perm = 0; + GRSTgaclEntry *entry; + GRSTgaclCred *cred; + + if (acl == NULL) return 0; + + for (entry = acl->firstentry; entry != NULL; entry = entry->next) + { + flag = 0; /* flag will be set if cred implies other users */ + + for (cred = entry->firstcred; cred != NULL; cred = cred->next) + { + if (strcmp(cred->type, "person") != 0) + /* if we ever add support for other person-specific credentials, + they must also be recognised here */ + { + flag = 1; + break; + } + + if (!GRSTgaclUserHasCred(user, cred)) + /* if user doesnt have this person credential, assume + it refers to a different individual */ + { + flag = 1; + break; + } + } + + if (flag) perm = perm | entry->allowed; + } + + return perm; +} + +/* + Wrapper functions for gridsite-gacl.h support of legacy API +*/ + +GRSTgaclEntry *GACLparseEntry(xmlNodePtr cur) +{ + return GRSTgaclEntryParse(cur); +} diff --git a/org.gridsite.core/src/grst_http.c b/org.gridsite.core/src/grst_http.c new file mode 100644 index 0000000..c7b375e --- /dev/null +++ b/org.gridsite.core/src/grst_http.c @@ -0,0 +1,407 @@ +/* + Copyright (c) 2002-3, Andrew McNab, University of Manchester + All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + o Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + o Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef VERSION +#define VERSION "x.x.x" +#endif + +#define _GNU_SOURCE +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gridsite.h" + +void GRSThttpBodyInit(GRSThttpBody *thisbody) +{ + thisbody->size = 0; /* simple, but we don't expose internals to callers */ +} + +void GRSThttpPrintf(GRSThttpBody *thisbody, char *fmt, ...) +/* append printf() style format and arguments to *thisbody. + This requires vasprintf from glibc!! */ +{ + char *p; + size_t size; + va_list args; + + va_start(args, fmt); + size = vasprintf(&p, fmt, args); + va_end(args); + + if (size == 0) free(p); /* don't need to bother in this case */ + else if (size > 0) + { + if (thisbody->size == 0) /* need to initialise */ + { + thisbody->first = (GRSThttpCharsList *)malloc(sizeof(GRSThttpCharsList)); + thisbody->first->text = p; + thisbody->first->next = NULL; + + thisbody->last = thisbody->first; + thisbody->size = size; + } + else + { + thisbody->last->next = (GRSThttpCharsList *) + malloc(sizeof(GRSThttpCharsList)); + ((GRSThttpCharsList *) thisbody->last->next)->text = p; + ((GRSThttpCharsList *) thisbody->last->next)->next = NULL; + + thisbody->last = thisbody->last->next; + thisbody->size = thisbody->size + size; + } + } +} + +int GRSThttpCopy(GRSThttpBody *thisbody, char *file) +/* + copy a whole file, named file[], into the body output buffer, returning + 1 if file was found and copied ok, or 0 otherwise. +*/ +{ + int fd, len; + char c, *p; + struct stat statbuf; + + fd = open(file, O_RDONLY); + + if (fd == -1) return 0; + + if (fstat(fd, &statbuf) != 0) + { + close(fd); + return 0; + } + + p = malloc(statbuf.st_size + 1); + + if (p == NULL) + { + close(fd); + return 0; + } + + len = read(fd, p, statbuf.st_size); + p[len] = '\0'; + + close(fd); + + if (thisbody->size == 0) /* need to initialise */ + { + thisbody->first = (GRSThttpCharsList *) malloc(sizeof(GRSThttpCharsList)); + thisbody->first->text = p; + thisbody->first->next = NULL; + + thisbody->last = thisbody->first; + thisbody->size = len; + } + else + { + thisbody->last->next=(GRSThttpCharsList *)malloc(sizeof(GRSThttpCharsList)); + ((GRSThttpCharsList *) thisbody->last->next)->text = p; + ((GRSThttpCharsList *) thisbody->last->next)->next = NULL; + + thisbody->last = thisbody->last->next; + thisbody->size = thisbody->size + len; + } + + return 1; +} + +void GRSThttpWriteOut(GRSThttpBody *thisbody) +/* output Content-Length header, blank line then whole of the body to + standard output */ +{ + GRSThttpCharsList *p; + + printf("Content-Length: %d\n\n", thisbody->size); + + p = thisbody->first; + + while (p != NULL) + { + fputs(p->text, stdout); + + p = p->next; + } +} + +int GRSThttpPrintHeaderFooter(GRSThttpBody *bp, char *file, char *headfootname) +/* + try to print Header or Footer appropriate for absolute path file[], + returning 1 rather than 0 if found. +*/ +{ + int found = 0; + char *pathfile, *p; + struct stat statbuf; + + pathfile = malloc(strlen(file) + strlen(headfootname) + 2); + strcpy(pathfile, file); + + if ((pathfile[strlen(pathfile) - 1] != '/') && + (stat(pathfile, &statbuf) == 0) && + S_ISDIR(statbuf.st_mode)) strcat(pathfile, "/"); + + for (;;) + { + p = rindex(pathfile, '/'); + if (p == NULL) break; + p[1] = '\0'; + strcat(p, headfootname); + + if (stat(pathfile, &statbuf) == 0) + { + found = GRSThttpCopy(bp, pathfile); + break; + } + + p[0] = '\0'; + } + + free(pathfile); + return found; +} + +char *GRSThttpGetCGI(char *name) +/* + Return a malloc()ed copy of CGI form parameter identified by name[], + either received by QUERY_STRING (via GET) or on stdin (via POST). + Caller must free() the returned string itself. If name[] is not found, + an empty NUL-terminated malloc()ed string is returned. name[] has any + URL-encoding reversed. +*/ +{ + char *p, *namepattern, *valuestart, *returnvalue, *querystring; + int c, i, j, n, contentlength = 0; + static char *cgiposted = NULL; + size_t size_needed; + + if (cgiposted == NULL) /* have to initialise cgiposted */ + { + p = getenv("CONTENT_LENGTH"); + if (p != NULL) sscanf(p, "%d", &contentlength); + + querystring = getenv("REDIRECT_QUERY_STRING"); + if (querystring == NULL) querystring = getenv("QUERY_STRING"); + + if (querystring == NULL) cgiposted = malloc(contentlength + 3); + else cgiposted = malloc(contentlength + strlen(querystring) + 4); + + cgiposted[0] = '&'; + + for (i = 1; i <= contentlength; ++i) + { + c = getchar(); + if (c == EOF) break; + cgiposted[i] = c; + } + + cgiposted[i] = '&'; + cgiposted[i+1] = '\0'; + + if (querystring != NULL) + { + strcat(cgiposted, querystring); + strcat(cgiposted, "&"); + } + } + + namepattern = malloc(strlen(name) + 3); + sprintf(namepattern, "&%s=", name); + + p = strstr(cgiposted, namepattern); + free(namepattern); + if (p == NULL) return strdup(""); + + valuestart = &p[strlen(name) + 2]; + + for (n=0; valuestart[n] != '&'; ++n) ; + + returnvalue = malloc(n + 1); + + j=0; + + for (i=0; i < n; ++i) + { + if ((i < n - 2) && (valuestart[i] == '%')) /* url encoded as %HH */ + { + returnvalue[j] = 0; + + if (isdigit(valuestart[i+1])) + returnvalue[j] += 16 * (valuestart[i+1] - '0'); + else if (isalpha(valuestart[i+1])) + returnvalue[j] += 16 * (10 + tolower(valuestart[i+1]) - 'a'); + + if (isdigit(valuestart[i+2])) + returnvalue[j] += valuestart[i+2] - '0'; + else if (isalpha(valuestart[i+2])) + returnvalue[j] += 10 + tolower(valuestart[i+2]) - 'a'; + + i = i + 2; + } + else if (valuestart[i] == '+') returnvalue[j] = ' '; + else returnvalue[j] = valuestart[i]; + + if (returnvalue[j] == '\r') continue; /* CR/LF -> LF */ + ++j; + } + + returnvalue[j] = '\0'; + + return returnvalue; +} + +/* * + * Utility functions * + * */ + +char *GRSThttpUrlDecode(char *in) +{ + int i, j, n; + char *out; + + n = strlen(in); + out = malloc(n + 1); + + j=0; + + for (i=0; i < n; ++i) + { + if ((i < n - 2) && (in[i] == '%')) /* url encoded as %HH */ + { + out[j] = 0; + + if (isdigit(in[i+1])) + out[j] += 16 * (in[i+1] - '0'); + else if (isalpha(in[i+1])) + out[j] += 16 * (10 + tolower(in[i+1]) - 'a'); + + if (isdigit(in[i+2])) + out[j] += in[i+2] - '0'; + else if (isalpha(in[i+2])) + out[j] += 10 + tolower(in[i+2]) - 'a'; + + i = i + 2; + } + else if (in[i] == '+') out[j] = ' '; + else out[j] = in[i]; + + ++j; + } + + out[j] = '\0'; + + return out; +} + +char *GRSThttpUrlEncode(char *in) +/* Return a pointer to a malloc'd string holding a URL-encoded (RFC 1738) + version of *in. Only A-Z a-z 0-9 . _ - are passed through unmodified. + (DN's processed by GRSThttpUrlEncode can be used as valid Unix filenames, + assuming they do not exceed restrictions on filename length.) */ +{ + char *out, *p, *q; + + out = malloc(3*strlen(in) + 1); + + p = in; + q = out; + + while (*p != '\0') + { + if (isalnum(*p) || (*p == '.') || (*p == '_') || (*p == '-')) + { + *q = *p; + ++q; + } + else + { + sprintf(q, "%%%2X", *p); + q = &q[3]; + } + + ++p; + } + + *q = '\0'; + return out; +} + +char *GRSThttpUrlMildencode(char *in) +/* Return a pointer to a malloc'd string holding a partially URL-encoded + version of *in. "Partially" means that A-Z a-z 0-9 . = - _ @ and / + are passed through unmodified. (DN's processed by GRSThttpUrlMildencode() + can be used as valid Unix paths+filenames if you are prepared to + create or simulate the resulting /X=xyz directories.) */ +{ + char *out, *p, *q; + + out = malloc(3*strlen(in) + 1); + + p = in; + q = out; + + while (*p != '\0') + { + if (isalnum(*p) || (*p == '.') || (*p == '=') || (*p == '-') + || (*p == '/') || (*p == '@') || (*p == '_')) + { + *q = *p; + ++q; + } + else if (*p == ' ') + { + *q = '+'; + ++q; + } + else + { + sprintf(q, "%%%2X", *p); + q = &q[3]; + } + + ++p; + } + + *q = '\0'; + return out; +} diff --git a/org.gridsite.core/src/grst_x509.c b/org.gridsite.core/src/grst_x509.c new file mode 100644 index 0000000..797786d --- /dev/null +++ b/org.gridsite.core/src/grst_x509.c @@ -0,0 +1,846 @@ +/* + Copyright (c) 2002-3, Andrew McNab, University of Manchester + All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + o Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + o Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + ------------------------------------------------------------------------ + For more information about GridSite: http://www.gridpp.ac.uk/gridsite/ + ------------------------------------------------------------------------ +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gridsite.h" + +/// Compare X509 Distinguished Name strings +int GRSTx509NameCmp(char *a, char *b) +/** + * This function attempts to do with string representations what + * would ideally be done with OIDs/values. In particular, we equate + * "/Email=" == "/emailAddress=" to deal with this important change + * between OpenSSL 0.9.6 and 0.9.7. + * Other than that, it is currently the same as ordinary strcmp(3). + */ +{ + int ret; + char *aa, *bb, *p; + + aa = strdup(a); + while ((p = strstr(aa, "/emailAddress=")) != NULL) + { + memmove(&p[6], &p[13], strlen(&p[13]) + 1); + p[1] = 'E'; + } + + bb = strdup(b); + while ((p = strstr(bb, "/emailAddress=")) != NULL) + { + memmove(&p[6], &p[13], strlen(&p[13]) + 1); + p[1] = 'E'; + } + + ret = strcmp(aa, bb); + + free(aa); + free(bb); + + return ret; +} + + +/// Check critical extensions +/** + * Returning GRST_RET_OK if all of extensions are known to us or + * OpenSSL; GRST_REF_FAILED otherwise. + * + * Since this function relies on functionality (X509_supported_extension) + * introduced in 0.9.7, then we do nothing and report an error + * (GRST_RET_FAILED) if one of the associated defines + * (X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION) is absent. + */ + +int GRSTx509KnownCriticalExts(X509 *cert) +{ + int i; + char s[80]; + X509_EXTENSION *ex; + +#ifdef X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION + for (i = 0; i < X509_get_ext_count(cert); ++i) + { + ex = X509_get_ext(cert, i); + + if (X509_EXTENSION_get_critical(ex) && + !X509_supported_extension(ex)) + { + OBJ_obj2txt(s, sizeof(s), X509_EXTENSION_get_object(ex), 1); + + if (strcmp(s, GRST_PROXYCERTINFO_OID) != 0) return GRST_RET_FAILED; + } + } + + return GRST_RET_OK; +#else + return GRST_RET_FAILED; +#endif +} + +/// ASN1 time string (in a char *) to time_t +/** + * (Use ASN1_STRING_data() to convert ASN1_GENERALIZEDTIME to char * if + * necessary) + */ + +time_t GRSTasn1TimeToTimeT(char *asn1time) +{ + char zone; + struct tm time_tm; + + if ((sscanf(asn1time, "%02d%02d%02d%02d%02d%02d%c", + &(time_tm.tm_year), + &(time_tm.tm_mon), + &(time_tm.tm_mday), + &(time_tm.tm_hour), + &(time_tm.tm_min), + &(time_tm.tm_sec), + &zone) != 7) || (zone != 'Z')) return 0; /* dont understand */ + + /* time format fixups */ + + if (time_tm.tm_year < 90) time_tm.tm_year += 100; + --(time_tm.tm_mon); + + return timegm(&time_tm); +} + +/// Check if certificate can be used as a CA to sign standard X509 certs +/* + * Return GRST_RET_OK if true; GRST_RET_FAILED if not. + */ + +int GRSTx509IsCA(X509 *cert) +{ + int idret, purpose_id; + + purpose_id = X509_PURPOSE_get_by_sname("sslclient"); + + /* final argument to X509_check_purpose() is whether to check for CAness */ + + if (X509_check_purpose(cert, purpose_id + X509_PURPOSE_MIN, 1)) + return GRST_RET_OK; + else return GRST_RET_FAILED; +} + +/// Check certificate chain for GSI proxy acceptability. +/** + * Returns X509_V_OK/GRST_RET_OK if valid; OpenSSL X509 errors otherwise. + * + * Inspired by GSIcheck written by Mike Jones, SVE, Manchester Computing, + * The University of Manchester. + * + * The GridSite version handles old and new style Globus proxies, and + * proxies derived from user certificates issued with "X509v3 Basic + * Constraints: CA:FALSE" (eg UK e-Science CA) + * + * We do not check chain links between certs here: this is done by + * GRST_check_issued/X509_check_issued in mod_ssl's ssl_engine_init.c + * + * TODO: we do not yet check ProxyCertInfo and ProxyCertPolicy extensions + * (although via GRSTx509KnownCriticalExts() we can accept them.) + */ + +int GRSTx509CheckChain(int *first_non_ca, X509_STORE_CTX *ctx) +{ + STACK_OF(X509) *certstack; /* Points to the client's cert chain */ + X509 *cert; /* Points to the client's cert */ + int depth; /* Depth of cert chain */ + size_t len,len2; /* Lengths of issuer and cert DN */ + int IsCA; /* Holds whether cert is allowed to sign */ + int prevIsCA; /* Holds whether previous cert in chain is + allowed to sign */ + int prevIsLimited; /* previous cert was proxy and limited */ + int i,j; /* Iteration variables */ + char *cert_DN; /* Pointer to current-certificate-in-chain's + DN */ + char *issuer_DN; /* Pointer to + issuer-of-current-cert-in-chain's DN */ + char *proxy_part_DN; /* Pointer to end part of current-cert-in-chain + maybe eg "/CN=proxy" */ + time_t now; + + time(&now); + + *first_non_ca = 0; /* set to something predictable if things fail */ + + /* Check for context */ + if (!ctx) return X509_V_ERR_INVALID_CA; + /* Can't GSI-verify if there is no context. Here and throughout this + function we report all errors as X509_V_ERR_INVALID_CA. */ + + /* Set necessary preliminary values */ + IsCA = TRUE; /* =prevIsCA - start from a CA */ + prevIsLimited = 0; + + /* Get the client cert chain */ + certstack = X509_STORE_CTX_get_chain(ctx); /* Get the client's chain */ + depth = sk_X509_num(certstack); /* How deep is that chain? */ + + /* Check the client chain */ + for (i=depth-1; i >= 0; --i) + /* loop through client-presented chain starting at CA end */ + { + prevIsCA=IsCA; + + /* Check for X509 certificate and point to it with 'cert' */ + if (cert = sk_X509_value(certstack, i)) + { + /* we check times and reject immediately if invalid */ + + if (now < + GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notBefore(cert)))) + return X509_V_ERR_INVALID_CA; + + if (now > + GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notAfter(cert)))) + return X509_V_ERR_INVALID_CA; + + /* If any forebear certificate is not allowed to sign we must + assume all decendents are proxies and cannot sign either */ + if (prevIsCA) + { + /* always treat the first cert (from the CA files) as a CA */ + if (i == depth-1) IsCA = TRUE; + /* check if this cert is valid CA for signing certs */ + else IsCA = (GRSTx509IsCA(cert) == GRST_RET_OK); + + if (!IsCA) *first_non_ca = i; + } + else + { + IsCA = FALSE; + /* Force proxy check next iteration. Important because I can + sign any CA I create! */ + } + + cert_DN = X509_NAME_oneline(X509_get_subject_name(cert),NULL,0); + issuer_DN = X509_NAME_oneline(X509_get_issuer_name(cert),NULL,0); + len = strlen(cert_DN); + len2 = strlen(issuer_DN); + + /* issuer didn't have CA status, so this is (at best) a proxy: + check for bad proxy extension*/ + + if (!prevIsCA) + { + if (prevIsLimited) /* we reject proxies of limited proxies! */ + return X509_V_ERR_INVALID_CA; + + /* User not allowed to sign shortened DN */ + if (len2 > len) return X509_V_ERR_INVALID_CA; + + /* Proxy subject must begin with issuer. */ + if (strncmp(cert_DN, issuer_DN, len2) != 0) + return X509_V_ERR_INVALID_CA; + + /* Set pointer to end of base DN in cert_DN */ + proxy_part_DN = &cert_DN[len2]; + + /* First attempt at support for Old and New style GSI + proxies: /CN=anything is ok for now */ + if (strncmp(proxy_part_DN, "/CN=", 4) != 0) + return X509_V_ERR_INVALID_CA; + + if ((strncmp(proxy_part_DN, "/CN=limited proxy", 17) == 0) && + (i > 0)) prevIsLimited = 1; /* ready for next cert ... */ + } + } + } + + /* Check cert whose private key is being used by client. If previous in + chain is not allowed to be a CA then need to check this final cert for + valid proxy-icity too */ + if (!prevIsCA) + { + if (prevIsLimited) return X509_V_ERR_INVALID_CA; + /* we do not accept proxies signed by limited proxies */ + + if (cert = sk_X509_value(certstack, 0)) + { + /* Load DN & length of DN and either its issuer or the + first-bad-issuer-in-chain */ + cert_DN = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0); + issuer_DN = X509_NAME_oneline(X509_get_issuer_name(cert), NULL, 0); + len = strlen(cert_DN); + len2 = strlen(issuer_DN); + + /* issuer didn't have CA status, check for bad proxy extension */ + + if (len2 > len) return X509_V_ERR_INVALID_CA; + /* User not allowed to sign shortened DN */ + + if (strncmp(cert_DN, issuer_DN, len2) != 0) + return X509_V_ERR_INVALID_CA; + /* Proxy subject must begin with issuer. */ + + proxy_part_DN = &cert_DN[len2]; + /* Set pointer to end of DN base in cert_DN */ + + /* Remander of subject must be either "/CN=proxy" or + "/CN=limited proxy" (or /CN=XYZ for New style GSI) */ + + /* First attempt at support for Old and New style GSI + proxies: /CN=anything is ok for now. */ + if (strncmp(proxy_part_DN, "/CN=", 4) != 0) + return X509_V_ERR_INVALID_CA; + } + } + + return X509_V_OK; /* this is also GRST_RET_OK, of course - by choice */ +} + +/// Example VerifyCallback routine + +/** + * + */ + +int GRSTx509VerifyCallback (int ok, X509_STORE_CTX *ctx) +{ + int errnum = X509_STORE_CTX_get_error(ctx); + int errdepth = X509_STORE_CTX_get_error_depth(ctx); + int first_non_ca; + +#ifndef X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION +#define X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION 34 +#endif + + if (errnum == X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION) + { + if (GRSTx509KnownCriticalExts(X509_STORE_CTX_get_current_cert(ctx)) + == GRST_RET_OK) + { + ok = TRUE; + errnum = X509_V_OK; + X509_STORE_CTX_set_error(ctx, errnum); + } + } + else if ((errdepth == 0) && + (errnum == X509_V_OK) && + (GRSTx509CheckChain(&first_non_ca, ctx) != X509_V_OK)) ok = FALSE; + + + return ok; + +// check this + +// if (ok) return GRST_RET_OK; +// else return GRST_RET_FAILED; +} + +/// Check VOMS signature +/** + * Return GRST_RET_OK if signature starting at *sig matches *data and is + * from VOMS *vo; return GRST_RET_FAILED otherwise. + */ + +int GRSTx509CheckVomsSig(unsigned char *sig, unsigned int siglen, + unsigned char *data, unsigned int datalen, + char *vomsdir, char *vo, char *vomsdn) +{ + int ret = 0; + char *certfilename, *certdn; + FILE *fp; + DIR *vomsDIR; + struct dirent *certdirent; + X509 *cert; + EVP_PKEY *pubkey; + EVP_MD_CTX ctx; + struct stat statbuf; + time_t now; + + time(&now); + + vomsDIR = opendir(vomsdir); + if (vomsDIR == NULL) + { + return GRST_RET_NO_SUCH_FILE; + } + + while ((certdirent = readdir(vomsDIR)) != NULL) + { + certfilename = malloc(strlen(vomsdir) + + strlen(certdirent->d_name) + 2); + + strcpy(certfilename, vomsdir); + strcat(certfilename, "/"); + strcat(certfilename, certdirent->d_name); + + if ((stat(certfilename, &statbuf) != 0) || S_ISDIR(statbuf.st_mode)) + { + free(certfilename); + continue; + } + + fp = fopen(certfilename, "r"); + free(certfilename); + + if (fp == NULL) continue; + + cert = PEM_read_X509(fp, NULL, NULL, NULL); + fclose(fp); + + if (cert == NULL) continue; + + certdn = X509_NAME_oneline(X509_get_subject_name(cert),NULL,0); + + if ((now < + GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notBefore(cert))) ) || + (now > + GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notAfter( cert))) ) || + (GRSTx509NameCmp(certdn, vomsdn) != 0)) + { + X509_free(cert); + continue; + } + + pubkey = X509_extract_key(cert); + + if (pubkey != NULL) + { +// EVP_MD_CTX_init(&ctx); + EVP_VerifyInit(&ctx, EVP_sha1()); + EVP_VerifyUpdate(&ctx, (unsigned char *) data, datalen); + + ret=EVP_VerifyFinal(&ctx, (unsigned char *) sig, siglen, pubkey); + +// EVP_MD_CTX_cleanup(&ctx); + } + + closedir(vomsDIR); /* we're finished now, one way or the other */ + + X509_free(cert); + + /* since we matched cert names and times, we always return */ + + if (ret == 1) return GRST_RET_OK; + else return GRST_RET_BAD_SIGNATURE; + } + + closedir(vomsDIR); + + return GRST_RET_CERT_NOT_FOUND; /* didnt find a matching VOMS cert */ +} + +/// Get the VOMS attributes in the extensions to the given cert +/* + * Puts any VOMS credentials found into the Compact Creds string array + * starting at *creds. Always returns GRST_RET_OK. + */ + +int GRSTx509GetVomsCreds(int *lastcred, int maxcreds, size_t credlen, + char *creds, X509 *cert, X509 *usercert, + char *vomsdir) +{ + int i, j; + unsigned int siglen=-1, datalength=-1, dataoffset = -1; + char s[80]; + unsigned char *charstr, *p, *time1 = NULL, *time2 = NULL, *vo = NULL, + *uri = NULL, *user = NULL, *group = "NULL", *role = "NULL", + *cap = "NULL", *server = NULL, *ucuser, *signature = NULL, + *data = NULL, *datalen = NULL; + X509_EXTENSION *ex; + ASN1_STRING *asn1str; + time_t now, time1_time = 0, time2_time = 0, + uctime1_time, uctime2_time; + + time(&now); + + uctime1_time = + GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notBefore(usercert))); + uctime2_time = + GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notAfter(usercert))); + ucuser = + X509_NAME_oneline(X509_get_subject_name(usercert), NULL, 0); + + for (i = 0; i < X509_get_ext_count(cert); ++i) + { + ex = X509_get_ext(cert, i); + + OBJ_obj2txt(s, sizeof(s), X509_EXTENSION_get_object(ex), 1); + + if (strcmp(s, GRST_VOMS_OID) == 0) /* a VOMS extension */ + { + asn1str = X509_EXTENSION_get_data(ex); + charstr = (char *) malloc(ASN1_STRING_length(asn1str) + 1); + memcpy(charstr, ASN1_STRING_data(asn1str), + ASN1_STRING_length(asn1str)); + charstr[ASN1_STRING_length(asn1str)] = '\0'; + + siglen = -1; + + if ((sscanf(charstr, "SIGLEN:%u", &siglen) != 1) || + (siglen == -1) || + ((p = index(charstr, '\n')) == NULL)) + { + free(charstr); + continue; + } + + ++p; + + if (strncmp(p, "SIGNATURE:", sizeof("SIGNATURE:") - 1) != 0) + { + free(charstr); + continue; + } + + signature = &p[sizeof("SIGNATURE:") - 1]; + + p = &p[siglen + sizeof("SIGNATURE:") - 1]; + data = p; + + /* nasty pointer arithmetic! */ + dataoffset = (unsigned int) ((long) data - (long) charstr); + datalength = (unsigned int) + (ASN1_STRING_length(asn1str) - dataoffset); + + if (datalength <= 0) + { + free(charstr); + continue; + } + + while (1) + { + if (strncmp(p, "USER:", sizeof("USER:") - 1) == 0) + { + p = &p[sizeof("USER:") - 1]; + while ((*p != '\n') && (*p != '\0') && (*p <= ' ')) ++p; + user = p; + p = index(p, '\n'); + if (p == NULL) break; + *p = '\0'; + ++p; + } + else if (strncmp(p, "TIME1:", sizeof("TIME1:") - 1) == 0) + { + p = &p[sizeof("TIME1:") - 1]; + while ((*p != '\n') && (*p != '\0') && (*p <= ' ')) ++p; + time1 = p; + p = index(p, '\n'); + if (p != NULL) *p = '\0'; + + time1_time = GRSTasn1TimeToTimeT(time1); + if (time1_time < uctime1_time) time1_time = uctime1_time; + if (p == NULL) break; + ++p; + } + else if (strncmp(p, "TIME2:", sizeof("TIME2:") - 1) == 0) + { + p = &p[sizeof("TIME2:") - 1]; + while ((*p != '\n') && (*p != '\0') && (*p <= ' ')) ++p; + time2 = p; + p = index(p, '\n'); + if (p != NULL) *p = '\0'; + + time2_time = GRSTasn1TimeToTimeT(time2); + if (time2_time > uctime2_time) time2_time = uctime2_time; + if (p == NULL) break; + ++p; + } + else if (strncmp(p, "VO:", sizeof("VO:") - 1) == 0) + { + p = &p[sizeof("VO:") - 1]; + while ((*p != '\n') && (*p != '\0') && (*p <= ' ')) ++p; + vo = p; + + p = index(p, '\n'); + if (p == NULL) break; + *p = '\0'; + ++p; + } + else if (strncmp(p, "SERVER:", sizeof("SERVER:") - 1) == 0) + { + p = &p[sizeof("SERVER:") - 1]; + while ((*p != '\n') && (*p != '\0') && (*p <= ' ')) ++p; + server = p; + + p = index(p, '\n'); + if (p == NULL) break; + *p = '\0'; + ++p; + } + else if (strncmp(p, "DATALEN:", sizeof("DATALEN:") - 1) == 0) + { + p = &p[sizeof("DATALEN:") - 1]; + while ((*p != '\n') && (*p != '\0') && (*p <= ' ')) ++p; + datalen = p; + p = index(p, '\n'); + if (p == NULL) break; + *p = '\0'; + ++p; + break; + } + else /* not something we use */ + { + p = index(p, '\n'); + if (p == NULL) break; + *p = '\0'; + ++p; + } + } + + if ( + (now >= time1_time) && + (now <= time2_time) && + (signature != NULL) && + (data != NULL) && + (siglen > 0) && + (user != NULL) && + (ucuser != NULL) && + (strcmp(user, ucuser) == 0) && + (GRSTx509CheckVomsSig(signature, siglen, + &((ASN1_STRING_data(asn1str))[dataoffset]), + datalength, vomsdir, vo, + server) == GRST_RET_OK)) while (1) + { + if (strncmp(p, "GROUP:", sizeof("GROUP:") - 1) == 0) + { + p = &p[sizeof("GROUP:") - 1]; + while ((*p != '\n') && (*p != '\0') && (*p <= ' ')) ++p; + group = p; + role = "NULL"; + cap = "NULL"; + + p = index(p, '\n'); + if (p == NULL) break; + *p = '\0'; + ++p; + } + else if (strncmp(p, "ROLE:", sizeof("ROLE:") - 1) == 0) + { + p = &p[sizeof("ROLE:") - 1]; + while ((*p != '\n') && (*p != '\0') && (*p <= ' ')) ++p; + role = p; + + p = index(p, '\n'); + if (p == NULL) break; + *p = '\0'; + ++p; + } + else if (strncmp(p, "CAP:", sizeof("CAP:") - 1) == 0) + { + p = &p[sizeof("CAP:") - 1]; + while ((*p != '\n') && (*p != '\0') && (*p <= ' ')) ++p; + cap = p; + + p = index(p, '\n'); + if (p != NULL) *p = '\0'; + + if (*lastcred < maxcreds - 1) + { + ++(*lastcred); + + if ((strcmp(role, "NULL") == 0) && + (strcmp(cap , "NULL") == 0)) + snprintf(&creds[*lastcred * (credlen + 1)], credlen+1, + "VOMS %010lu %010lu 0 /%s%s", + time1_time, time2_time, vo, group); + else if ((strcmp(role, "NULL") != 0) && + (strcmp(cap , "NULL") == 0)) + snprintf(&creds[*lastcred * (credlen + 1)], credlen+1, + "VOMS %010lu %010lu 0 /%s%s/Role=%s", + time1_time, time2_time, vo, group, role); + else if ((strcmp(role, "NULL") == 0) && + (strcmp(cap , "NULL") != 0)) + snprintf(&creds[*lastcred * (credlen + 1)], credlen+1, + "VOMS %010lu %010lu 0 /%s%s/Capability=%s", + time1_time, time2_time, vo, group, cap); + else + snprintf(&creds[*lastcred * (credlen + 1)], credlen+1, + "VOMS %010lu %010lu 0 /%s%s/Role=%s/Capability=%s", + time1_time, time2_time, vo, group, role, cap); + } + + if (p == NULL) break; + ++p; + } + else /* not something we use */ + { + p = index(p, '\n'); + if (p == NULL) break; + *p = '\0'; + ++p; + } + } + + free(charstr); + } + } + + return GRST_RET_OK; +} + +/// Turn a Compact Cred line into a GRSTgaclCred object +/** + * Returns pointer to created GRSTgaclCred or NULL or failure. + */ + +GRSTgaclCred *GRSTx509CompactToCred(char *grst_cred) +{ + int delegation; + char *p; + time_t now, notbefore, notafter; + GRSTgaclCred *cred = NULL; + + time(&now); + + if (grst_cred == NULL) return NULL; /* just in case */ + + if (strncmp(grst_cred, "X509USER ", 9) == 0) + { + if ((sscanf(grst_cred, "X509USER %lu %lu %d", + ¬before, ¬after, &delegation) == 3) + && (now >= notbefore) + && (now <= notafter) + && (p = index(grst_cred, ' ')) + && (p = index(++p, ' ')) + && (p = index(++p, ' ')) + && (p = index(++p, ' '))) + { + cred = GRSTgaclCredNew("person"); + GRSTgaclCredSetDelegation(cred, delegation); + GRSTgaclCredAddValue(cred, "dn", &p[1]); + } + + return cred; + } + + if (strncmp(grst_cred, "VOMS ", 5) == 0) + { + if ((sscanf(grst_cred, "VOMS %lu %lu", + ¬before, ¬after, &delegation) == 3) + && (now >= notbefore) + && (now <= notafter) + && (p = index(grst_cred, ' ')) + && (p = index(++p, ' ')) + && (p = index(++p, ' ')) + && (p = index(++p, ' '))) + { + /* include /VO/group/subgroup/Role=role/Capability=cap */ + + if (*p != '/') return NULL; /* must begin with / */ + + cred = GRSTgaclCredNew("voms"); + GRSTgaclCredSetDelegation(cred, delegation); + GRSTgaclCredAddValue(cred, "fqan", p); + } + + return cred; + } + + return NULL; /* dont recognise this credential type */ +} + +/// Get the credentials in an X509 cert/GSI proxy, including any VOMS +/** + * Credentials are placed in Compact Creds string array at *creds. + * + * Function returns GRST_RET_OK on success, or GRST_RET_FAILED if + * some inconsistency found in certificate. + */ + +int GRSTx509CompactCreds(int *lastcred, int maxcreds, size_t credlen, + char *creds, STACK_OF(X509) *certstack, char *vomsdir) +{ + int i, j, delegation = 0; + char credtemp[credlen+1]; + X509 *cert, *usercert = NULL, *gsiproxycert = NULL; + + *lastcred = -1; + + for (i = sk_X509_num(certstack) - 1; i >= 0; --i) + { + cert = sk_X509_value(certstack, i); + + if (usercert != NULL) + { /* found a (GSI proxy) cert after the user cert */ + gsiproxycert = cert; + ++delegation; + } + + if ((usercert == NULL) && + (i < sk_X509_num(certstack) - 1) && + (GRSTx509IsCA(cert) != GRST_RET_OK)) usercert = cert; + /* found the 1st non-CA cert */ + } + + if ((usercert == NULL) /* if no usercert ("EEC"), we're not interested */ + || + (snprintf(credtemp, credlen+1, "X509USER %010lu %010lu %d %s", + GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notBefore(usercert))), + GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notAfter(usercert))), + delegation, + X509_NAME_oneline(X509_get_subject_name(usercert), NULL, 0)) >= credlen+1) + || + (*lastcred >= maxcreds-1)) + { + *lastcred = -1; /* just in case the caller looks at it */ + return GRST_RET_FAILED; /* tell caller that things didn't work out */ + } + + ++(*lastcred); + strcpy(&creds[*lastcred * (credlen + 1)], credtemp); + + if ((gsiproxycert != NULL) + && + (snprintf(credtemp, credlen+1, "GSIPROXY %010lu %010lu %d %s", + GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notBefore(gsiproxycert))), + GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notAfter(gsiproxycert))), + delegation, + X509_NAME_oneline(X509_get_subject_name(gsiproxycert), NULL, 0)) < credlen+1) + && + (*lastcred < maxcreds-1)) + { + ++(*lastcred); + strcpy(&creds[*lastcred * (credlen + 1)], credtemp); + + GRSTx509GetVomsCreds(lastcred, maxcreds, credlen, creds, + gsiproxycert, usercert, vomsdir); + } + + return GRST_RET_OK; +} diff --git a/org.gridsite.core/src/htcp b/org.gridsite.core/src/htcp new file mode 100644 index 0000000000000000000000000000000000000000..77f51e31d4a047ffbf73d5ea4567d09c918bac2d GIT binary patch literal 20971 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 literal 0 HcmV?d00001 diff --git a/org.gridsite.core/src/htcp.c b/org.gridsite.core/src/htcp.c new file mode 100644 index 0000000..8f0ba7f --- /dev/null +++ b/org.gridsite.core/src/htcp.c @@ -0,0 +1,1127 @@ +/* + Copyright (c) 2002-3, Andrew McNab, University of Manchester + All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + o Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + o Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +/*------------------------------------------------------------------------* + * For more about GridSite: http://www.gridpp.ac.uk/gridsite/ * + *------------------------------------------------------------------------*/ + +#ifndef VERSION +#define VERSION "0.0.0" +#endif + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* deal with older versions of libcurl and curl.h */ + +#ifndef CURLOPT_WRITEDATA +#define CURLOPT_WRITEDATA CURLOPT_FILE +#endif + +#ifndef CURLOPT_READDATA +#define CURLOPT_READDATA CURLOPT_FILE +#endif + +#ifndef CURLE_HTTP_RETURNED_ERROR +#define CURLE_HTTP_RETURNED_ERROR CURLE_HTTP_NOT_FOUND +#endif + +#define HTCP_GET 1 +#define HTCP_PUT 2 +#define HTCP_DELETE 3 +#define HTCP_LIST 4 +#define HTCP_LONGLIST 5 +#define HTCP_MKDIR 6 + +struct grst_stream_data { char *source; + char *destination; + int ishttps; + int method; + FILE *fp; + char *cert; + char *key; + char *capath; + char *useragent; + char *errorbuf; + int noverify; + int anonymous; + int verbose; } ; + +struct grst_index_blob { char *text; + size_t used; + size_t allocated; } ; + +struct grst_dir_list { char *filename; + size_t length; + int length_set; + time_t modified; + int modified_set; } ; + +struct grst_header_data { int retcode; + char *location; + size_t length; + int length_set; + time_t modified; + int modified_set; } ; + +size_t headers_callback(void *ptr, size_t size, size_t nmemb, void *p) +/* Find the values of the return code, Content-Length, Last-Modified + and Location headers */ +{ + float f; + char *s; + size_t realsize; + struct tm modified_tm; + struct grst_header_data *header_data; + + header_data = (struct grst_header_data *) p; + realsize = size * nmemb; + s = malloc(realsize + 1); + memcpy(s, ptr, realsize); + s[realsize] = '\0'; + + if (sscanf(s, "Content-Length: %d", &(header_data->length)) == 1) + header_data->length_set = 1; + else if (sscanf(s, "HTTP/%f %d ", &f, &(header_data->retcode)) == 2) ; + else if (strncmp(s, "Location: ", 10) == 0) + header_data->location = strdup(&s[10]); + else if (strncmp(s, "Last-Modified: ", 15) == 0) + { + /* follow RFC 2616: first try RFC 822 (kosher), then RFC 850 and + asctime() formats too. Must be GMT whatever the format. */ + + if (strptime(&s[15], "%a, %d %b %Y %T GMT", &modified_tm) != NULL) + { + header_data->modified = mktime(&modified_tm); + header_data->modified_set = 1; + } + else if (strptime(&s[15], "%a, %d-%b-%y %T GMT", &modified_tm) != NULL) + { + header_data->modified = mktime(&modified_tm); + header_data->modified_set = 1; + } + else if (strptime(&s[15], "%a %b %d %T %Y", &modified_tm) != NULL) + { + header_data->modified = mktime(&modified_tm); + header_data->modified_set = 1; + } + } + + free(s); + return realsize; +} + +int set_std_opts(CURL *easyhandle, struct grst_stream_data *common_data) +{ + struct stat statbuf; + + curl_easy_setopt(easyhandle, CURLOPT_FOLLOWLOCATION, 0); + + if ((common_data->cert != NULL) && (common_data->key != NULL)) + { + curl_easy_setopt(easyhandle, CURLOPT_SSLENGINE, NULL); + curl_easy_setopt(easyhandle, CURLOPT_SSLCERTTYPE, "PEM"); + curl_easy_setopt(easyhandle, CURLOPT_SSLCERT, common_data->cert); + curl_easy_setopt(easyhandle, CURLOPT_SSLKEY, common_data->key); + } + else + { + curl_easy_setopt(easyhandle, CURLOPT_SSLENGINE, "RSA"); + curl_easy_setopt(easyhandle, CURLOPT_SSLCERTTYPE, "ENG"); + } + + if (common_data->capath != NULL) + { +#if (LIBCURL_VERSION_NUM >= 0x070908) + if ((stat(common_data->capath, &statbuf) == 0) && + S_ISDIR(statbuf.st_mode)) + curl_easy_setopt(easyhandle, CURLOPT_CAPATH, common_data->capath); + else +#endif + curl_easy_setopt(easyhandle, CURLOPT_CAINFO, common_data->capath); + } + + if (common_data->noverify) + curl_easy_setopt(easyhandle, CURLOPT_SSL_VERIFYHOST, 0); + else curl_easy_setopt(easyhandle, CURLOPT_SSL_VERIFYHOST, 2); + + return 1; +} + +int do_copies(char *sources[], char *destination, + struct grst_stream_data *common_data) +{ + char *p, *thisdestination; + int isrc, anyerror = 0, thiserror, isdirdest; + CURL *easyhandle; + struct stat statbuf; + struct grst_header_data header_data; + + easyhandle = curl_easy_init(); + + curl_easy_setopt(easyhandle, CURLOPT_USERAGENT, common_data->useragent); + if (common_data->verbose > 1) + curl_easy_setopt(easyhandle, CURLOPT_VERBOSE, 1); + + curl_easy_setopt(easyhandle, CURLOPT_HEADERFUNCTION, headers_callback); + curl_easy_setopt(easyhandle, CURLOPT_WRITEHEADER, &header_data); + + set_std_opts(easyhandle, common_data); + + curl_easy_setopt(easyhandle, CURLOPT_ERRORBUFFER, common_data->errorbuf); + + if (destination[strlen(destination) - 1] != '/') + { + isdirdest = 0; + thisdestination = destination; + } + else isdirdest = 1; + + for (isrc=0; sources[isrc] != NULL; ++isrc) + { + if (isdirdest) + { + p = rindex(sources[isrc], '/'); + if (p == NULL) p = sources[isrc]; + else p++; + + asprintf(&thisdestination, "%s%s", destination, p); + } + + if (common_data->verbose > 0) + fprintf(stderr, "%s -> %s\n", sources[isrc], thisdestination); + + if (common_data->method == HTCP_GET) + { + common_data->fp = fopen(thisdestination, "w"); + if (common_data->fp == NULL) + { + fprintf(stderr,"... failed to open destination source file %s\n", + thisdestination); + anyerror = 99; + if (isdirdest) free(thisdestination); + continue; + } + + curl_easy_setopt(easyhandle, CURLOPT_WRITEDATA, common_data->fp); + curl_easy_setopt(easyhandle, CURLOPT_URL, sources[isrc]); + } + else if (common_data->method == HTCP_PUT) + { + if (stat(sources[isrc], &statbuf) != 0) + { + fprintf(stderr, "... source file %s not found\n", sources[isrc]); + anyerror = 99; + if (isdirdest) free(thisdestination); + continue; + } + + common_data->fp = fopen(sources[isrc], "r"); + if (common_data->fp == NULL) + { + fprintf(stderr, "... failed to open source file %s\n", + sources[isrc]); + anyerror = 99; + if (isdirdest) free(thisdestination); + continue; + } + + curl_easy_setopt(easyhandle, CURLOPT_READDATA, common_data->fp); + curl_easy_setopt(easyhandle, CURLOPT_URL, thisdestination); + curl_easy_setopt(easyhandle, CURLOPT_INFILESIZE, statbuf.st_size); + curl_easy_setopt(easyhandle, CURLOPT_UPLOAD, 1); + } + + header_data.retcode = 0; + thiserror = curl_easy_perform(easyhandle); + + if ((thiserror != 0) || + (header_data.retcode < 200) || + (header_data.retcode >= 300)) + { + fprintf(stderr, "... curl error: %s (%d), HTTP error: %d\n", + common_data->errorbuf, thiserror, header_data.retcode); + + if (thiserror != 0) anyerror = thiserror; + else anyerror = header_data.retcode; + } + else if (common_data->verbose > 0) + fprintf(stderr, "... OK (%d)\n", header_data.retcode); + + fclose(common_data->fp); + + if (isdirdest) free(thisdestination); + } + + curl_easy_cleanup(easyhandle); + + return anyerror; +} + +int do_deletes(char *sources[], struct grst_stream_data *common_data) +{ + int isrc, anyerror = 0, thiserror; + CURL *easyhandle; + struct grst_header_data header_data; + + easyhandle = curl_easy_init(); + + curl_easy_setopt(easyhandle, CURLOPT_USERAGENT, common_data->useragent); + if (common_data->verbose > 1) + curl_easy_setopt(easyhandle, CURLOPT_VERBOSE, 1); + + curl_easy_setopt(easyhandle, CURLOPT_HEADERFUNCTION, headers_callback); + curl_easy_setopt(easyhandle, CURLOPT_WRITEHEADER, &header_data); + + curl_easy_setopt(easyhandle, CURLOPT_ERRORBUFFER, common_data->errorbuf); + curl_easy_setopt(easyhandle, CURLOPT_CUSTOMREQUEST, "DELETE"); + curl_easy_setopt(easyhandle, CURLOPT_NOBODY, 1); + + set_std_opts(easyhandle, common_data); + + for (isrc=0; sources[isrc] != NULL; ++isrc) + { + if (common_data->verbose > 0) + fprintf(stderr, "Deleting %s\n", sources[isrc]); + + curl_easy_setopt(easyhandle, CURLOPT_URL, sources[isrc]); + + header_data.retcode = 0; + thiserror = curl_easy_perform(easyhandle); + + if ((thiserror != 0) || + (header_data.retcode < 200) || + (header_data.retcode >= 300)) + { + fprintf(stderr, "... curl error: %s (%d), HTTP error: %d\n", + common_data->errorbuf, thiserror, header_data.retcode); + + if (thiserror != 0) anyerror = thiserror; + else anyerror = header_data.retcode; + } + else if (common_data->verbose > 0) + fprintf(stderr, "... OK (%d)\n", header_data.retcode); + } + + curl_easy_cleanup(easyhandle); + + return anyerror; +} + +int do_mkdirs(char *sources[], struct grst_stream_data *common_data) +{ + int isrc, anyerror = 0, thiserror; + CURL *easyhandle; + struct grst_header_data header_data; + + easyhandle = curl_easy_init(); + + curl_easy_setopt(easyhandle, CURLOPT_USERAGENT, common_data->useragent); + if (common_data->verbose > 1) + curl_easy_setopt(easyhandle, CURLOPT_VERBOSE, 1); + + curl_easy_setopt(easyhandle, CURLOPT_HEADERFUNCTION, headers_callback); + curl_easy_setopt(easyhandle, CURLOPT_WRITEHEADER, &header_data); + + curl_easy_setopt(easyhandle, CURLOPT_ERRORBUFFER, common_data->errorbuf); + curl_easy_setopt(easyhandle, CURLOPT_CUSTOMREQUEST, "PUT"); + curl_easy_setopt(easyhandle, CURLOPT_NOBODY, 1); + + set_std_opts(easyhandle, common_data); + + for (isrc=0; sources[isrc] != NULL; ++isrc) + { + if (common_data->verbose > 0) + fprintf(stderr, "Make directory %s\n", sources[isrc]); + + curl_easy_setopt(easyhandle, CURLOPT_URL, sources[isrc]); + + header_data.retcode = 0; + thiserror = curl_easy_perform(easyhandle); + + if ((thiserror != 0) || + (header_data.retcode < 200) || + (header_data.retcode >= 300)) + { + fprintf(stderr, "... curl error: %s (%d), HTTP error: %d\n", + common_data->errorbuf, thiserror, header_data.retcode); + + if (thiserror != 0) anyerror = thiserror; + else anyerror = header_data.retcode; + } + else if (common_data->verbose > 0) + fprintf(stderr, "... OK (%d)\n", header_data.retcode); + } + + curl_easy_cleanup(easyhandle); + + return anyerror; +} + +size_t rawindex_callback(void *ptr, size_t size, size_t nmemb, void *data) +{ + if ( ((struct grst_index_blob *) data)->used + size * nmemb >= + ((struct grst_index_blob *) data)->allocated ) + { + ((struct grst_index_blob *) data)->allocated = + ((struct grst_index_blob *) data)->used + size * nmemb + 4096; + + ((struct grst_index_blob *) data)->text = + realloc( ((struct grst_index_blob *) data)->text, + ((struct grst_index_blob *) data)->allocated ); + } + + memcpy( &( ((struct grst_index_blob *) + data)->text[((struct grst_index_blob *) data)->used] ), + ptr, size * nmemb); + + ((struct grst_index_blob *) data)->used += size * nmemb; + + return size * nmemb; +} + +char *canonicalise(char *link, char *source) +{ + int i, j, srclen; + char *s; + + srclen = strlen(source); + + if ((strncmp(link, "https://", 8) == 0) || + (strncmp(link, "http://", 7) == 0)) + { + if (strncmp(link, source, srclen) != 0) return NULL; /* other site */ + + if (link[srclen] == '\0') return NULL; /* we dont self-link! */ + + for (i=0; link[srclen + i] != '\0'; ++i) + if (link[srclen + i] == '/') + { + if (link[srclen + i + 1] != '\0') return NULL; /* no subdirs */ + else return strdup(&link[srclen]); /* resolves to this dir */ + } + } + else if (link[0] != '/') /* relative link - need to check for subsubdirs */ + { + for (i=0; link[i] != '\0'; ++i) + if ((link[i] == '/') && (link[i+1] != '\0')) return NULL; + + s = strdup(link); + + for (i=0; s[i] != '\0'; ++i) + if (s[i] == '#') + { + s[i] = '\0'; + break; + } + + return s; + } + + /* absolute link on this server, starting / */ + + for (i=8; source[i] != '\0'; ++i) if (source[i] == '/') break; + + if (strncmp(link, &source[i], srclen - i) != 0) return NULL; + + for (j = srclen - i; link[j] != '\0'; ++j) + if ((link[j] == '/') && (link[j+1] != '\0')) return NULL; + + s = strdup(&link[srclen - i]); + + for (i=0; s[i] != '\0'; ++i) + if (s[i] == '#') + { + s[i] = '\0'; + break; + } + + if (s[0] == '\0') /* on second thoughts... */ + { + free(s); + return NULL; + } + + return s; +} + +int grst_dir_list_cmp(const void *a, const void *b) +{ + return strcmp( ((struct grst_dir_list *) a)->filename, + ((struct grst_dir_list *) b)->filename); +} + +struct grst_dir_list *index_to_dir_list(char *text, char *source) +{ + int taglevel = 0, wordnew = 1, i, namestart, used = 0, + allocated = 256; + char *p, *s; + struct grst_dir_list *list; + + list = (struct grst_dir_list *) + malloc(allocated * sizeof(struct grst_dir_list)); + + list[0].filename = NULL; + list[0].length = 0; + list[0].length_set = 0; + list[0].modified = 0; + list[0].modified_set = 0; + + for (p=text; *p != '\0'; ++p) + { + if (*p == '<') + { + ++taglevel; + + if ((taglevel == 1) && (list[used].filename != NULL)) + { + ++used; + if (used >= allocated) + { + allocated += 256; + list = (struct grst_dir_list *) + malloc(allocated * sizeof(struct grst_dir_list)); + } + + list[used].filename = NULL; + list[used].length = 0; + list[used].length_set = 0; + list[used].modified = 0; + list[used].modified_set = 0; + } + + wordnew = 1; + continue; + } + + if (*p == '>') + { + --taglevel; + wordnew = 1; + continue; + } + + if (isspace(*p)) + { + wordnew = 1; + continue; + } + + if ((wordnew) && (taglevel == 1)) + { + if (((*p == 'h') || (*p == 'H')) && + (strncasecmp(p, "href=", 5) == 0)) + { + if (p[5] == '"') { namestart = 6; + for (i=namestart; (p[i] != '\0') && + (p[i] != '"' ) && + (p[i] != '\n') && + (p[i] != '\t') && + (p[i] != '>' ) ; ++i) ; } + else { namestart = 5; + for (i=namestart; (p[i] != '\0') && + (p[i] != '"' ) && + (p[i] != ' ' ) && + (p[i] != '\n') && + (p[i] != '\t') && + (p[i] != ')' ) && + (p[i] != '>' ) ; ++i) ; } + if (i > namestart) + { + s = malloc(1 + i - namestart); + memcpy(s, &p[namestart], i - namestart); + s[i - namestart] = '\0'; + + list[used].filename = canonicalise(s, source); + free(s); + } + + p = &p[i-1]; /* -1 since continue results in ++i */ + continue; + } + + if (((*p == 'c') || (*p == 'C')) && + (strncasecmp(p, "content-length=", 15) == 0)) + { + list[used].length = 0; + list[used].length_set = 1; + + if (p[15] == '"') list[used].length = atoi(&p[16]); + else list[used].length = atoi(&p[15]); + + p = &p[15]; + continue; + } + + if (((*p == 'l') || (*p == 'L')) && + (strncasecmp(p, "last-modified=", 14) == 0)) + { + list[used].modified = 0; + list[used].modified_set = 1; + + if (p[14] == '"') list[used].modified = atoi(&p[15]); + else list[used].modified = atoi(&p[14]); + + p = &p[14]; + continue; + } + } + + wordnew = 0; + } + + qsort((void *) list, used, sizeof(struct grst_dir_list), grst_dir_list_cmp); + + return list; +} + +int do_listings(char *sources[], struct grst_stream_data *common_data, + int islonglist) +{ + int isrc, anyerror = 0, thiserror, i, isdir, ilast; + CURL *easyhandle; + const char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + char *s; + struct grst_index_blob rawindex; + struct grst_dir_list *list; + struct grst_header_data header_data; + struct tm modified_tm; + time_t now; + + time(&now); + + easyhandle = curl_easy_init(); + + curl_easy_setopt(easyhandle, CURLOPT_USERAGENT, common_data->useragent); + if (common_data->verbose > 1) + curl_easy_setopt(easyhandle, CURLOPT_VERBOSE, 1); + + curl_easy_setopt(easyhandle, CURLOPT_WRITEHEADER, &header_data); + curl_easy_setopt(easyhandle, CURLOPT_HEADERFUNCTION, headers_callback); + + curl_easy_setopt(easyhandle, CURLOPT_ERRORBUFFER, common_data->errorbuf); + + set_std_opts(easyhandle, common_data); + + for (isrc=0; sources[isrc] != NULL; ++isrc) + { + if (common_data->verbose > 0) + fprintf(stderr, "Listing %s\n", sources[isrc]); + + if (sources[1] != NULL) printf("\n%s:\n", sources[isrc]); + + curl_easy_setopt(easyhandle, CURLOPT_URL, sources[isrc]); + + if (sources[isrc][strlen(sources[isrc])-1] == '/') + { + isdir = 1; + curl_easy_setopt(easyhandle,CURLOPT_WRITEFUNCTION,rawindex_callback); + curl_easy_setopt(easyhandle,CURLOPT_WRITEDATA,(void *) &rawindex); + curl_easy_setopt(easyhandle,CURLOPT_NOBODY,0); + rawindex.text = NULL; + rawindex.used = 0; + rawindex.allocated = 0; + } + else + { + isdir = 0; + curl_easy_setopt(easyhandle, CURLOPT_WRITEFUNCTION, NULL); + curl_easy_setopt(easyhandle, CURLOPT_WRITEDATA, NULL); + curl_easy_setopt(easyhandle, CURLOPT_NOBODY, 1); + } + + header_data.length_set = 0; + header_data.modified_set = 0; + header_data.retcode = 0; + thiserror = curl_easy_perform(easyhandle); + + if ((thiserror != 0) || + (header_data.retcode < 200) || + (header_data.retcode >= 300)) + { + fprintf(stderr, "... curl error: %s (%d), HTTP error: %d\n", + common_data->errorbuf, thiserror, header_data.retcode); + + if (thiserror != 0) anyerror = thiserror; + else anyerror = header_data.retcode; + } + else if (isdir) + { + if (common_data->verbose > 0) + fprintf(stderr, "... OK (%d)\n", header_data.retcode); + + rawindex.text[rawindex.used] = '\0'; + + list = index_to_dir_list(rawindex.text, sources[isrc]); + ilast = -1; + + for (i=0; list[i].filename != NULL; ++i) + { + if (list[i].filename[0] == '.') continue; + + if (strncmp(list[i].filename, "mailto:", 7) == 0) continue; + + if ((ilast >= 0) && + (strcmp(list[i].filename, list[ilast].filename) == 0)) + continue; + ilast=i; + + if (islonglist) + { + if (!list[i].length_set || !list[i].modified_set) + { + curl_easy_setopt(easyhandle, CURLOPT_WRITEFUNCTION, + NULL); + curl_easy_setopt(easyhandle, CURLOPT_WRITEDATA, NULL); + curl_easy_setopt(easyhandle, CURLOPT_NOBODY, 1); + + asprintf(&s, "%s%s", sources[isrc], list[i].filename); + curl_easy_setopt(easyhandle, CURLOPT_URL, s); + + header_data.length_set = 0; + header_data.modified_set = 0; + header_data.retcode = 0; + thiserror = curl_easy_perform(easyhandle); + free(s); + + if ((thiserror == 0) && + (header_data.retcode >= 200) && + (header_data.retcode <= 299)) + { + if (header_data.length_set) + { + list[i].length_set = 1; + list[i].length = header_data.length; + } + + if (header_data.modified_set) + { + list[i].modified_set = 1; + list[i].modified = header_data.modified; + } + } + } + + if (list[i].length_set) printf("%10ld ", list[i].length); + else fputs(" ? ", stdout); + + if (list[i].modified_set) + { + localtime_r(&(list[i].modified), &modified_tm); + + if (list[i].modified < now - 15552000) + printf("%s %2d %4d ", + months[modified_tm.tm_mon], + modified_tm.tm_mday, + modified_tm.tm_year + 1900); + else printf("%s %2d %02d:%02d ", + months[modified_tm.tm_mon], + modified_tm.tm_mday, + modified_tm.tm_hour, + modified_tm.tm_min); + } + else fputs(" ? ? ? ", stdout); + } + + puts(list[i].filename); + } + } + else + { + if (islonglist) + { + printf("%10ld ", header_data.length); + + localtime_r(&(header_data.modified), &modified_tm); + + if (header_data.modified < now - 15552000) + printf("%s %2d %4d ", + months[modified_tm.tm_mon], + modified_tm.tm_mday, + modified_tm.tm_year + 1900); + else printf("%s %2d %02d:%02d ", + months[modified_tm.tm_mon], + modified_tm.tm_mday, + modified_tm.tm_hour, + modified_tm.tm_min); + } + + puts(sources[isrc]); + } + } + + curl_easy_cleanup(easyhandle); + + return anyerror; +} + +#if (LIBCURL_VERSION_NUM < 0x070908) +char *make_tmp_ca_roots(char *dir) +/* libcurl before 7.9.8 doesnt support CURLOPT_CAPATH and the directory, + so we make a temporary file with the concatenated CA root certs: that + is, all the files in that directory which end in .0 */ +{ + int ofd, ifd, c; + size_t size; + char tmp_ca_roots[] = "/tmp/.ca-roots-XXXXXX", buffer[4096], *s; + DIR *rootsDIR; + struct dirent *root_ent; + + if ((rootsDIR = opendir(dir)) == NULL) return NULL; + + if ((ofd = mkstemp(tmp_ca_roots)) == -1) + { + closedir(rootsDIR); + return NULL; + } + + while ((root_ent = readdir(rootsDIR)) != NULL) + { + if ((root_ent->d_name[0] != '.') && + (strlen(root_ent->d_name) > 2) && + (strncmp(&(root_ent->d_name[strlen(root_ent->d_name)-2]), + ".0", 2) == 0)) + { + asprintf(&s, "%s/%s", dir, root_ent->d_name); + ifd = open(s, O_RDONLY); + free(s); + + if (ifd != -1) + { + while ((size = read(ifd, buffer, sizeof(buffer))) > 0) + write(ofd, buffer, size); + close(ifd); + } + } + } + + closedir(rootsDIR); + + if (close(ofd) == 0) return strdup(tmp_ca_roots); + + unlink(tmp_ca_roots); /* try to clean up */ + + return NULL; +} +#endif + +void printsyntax(char *argv0) +{ + char *p; + + p = rindex(argv0, '/'); + if (p != NULL) ++p; + else p = argv0; + + fprintf(stderr, "%s [options] Source-URL[s] [Destination URL]\n" + "%s is one of a set of clients to fetch files or directory listings\n" +"from remote servers using HTTP or HTTPS, or to put or delete files or\n" +"directories onto remote servers using HTTPS. htcp is similar to scp(1)\n" +"but uses HTTP/HTTPS rather than ssh as its transfer protocol.\n" +"See the htcp(1) or http://www.gridpp.ac.uk/gridsite/ for details.\n" +"(Version: %s)\n", p, p, VERSION); +} + +int main(int argc, char *argv[]) +{ + char **sources, *destination = NULL, *executable, *p; + int c, i, option_index, anyerror; + struct stat statbuf; + struct grst_stream_data common_data; + struct passwd *userpasswd; + struct option long_options[] = { {"verbose", 0, 0, 'v'}, + {"cert", 1, 0, 0}, + {"key", 1, 0, 0}, + {"capath", 1, 0, 0}, + {"delete", 0, 0, 0}, + {"list", 0, 0, 0}, + {"long-list", 0, 0, 0}, + {"mkdir", 0, 0, 0}, + {"no-verify", 0, 0, 0}, + {"anon", 0, 0, 0}, +// {"streams", 1, 0, 0}, +// {"blocksize", 1, 0, 0}, +// {"recursive", 0, 0, 0}, + {0, 0, 0, 0} }; + +#if (LIBCURL_VERSION_NUM < 0x070908) + char *tmp_ca_roots = NULL; +#endif + + if (argc == 1) + { + printsyntax(argv[0]); + return 0; + } + + common_data.cert = NULL; + common_data.key = NULL; + common_data.capath = NULL; + common_data.method = 0; + common_data.errorbuf = malloc(CURL_ERROR_SIZE); + asprintf(&(common_data.useragent), + "htcp/%s (http://www.gridpp.ac.uk/gridsite/)", VERSION); + common_data.verbose = 0; + common_data.noverify = 0; + common_data.anonymous = 0; + + while (1) + { + option_index = 0; + + c = getopt_long(argc, argv, "v", long_options, &option_index); + + if (c == -1) break; + else if (c == 0) + { + if (option_index == 1) common_data.cert = optarg; + else if (option_index == 2) common_data.key = optarg; + else if (option_index == 3) common_data.capath = optarg; + else if (option_index == 4) common_data.method = HTCP_DELETE; + else if (option_index == 5) common_data.method = HTCP_LIST; + else if (option_index == 6) common_data.method = HTCP_LONGLIST; + else if (option_index == 7) common_data.method = HTCP_MKDIR; + else if (option_index == 8) common_data.noverify = 1; + else if (option_index == 9) common_data.anonymous = 1; + } + else if (c == 'v') ++(common_data.verbose); + } + + if (common_data.verbose > 0) + { + p = rindex(argv[0], '/'); + if (p != NULL) ++p; + else p = argv[0]; + fprintf(stderr, "%s version %s\n", p, VERSION); + } + + if (common_data.anonymous) /* prevent any use of user certs */ + { + common_data.cert = NULL; + common_data.key = NULL; + } + else if ((common_data.cert == NULL) && (common_data.key != NULL)) + common_data.cert = common_data.key; + else if ((common_data.cert != NULL) && (common_data.key == NULL)) + common_data.key = common_data.cert; + else if ((common_data.cert == NULL) && (common_data.key == NULL)) + { + common_data.cert = getenv("X509_USER_PROXY"); + if (common_data.cert != NULL) common_data.key = common_data.cert; + else + { + asprintf(&(common_data.cert), "/tmp/x509up_u%d", geteuid()); + + /* one fine day, we will check the proxy file for expiry too ... */ + + if (stat(common_data.cert, &statbuf) == 0) + common_data.key = common_data.cert; + else + { + common_data.cert = getenv("X509_USER_CERT"); + common_data.key = getenv("X509_USER_KEY"); + + userpasswd = getpwuid(geteuid()); + + if ((common_data.cert == NULL) && + (userpasswd != NULL) && + (userpasswd->pw_dir != NULL)) + asprintf(&(common_data.cert), "%s/.globus/usercert.pem", + userpasswd->pw_dir); + + if ((common_data.key == NULL) && + (userpasswd != NULL) && + (userpasswd->pw_dir != NULL)) + asprintf(&(common_data.key), "%s/.globus/userkey.pem", + userpasswd->pw_dir); + } + } + } + + if (common_data.capath == NULL) common_data.capath = getenv("X509_CERT_DIR"); + + if (common_data.capath == NULL) + common_data.capath = "/etc/grid-security/certificates"; + +#if (LIBCURL_VERSION_NUM < 0x070908) + /* libcurl before 7.9.8 doesnt support CURLOPT_CAPATH and the directory */ + + if ((common_data.capath != NULL) && + (stat(common_data.capath, &statbuf) == 0) && S_ISDIR(statbuf.st_mode)) + { + tmp_ca_roots = make_tmp_ca_roots(common_data.capath); + common_data.capath = tmp_ca_roots; + } +#endif + + executable = rindex(argv[0], '/'); + if (executable != NULL) executable++; + else executable = argv[0]; + + if (common_data.method == 0) /* command-line options override exec name */ + { + if (strcmp(executable,"htls")==0) common_data.method=HTCP_LIST; + else if (strcmp(executable,"htll")==0) common_data.method=HTCP_LONGLIST; + else if (strcmp(executable,"htrm")==0) common_data.method=HTCP_DELETE; + else if (strcmp(executable,"htmkdir")==0) common_data.method=HTCP_MKDIR; + } + + if ((common_data.method == HTCP_DELETE) || + (common_data.method == HTCP_LIST) || + (common_data.method == HTCP_MKDIR) || + (common_data.method == HTCP_LONGLIST)) + { + if (optind >= argc) + { + fprintf(stderr, "Must give at least 1 non-option argument\n\n"); + printsyntax(argv[0]); + return CURLE_URL_MALFORMAT; + } + + sources = (char **) malloc(sizeof(char *) * (1 + argc - optind)); + for (i=0; i < argc - optind; ++i) + { + sources[i] = argv[optind + i]; + + if ((common_data.method == HTCP_MKDIR) && + (sources[i][strlen(sources[i])-1] != '/')) + { + fprintf(stderr, "Argument \"%s\" is not a " + "directory URL (no trailing /)\n\n", sources[i]); + printsyntax(argv[0]); + return CURLE_URL_MALFORMAT; + } + } + + sources[i] = NULL; + + if (common_data.method == HTCP_DELETE) + anyerror = do_deletes(sources, &common_data); + else if (common_data.method == HTCP_MKDIR) + anyerror = do_mkdirs(sources, &common_data); + else if (common_data.method == HTCP_LONGLIST) + anyerror = do_listings(sources, &common_data, 1); + else anyerror = do_listings(sources, &common_data, 0); + + if (anyerror > 99) anyerror = CURLE_HTTP_RETURNED_ERROR; + + return anyerror; + } + + if (optind >= argc - 1) + { + fputs("Must give at least 2 non-option arguments\n\n", stderr); + printsyntax(argv[0]); + return CURLE_URL_MALFORMAT; + } + + sources = (char **) malloc(sizeof(char *) * (argc - optind)); + + for (i=0; i < (argc - optind - 1); ++i) + { + if (strncmp(argv[optind + i], "file:", 5) == 0) + sources[i] = &argv[optind + i][5]; + else sources[i] = argv[optind + i]; + + if (sources[i][0] == '\0') + { + fprintf(stderr, "Source argument %d is empty\n\n", i + 1); + printsyntax(argv[0]); + return CURLE_URL_MALFORMAT; + } + } + + sources[i] = NULL; + + if (strncmp(argv[optind + i], "file:", 5) == 0) + destination = &argv[optind + i][5]; + else destination = argv[optind + i]; + + if (destination[0] == '\0') + { + fputs("Destination argument is empty\n\n", stderr); + printsyntax(argv[0]); + return CURLE_URL_MALFORMAT; + } + + if ((argc - optind > 2) && (destination[strlen(destination)-1] != '/')) + { + fputs("For multiple sources, destination " + "must be a directory (end in /)\n\n", stderr); + printsyntax(argv[0]); + return CURLE_URL_MALFORMAT; + } + + if ((strncmp(destination, "http://", 7) == 0) || + (strncmp(destination, "https://", 8) == 0)) + common_data.method = HTCP_PUT; + else common_data.method = HTCP_GET; + + for (i=0; sources[i] != NULL; ++i) + { + if ((common_data.method == HTCP_PUT) && + ((strncmp(sources[i], "http://", 7) == 0) || + (strncmp(sources[i], "https://", 8) == 0))) + { + fputs("Cannot have both source and destination remote\n\n",stderr); + printsyntax(argv[0]); + return CURLE_URL_MALFORMAT; + } + + if ((common_data.method == HTCP_GET) && + ((strncmp(sources[i], "http://", 7) != 0) && + (strncmp(sources[i], "https://", 8) != 0))) + { + fputs("Cannot have both source and " + "destination local (for now)\n\n",stderr); + printsyntax(argv[0]); + return CURLE_URL_MALFORMAT; + } + } + + anyerror = do_copies(sources, destination, &common_data); + if (anyerror > 99) anyerror = CURLE_HTTP_RETURNED_ERROR; + + return anyerror; +} diff --git a/org.gridsite.core/src/mod_gridsite.c b/org.gridsite.core/src/mod_gridsite.c new file mode 100644 index 0000000..05ae8ff --- /dev/null +++ b/org.gridsite.core/src/mod_gridsite.c @@ -0,0 +1,1853 @@ +/* + Copyright (c) 2003-4, Andrew McNab, University of Manchester + All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + o Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + o Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +/*---------------------------------------------------------------------------* + * This program is part of GridSite: http://www.gridpp.ac.uk/gridsite/ * + *---------------------------------------------------------------------------*/ + +#ifndef VERSION +#define VERSION "x.x.x" +#endif + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "mod_ssl-private.h" + +#include "gridsite.h" + +#ifndef UNSET +#define UNSET -1 +#endif + +module AP_MODULE_DECLARE_DATA gridsite_module; + +typedef struct +{ + int auth; + int envs; + int format; + int indexes; + char *indexheader; + int gridsitelink; + char *adminfile; + char *adminuri; + char *helpuri; + char *dnlists; + char *dnlistsuri; + char *adminlist; + int gsiproxylimit; + char *unzip; + char *methods; + char *editable; + char *headfile; + char *footfile; +} mod_gridsite_cfg; /* per-directory config choices */ + +char *make_admin_footer(request_rec *r, mod_gridsite_cfg *conf, + int isdirectory) +/* + make string holding last modified text and admin links +*/ +{ + char *out, *https, *p, *dn = NULL, *file = NULL, *permstr = NULL, + *temp, modified[99], *dir_uri, *grst_cred_0 = NULL; + GRSTgaclPerm perm = GRST_PERM_NONE; + struct tm mtime_tm; + time_t mtime_time; + + https = (char *) apr_table_get(r->subprocess_env, "HTTPS"); + + dir_uri = apr_pstrdup(r->pool, r->uri); + p = rindex(dir_uri, '/'); + + if (p == NULL) return ""; + + file = apr_pstrdup(r->pool, &p[1]); + p[1] = '\0'; + /* dir_uri always gets both a leading and a trailing slash */ + + out = apr_pstrdup(r->pool, "

\n"); + + if (!isdirectory) + { + mtime_time = apr_time_sec(r->finfo.mtime); + + localtime_r(&mtime_time, &mtime_tm); + strftime(modified, sizeof(modified), + "%a %e %B %Y", &mtime_tm); + temp = apr_psprintf(r->pool,"


Last modified %s\n", modified); + out = apr_pstrcat(r->pool, out, temp, NULL); + + if ((conf->adminuri != NULL) && + (conf->adminuri[0] != '\0') && + (conf->adminfile != NULL) && + (conf->adminfile[0] != '\0') && + (strncmp(file, GRST_HIST_PREFIX, sizeof(GRST_HIST_PREFIX)-1) != 0)) + { + temp = apr_psprintf(r->pool, + ". " + "View page history\n", + conf->adminfile, file); + out = apr_pstrcat(r->pool, out, temp, NULL); + } + } + + out = apr_pstrcat(r->pool, out, "
", NULL); + + if (r->connection->notes != NULL) + grst_cred_0 = (char *) + apr_table_get(r->connection->notes, "GRST_CRED_0"); + + if ((grst_cred_0 != NULL) && + (strncmp(grst_cred_0, "X509USER ", sizeof("X509USER")) == 0)) + { + p = index(grst_cred_0, ' '); + if (p != NULL) + { + p = index(++p, ' '); + if (p != NULL) + { + p = index(++p, ' '); + if (p != NULL) + { + p = index(++p, ' '); + if (p != NULL) dn = p; + } + } + } + } + + if (dn != NULL) + { + temp = apr_psprintf(r->pool, "You are %s
\n", dn); + out = apr_pstrcat(r->pool, out, temp, NULL); + + if (r->notes != NULL) + permstr = (char *) apr_table_get(r->notes, "GRST_PERM"); + + if ((permstr != NULL) && + (conf->adminuri != NULL) && + (conf->adminuri[0] != '\0') && + (conf->adminfile != NULL) && + (conf->adminfile[0] != '\0')) + { + sscanf(permstr, "%d", &perm); + + if (!isdirectory && + GRSTgaclPermHasWrite(perm) && + (strncmp(file, GRST_HIST_PREFIX, + sizeof(GRST_HIST_PREFIX) - 1) != 0)) + { + temp = apr_psprintf(r->pool, + "" + "Edit page .\n", conf->adminfile, file); + out = apr_pstrcat(r->pool, out, temp, NULL); + } + + if (GRSTgaclPermHasList(perm) || GRSTgaclPermHasWrite(perm)) + { + temp = apr_psprintf(r->pool, + "Manage directory .\n", + dir_uri, conf->adminfile); + + out = apr_pstrcat(r->pool, out, temp, NULL); + } + } + } + + if ((https != NULL) && (strcasecmp(https, "on") == 0)) + temp = apr_psprintf(r->pool, + "Switch to HTTP \n", + r->server->server_hostname, r->unparsed_uri); + else temp = apr_psprintf(r->pool, + "Switch to HTTPS \n", + r->server->server_hostname, r->unparsed_uri); + + out = apr_pstrcat(r->pool, out, temp, NULL); + + if ((conf->helpuri != NULL) && (conf->helpuri[0] != '\0')) + { + temp = apr_psprintf(r->pool, + ". Website Help\n", conf->helpuri); + out = apr_pstrcat(r->pool, out, temp, NULL); + } + + if ((!isdirectory) && + (conf->adminuri != NULL) && + (conf->adminuri[0] != '\0') && + (conf->adminfile != NULL) && + (conf->adminfile[0] != '\0')) + { + temp = apr_psprintf(r->pool, ". " + "Print View\n", conf->adminfile, file); + out = apr_pstrcat(r->pool, out, temp, NULL); + } + + if (conf->gridsitelink) + { + temp = apr_psprintf(r->pool, + ". Built with " + "GridSite %s\n", VERSION); + out = apr_pstrcat(r->pool, out, temp, NULL); + } + + out = apr_pstrcat(r->pool, out, "\n
\n", NULL); + + return out; +} + +int html_format(request_rec *r, mod_gridsite_cfg *conf) +/* + try to do GridSite formatting of .html files (NOT .shtml etc) +*/ +{ + int i, fd, errstatus; + char *buf, *p, *file, *s, *head_formatted, *header_formatted, + *body_formatted, *admin_formatted, *footer_formatted; + size_t length; + struct stat statbuf; + apr_file_t *fp; + + if (r->finfo.filetype == APR_NOFILE) return HTTP_NOT_FOUND; + + if (apr_file_open(&fp, r->filename, APR_READ, 0, r->pool) != 0) + return HTTP_INTERNAL_SERVER_ERROR; + + file = rindex(r->uri, '/'); + if (file != NULL) ++file; /* file points to name without path */ + + buf = apr_palloc(r->pool, (size_t)(r->finfo.size + 1)); + length = r->finfo.size; + apr_file_read(fp, buf, &length); + buf[r->finfo.size] = '\0'; + apr_file_close(fp); + + /* **** try to find a header file in this or parent directories **** */ + + /* first make a buffer big enough to hold path names we want to try */ + fd = -1; + s = malloc(strlen(r->filename) + strlen(conf->headfile) + 1); + strcpy(s, r->filename); + + for (;;) + { + p = rindex(s, '/'); + if (p == NULL) break; /* failed to find one */ + p[1] = '\0'; + strcat(p, conf->headfile); + + fd = open(s, O_RDONLY); + if (fd != -1) break; /* found one */ + + *p = '\0'; + } + + free(s); + + if (fd == -1) /* not found, so set up not to output one */ + { + head_formatted = apr_pstrdup(r->pool, ""); + header_formatted = apr_pstrdup(r->pool, ""); + body_formatted = buf; + } + else /* found a header file, so set up head and body to surround it */ + { + fstat(fd, &statbuf); + header_formatted = apr_palloc(r->pool, statbuf.st_size + 1); + read(fd, header_formatted, statbuf.st_size); + header_formatted[statbuf.st_size] = '\0'; + close(fd); + + p = strstr(buf, "pool, ""); + body_formatted = buf; + } + else + { + *p = '\0'; + head_formatted = buf; + ++p; + + while ((*p != '>') && (*p != '\0')) ++p; + + if (*p == '\0') + { + body_formatted = p; + } + else + { + *p = '\0'; + ++p; + body_formatted = p; + } + } + } + + /* **** remove closing tag from body **** */ + + p = strstr(body_formatted, "filename) + strlen(conf->footfile)); + strcpy(s, r->filename); + + for (;;) + { + p = rindex(s, '/'); + if (p == NULL) break; /* failed to find one */ + + p[1] = '\0'; + strcat(p, conf->footfile); + + fd = open(s, O_RDONLY); + if (fd != -1) break; /* found one */ + + *p = '\0'; + } + + free(s); + + if (fd == -1) /* failed to find a footer, so set up empty default */ + { + footer_formatted = apr_pstrdup(r->pool, ""); + } + else /* found a footer, so set up to use it */ + { + fstat(fd, &statbuf); + footer_formatted = apr_palloc(r->pool, statbuf.st_size + 1); + read(fd, footer_formatted, statbuf.st_size); + footer_formatted[statbuf.st_size] = '\0'; + close(fd); + } + + /* **** can now calculate the Content-Length and output headers **** */ + + length = strlen(head_formatted) + strlen(header_formatted) + + strlen(body_formatted) + strlen(admin_formatted) + + strlen(footer_formatted); + + ap_set_content_length(r, length); + ap_set_content_type(r, "text/html"); + + /* ** output the HTTP body (HTML Head+Body) ** */ + + ap_rputs(head_formatted, r); + ap_rputs(header_formatted, r); + ap_rputs(body_formatted, r); + ap_rputs(admin_formatted, r); + ap_rputs(footer_formatted, r); + + return OK; +} + +int html_dir_list(request_rec *r, mod_gridsite_cfg *conf) +/* + output HTML directory listing, with level of formatting controlled + by GridSiteHtmlFormat/conf->format +*/ +{ + int i, fd, n; + char *buf, *p, *s, *head_formatted, *header_formatted, + *body_formatted, *admin_formatted, *footer_formatted, *temp, + modified[99], *d_namepath, *indexheaderpath, *indexheadertext; + size_t length; + struct stat statbuf; + struct tm mtime_tm; + struct dirent **namelist; + + if (r->finfo.filetype == APR_NOFILE) return HTTP_NOT_FOUND; + + head_formatted = apr_psprintf(r->pool, + "Directory listing %s\n", r->uri); + + if (conf->format) + { + /* **** try to find a header file in this or parent directories **** */ + + /* first make a buffer big enough to hold path names we want to try */ + fd = -1; + s = malloc(strlen(r->filename) + strlen(conf->headfile) + 1); + strcpy(s, r->filename); + + for (;;) + { + p = rindex(s, '/'); + if (p == NULL) break; /* failed to find one */ + p[1] = '\0'; + strcat(p, conf->headfile); + + fd = open(s, O_RDONLY); + if (fd != -1) break; /* found one */ + + *p = '\0'; + } + + free(s); + + if (fd == -1) /* not found, so set up to output sensible default */ + { + header_formatted = apr_pstrdup(r->pool, ""); + } + else /* found a header file, so set up head and body to surround it */ + { + fstat(fd, &statbuf); + header_formatted = apr_palloc(r->pool, statbuf.st_size + 1); + read(fd, header_formatted, statbuf.st_size); + header_formatted[statbuf.st_size] = '\0'; + close(fd); + } + } + else header_formatted = apr_pstrdup(r->pool, ""); + + body_formatted = apr_psprintf(r->pool, + "

Directory listing %s

\n", r->uri); + + if (conf->indexheader != NULL) + { + indexheaderpath = apr_psprintf(r->pool, "%s/%s", r->filename, + conf->indexheader); + fd = open(indexheaderpath, O_RDONLY); + if (fd != -1) + { + fstat(fd, &statbuf); + indexheadertext = apr_palloc(r->pool, statbuf.st_size + 1); + read(fd, indexheadertext, statbuf.st_size); + indexheadertext[statbuf.st_size] = '\0'; + close(fd); + + body_formatted = apr_pstrcat(r->pool, body_formatted, + indexheadertext, NULL); + } + } + + body_formatted = apr_pstrcat(r->pool, body_formatted, "

\n", NULL); + + if (r->unparsed_uri[1] != '\0') + body_formatted = apr_pstrcat(r->pool, body_formatted, + "\n", + NULL); + + n = scandir(r->filename, &namelist, 0, versionsort); + while (n--) + { + if ((namelist[n]->d_name[0] != '.') && + ((conf->indexheader == NULL) || + (strcmp(conf->indexheader, namelist[n]->d_name) != 0))) + { + d_namepath = apr_psprintf(r->pool, "%s/%s", r->filename, + namelist[n]->d_name); + stat(d_namepath, &statbuf); + + localtime_r(&(statbuf.st_mtime), &mtime_tm); + strftime(modified, sizeof(modified), + "", + &mtime_tm); + + if (S_ISDIR(statbuf.st_mode)) + temp = apr_psprintf(r->pool, + "" + "%s\n", + namelist[n]->d_name, statbuf.st_mtime, + namelist[n]->d_name, + statbuf.st_size, modified); + else temp = apr_psprintf(r->pool, + "" + "%s\n", + namelist[n]->d_name, statbuf.st_size, statbuf.st_mtime, + namelist[n]->d_name, + statbuf.st_size, modified); + + body_formatted = apr_pstrcat(r->pool,body_formatted,temp,NULL); + } + + free(namelist[n]); + } + + free(namelist); + + body_formatted = apr_pstrcat(r->pool, body_formatted, "
[Parent directory]
%R%e %b %y
%s/%ld
" + "%s%ld
\n", NULL); + + if (conf->format) + { + /* **** set up dynamic part of footer to go at end of body **** */ + + admin_formatted = make_admin_footer(r, conf, TRUE); + + /* **** try to find a footer file in this or parent directories **** */ + + /* first make a buffer big enough to hold path names we want to try */ + fd = -1; + s = malloc(strlen(r->filename) + strlen(conf->footfile)); + strcpy(s, r->filename); + + for (;;) + { + p = rindex(s, '/'); + if (p == NULL) break; /* failed to find one */ + + p[1] = '\0'; + strcat(p, conf->footfile); + + fd = open(s, O_RDONLY); + if (fd != -1) break; /* found one */ + + *p = '\0'; + } + + free(s); + + if (fd == -1) /* failed to find a footer, so use standard default */ + { + footer_formatted = apr_pstrdup(r->pool, ""); + } + else /* found a footer, so set up to use it */ + { + fstat(fd, &statbuf); + footer_formatted = apr_palloc(r->pool, statbuf.st_size + 1); + read(fd, footer_formatted, statbuf.st_size); + footer_formatted[statbuf.st_size] = '\0'; + close(fd); + } + } + else + { + admin_formatted = apr_pstrdup(r->pool, ""); + footer_formatted = apr_pstrdup(r->pool, ""); + } + + /* **** can now calculate the Content-Length and output headers **** */ + + length = strlen(head_formatted) + strlen(header_formatted) + + strlen(body_formatted) + strlen(admin_formatted) + + strlen(footer_formatted); + + ap_set_content_length(r, length); + ap_set_content_type(r, "text/html"); + + /* ** output the HTTP body (HTML Head+Body) ** */ + + ap_rputs(head_formatted, r); + ap_rputs(header_formatted, r); + ap_rputs(body_formatted, r); + ap_rputs(admin_formatted, r); + ap_rputs(footer_formatted, r); + + return OK; +} + +int http_put_method(request_rec *r, mod_gridsite_cfg *conf) +{ + char buf[2048]; + size_t length; + int retcode; + apr_file_t *fp; + + /* *** check if directory creation: PUT /.../ *** */ + + if ((r->unparsed_uri != NULL) && + (r->unparsed_uri[0] != '\0') && + (r->unparsed_uri[strlen(r->unparsed_uri) - 1] == '/')) + { + if (apr_dir_make(r->filename, APR_UREAD | APR_UWRITE | APR_UEXECUTE, + r->pool) != 0) return HTTP_INTERNAL_SERVER_ERROR; + + ap_set_content_length(r, 0); + ap_set_content_type(r, "text/html"); + return OK; + } + + /* *** otherwise assume trying to create a regular file *** */ + + if (apr_file_open(&fp, r->filename, APR_WRITE | APR_CREATE | APR_BUFFERED, + APR_UREAD | APR_UWRITE, r->pool) != 0) return HTTP_INTERNAL_SERVER_ERROR; + +// need to add Range: support at some point too + + retcode = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK); + if (retcode == OK) + { + if (ap_should_client_block(r)) + while ((length = ap_get_client_block(r, buf, sizeof(buf))) > 0) + if (apr_file_write(fp, buf, &length) != 0) + { + retcode = HTTP_INTERNAL_SERVER_ERROR; + break; + } + + ap_set_content_length(r, 0); + ap_set_content_type(r, "text/html"); + } + + if (apr_file_close(fp) != 0) return HTTP_INTERNAL_SERVER_ERROR; + + return retcode; +} + +int http_delete_method(request_rec *r, mod_gridsite_cfg *conf) +{ + if (remove(r->filename) != 0) return HTTP_FORBIDDEN; + + ap_set_content_length(r, 0); + ap_set_content_type(r, "text/html"); + + return OK; +} + +static int mod_gridsite_dir_handler(request_rec *r, mod_gridsite_cfg *conf) +/* + handler switch for directories +*/ +{ + /* *** is this a write method? only possible if GridSiteAuth on *** */ + + if (conf->auth) + { + if ((r->method_number == M_PUT) && + (conf->methods != NULL) && + (strstr(conf->methods, " PUT " ) != NULL)) + return http_put_method(r, conf); + + if ((r->method_number == M_DELETE) && + (conf->methods != NULL) && + (strstr(conf->methods, " DELETE ") != NULL)) + return http_delete_method(r, conf); + } + + /* *** directory listing? *** */ + + if ((r->method_number == M_GET) && (conf->indexes)) + return html_dir_list(r, conf); /* directory listing */ + + return DECLINED; /* *** nothing to see here, move along *** */ +} + +static int mod_gridsite_nondir_handler(request_rec *r, mod_gridsite_cfg *conf) +/* + one big handler switch for everything other than directories, since we + might be responding to MIME * / * for local PUT, MOVE, COPY and DELETE, + and GET inside ghost directories. +*/ +{ + /* *** is this a write method? only possible if GridSiteAuth on *** */ + + if (conf->auth) + { + if ((r->method_number == M_PUT) && + (conf->methods != NULL) && + (strstr(conf->methods, " PUT " ) != NULL)) + return http_put_method(r, conf); + + if ((r->method_number == M_DELETE) && + (conf->methods != NULL) && + (strstr(conf->methods, " DELETE ") != NULL)) + return http_delete_method(r, conf); + } + + /* *** check if a special ghost admin CGI *** */ + + if (conf->adminfile && conf->adminuri && + (strlen(r->filename) > strlen(conf->adminfile) + 1) && + (strcmp(&(r->filename[strlen(r->filename) - strlen(conf->adminfile)]), + conf->adminfile) == 0) && + (r->filename[strlen(r->filename)-strlen(conf->adminfile)-1] == '/') && + ((r->method_number == M_POST) || + (r->method_number == M_GET))) + { + ap_internal_redirect(conf->adminuri, r); + return OK; + } + + /* *** finally look for .html files that we should format *** */ + + if ((conf->format) && /* conf->format set by GridSiteHtmlFormat on */ + (strlen(r->filename) > 5) && + (strcmp(&(r->filename[strlen(r->filename)-5]), ".html") == 0) && + (r->method_number == M_GET)) return html_format(r, conf); + + return DECLINED; /* *** nothing to see here, move along *** */ +} + +static void recurse4dirlist(char *dirname, time_t *dirs_time, + char *fulluri, int fullurilen, + char *encfulluri, int enclen, + apr_pool_t *pool, char **body, + int recurse_level) +/* try to find DN Lists in dir[] and its subdirs that match the fulluri[] + prefix. add blobs of HTML to body as they are found. */ +{ + char *unencname, modified[99], *oneline, *d_namepath; + DIR *oneDIR; + struct dirent *onedirent; + struct tm mtime_tm; + size_t length; + struct stat statbuf; + + if ((stat(dirname, &statbuf) != 0) || + (!S_ISDIR(statbuf.st_mode)) || + ((oneDIR = opendir(dirname)) == NULL)) return; + + if (statbuf.st_mtime > *dirs_time) *dirs_time = statbuf.st_mtime; + + while ((onedirent = readdir(oneDIR)) != NULL) + { + if (onedirent->d_name[0] == '.') continue; + + d_namepath = apr_psprintf(pool, "%s/%s", dirname, onedirent->d_name); + if (stat(d_namepath, &statbuf) != 0) continue; + + if (S_ISDIR(statbuf.st_mode) && (recurse_level < GRST_RECURS_LIMIT)) + recurse4dirlist(d_namepath, dirs_time, fulluri, + fullurilen, encfulluri, enclen, + pool, body, recurse_level + 1); + else if ((strncmp(onedirent->d_name, encfulluri, enclen) == 0) && + (onedirent->d_name[strlen(onedirent->d_name) - 1] != '~')) + { + unencname = GRSThttpUrlDecode(onedirent->d_name); + + if (strncmp(unencname, fulluri, fullurilen) == 0) + { + + if (statbuf.st_mtime > *dirs_time) + *dirs_time = statbuf.st_mtime; + + localtime_r(&(statbuf.st_mtime), &mtime_tm); + strftime(modified, sizeof(modified), + "%R%e %b %y", + &mtime_tm); + + oneline = apr_psprintf(pool, + "" + "%s" + "%ld%s\n", + &unencname[fullurilen], statbuf.st_size, + statbuf.st_mtime, unencname, + statbuf.st_size, modified); + + *body = apr_pstrcat(pool, *body, oneline, NULL); + } + + free(unencname); /* libgridsite doesnt use pools */ + } + } + + closedir(oneDIR); +} + +static int mod_gridsite_dnlistsuri_dir_handler(request_rec *r, + mod_gridsite_cfg *conf) +/* + virtual DN-list file lister: make all DN lists on the dn-lists + path of this server appear to be in the dn-lists directory itself + (ie where they appear in the DN lists path doesnt matter, as long + as their name matches) +*/ +{ + int enclen, fullurilen, fd; + char *fulluri, *encfulluri, *dn_list_ptr, *dirname, *unencname, + *body, *oneline, *p, *s, + *head_formatted, *header_formatted, *footer_formatted, + *permstr = NULL; + struct stat statbuf; + size_t length; + time_t dirs_time = 0; + GRSTgaclPerm perm = GRST_PERM_NONE; + + if (r->notes != NULL) + permstr = (char *) apr_table_get(r->notes, "GRST_PERM"); + + if (permstr != NULL) sscanf(permstr, "%d", &perm); + + fulluri = apr_psprintf(r->pool, "https://%s%s", + ap_get_server_name(r), conf->dnlistsuri); + fullurilen = strlen(fulluri); + + encfulluri = GRSThttpUrlEncode(fulluri); + enclen = strlen(encfulluri); + + if (conf->dnlists != NULL) p = conf->dnlists; + else p = getenv("GRST_DN_LISTS"); + + if (p == NULL) p = GRST_DN_LISTS; + dn_list_ptr = apr_pstrdup(r->pool, p); + + head_formatted = apr_psprintf(r->pool, + "Directory listing %s\n", r->uri); + + if (conf->format) + { + /* **** try to find a header file in this or parent directories **** */ + + /* first make a buffer big enough to hold path names we want to try */ + fd = -1; + s = malloc(strlen(r->filename) + strlen(conf->headfile) + 1); + strcpy(s, r->filename); + + for (;;) + { + p = rindex(s, '/'); + if (p == NULL) break; /* failed to find one */ + p[1] = '\0'; + strcat(p, conf->headfile); + + fd = open(s, O_RDONLY); + if (fd != -1) break; /* found one */ + + *p = '\0'; + } + + free(s); + + if (fd == -1) /* not found, so set up to output sensible default */ + { + header_formatted = apr_pstrdup(r->pool, ""); + } + else /* found a header file, so set up head and body to surround it */ + { + fstat(fd, &statbuf); + header_formatted = apr_palloc(r->pool, statbuf.st_size + 1); + read(fd, header_formatted, statbuf.st_size); + header_formatted[statbuf.st_size] = '\0'; + close(fd); + } + } + else header_formatted = apr_pstrdup(r->pool, ""); + + body = apr_psprintf(r->pool, + "

Directory listing %s

\n", r->uri); + + if ((r->uri)[1] != '\0') + body = apr_pstrcat(r->pool, body, + "\n", + NULL); + + while ((dirname = strsep(&dn_list_ptr, ":")) != NULL) + recurse4dirlist(dirname, &dirs_time, fulluri, fullurilen, + encfulluri, enclen, r->pool, &body, 0); + + if ((stat(r->filename, &statbuf) == 0) && + S_ISDIR(statbuf.st_mode) && + GRSTgaclPermHasWrite(perm)) + { + oneline = apr_psprintf(r->pool, + "\n" + "" + "\n", + r->uri, conf->adminfile); + + body = apr_pstrcat(r->pool, body, oneline, NULL); + } + + body = apr_pstrcat(r->pool, body, "
[Parent directory]
\n", NULL); + + free(encfulluri); /* libgridsite doesnt use pools */ + + if (conf->format) + { + /* **** try to find a footer file in this or parent directories **** */ + + /* first make a buffer big enough to hold path names we want to try */ + fd = -1; + s = malloc(strlen(r->filename) + strlen(conf->footfile)); + strcpy(s, r->filename); + + for (;;) + { + p = rindex(s, '/'); + if (p == NULL) break; /* failed to find one */ + + p[1] = '\0'; + strcat(p, conf->footfile); + + fd = open(s, O_RDONLY); + if (fd != -1) break; /* found one */ + + *p = '\0'; + } + + free(s); + + if (fd == -1) /* failed to find a footer, so use standard default */ + { + footer_formatted = apr_pstrdup(r->pool, ""); + } + else /* found a footer, so set up to use it */ + { + fstat(fd, &statbuf); + footer_formatted = apr_palloc(r->pool, statbuf.st_size + 1); + read(fd, footer_formatted, statbuf.st_size); + footer_formatted[statbuf.st_size] = '\0'; + close(fd); + } + } + else footer_formatted = apr_pstrdup(r->pool, ""); + + /* **** can now calculate the Content-Length and output headers **** */ + + length = strlen(head_formatted) + strlen(header_formatted) + + strlen(body) + strlen(footer_formatted); + + ap_set_content_length(r, length); + r->mtime = apr_time_from_sec(dirs_time); + ap_set_last_modified(r); + ap_set_content_type(r, "text/html"); + + /* ** output the HTTP body (HTML Head+Body) ** */ + ap_rputs(head_formatted, r); + ap_rputs(header_formatted, r); + ap_rputs(body, r); + ap_rputs(footer_formatted, r); + + return OK; +} + +static char *recurse4file(char *dir, char *file, apr_pool_t *pool, + int recurse_level) +/* try to find file[] in dir[]. try subdirs if not found. + return full path to first found version or NULL on failure */ +{ + char *fullfilename, *fulldirname; + struct stat statbuf; + DIR *dirDIR; + struct dirent *file_ent; + + /* try to find in current directory */ + + fullfilename = apr_psprintf(pool, "%s/%s", dir, file); + + if (stat(fullfilename, &statbuf) == 0) return fullfilename; + + /* maybe search in subdirectories */ + + if (recurse_level >= GRST_RECURS_LIMIT) return NULL; + + dirDIR = opendir(dir); + + if (dirDIR == NULL) return NULL; + + while ((file_ent = readdir(dirDIR)) != NULL) + { + if (file_ent->d_name[0] == '.') continue; + + fulldirname = apr_psprintf(pool, "%s/%s", dir, file_ent->d_name); + if ((stat(fulldirname, &statbuf) == 0) && + S_ISDIR(statbuf.st_mode) && + ((fullfilename = recurse4file(fulldirname, file, + pool, recurse_level + 1)) != NULL)) + { + closedir(dirDIR); + return fullfilename; + } + } + + closedir(dirDIR); + + return NULL; +} + +static int mod_gridsite_dnlistsuri_handler(request_rec *r, + mod_gridsite_cfg *conf) +/* + virtual DN-list file generator +*/ +{ + int fd; + char *fulluri, *encfulluri, *dn_list_ptr, *filename, *dirname, *p, + *buf; + struct stat statbuf; + + /* *** check if a special ghost admin CGI *** */ + + if (conf->adminfile && conf->adminuri && + (strlen(r->filename) > strlen(conf->adminfile) + 1) && + (strcmp(&(r->filename[strlen(r->filename) - strlen(conf->adminfile)]), + conf->adminfile) == 0) && + (r->filename[strlen(r->filename)-strlen(conf->adminfile)-1] == '/') && + ((r->method_number == M_POST) || + (r->method_number == M_GET))) + { + ap_internal_redirect(conf->adminuri, r); + return OK; + } + + fulluri = apr_psprintf(r->pool, "https://%s%s", + ap_get_server_name(r), r->uri); + + encfulluri = GRSThttpUrlEncode(fulluri); + + if (conf->dnlists != NULL) p = conf->dnlists; + else p = getenv("GRST_DN_LISTS"); + + if (p == NULL) p = GRST_DN_LISTS; + dn_list_ptr = apr_pstrdup(r->pool, p); + + while ((dirname = strsep(&dn_list_ptr, ":")) != NULL) + { + filename = recurse4file(dirname, encfulluri, r->pool, 0); + + if (filename == NULL) continue; + + fd = open(filename, O_RDONLY); + + if (fd == -1) continue; + + fstat(fd, &statbuf); + ap_set_content_length(r, (apr_off_t) statbuf.st_size); + r->mtime = apr_time_from_sec(statbuf.st_mtime); + ap_set_content_type(r, "text/plain"); + ap_set_last_modified(r); + + buf = apr_palloc(r->pool, statbuf.st_size + 1); + read(fd, buf, statbuf.st_size); + buf[statbuf.st_size] = '\0'; + + ap_rputs(buf, r); + + close(fd); + + return OK; + } + + return HTTP_NOT_FOUND; +} + +static void *create_gridsite_dir_config(apr_pool_t *p, char *path) +{ + mod_gridsite_cfg *conf = apr_palloc(p, sizeof(*conf)); + + if (path == NULL) /* set up server defaults */ + { + conf->auth = 0; /* GridSiteAuth on/off */ + conf->envs = 1; /* GridSiteEnvs on/off */ + conf->format = 0; /* GridSiteHtmlFormat on/off */ + conf->indexes = 0; /* GridSiteIndexes on/off */ + conf->indexheader = NULL; /* GridSiteIndexHeader File-value */ + conf->gridsitelink = 1; /* GridSiteLink on/off */ + conf->adminfile = apr_pstrdup(p, GRST_ADMIN_FILE); + /* GridSiteAdminFile File-value */ + conf->adminuri = NULL; /* GridSiteAdminURI URI-value */ + conf->helpuri = NULL; /* GridSiteHelpURI URI-value */ + conf->dnlists = NULL; /* GridSiteDNlists Search-path */ + conf->dnlistsuri = NULL; /* GridSiteDNlistsURI URI-value */ + conf->adminlist = NULL; /* GridSiteAdminList URI-value */ + conf->gsiproxylimit = 1; /* GridSiteGSIProxyLimit number */ + conf->unzip = NULL; /* GridSiteUnzip file-path */ + + conf->methods = apr_pstrdup(p, " GET "); + /* GridSiteMethods methods */ + + conf->editable = apr_pstrdup(p, " txt shtml html htm css js php jsp "); + /* GridSiteEditable types */ + + conf->headfile = apr_pstrdup(p, GRST_HEADFILE); + conf->footfile = apr_pstrdup(p, GRST_FOOTFILE); + /* GridSiteHeadFile and GridSiteFootFile file name */ + } + else + { + conf->auth = UNSET; /* GridSiteAuth on/off */ + conf->envs = UNSET; /* GridSiteEnvs on/off */ + conf->format = UNSET; /* GridSiteHtmlFormat on/off */ + conf->indexes = UNSET; /* GridSiteIndexes on/off */ + conf->indexheader = NULL; /* GridSiteIndexHeader File-value */ + conf->gridsitelink = UNSET; /* GridSiteLink on/off */ + conf->adminfile = NULL; /* GridSiteAdminFile File-value */ + conf->adminuri = NULL; /* GridSiteAdminURI URI-value */ + conf->helpuri = NULL; /* GridSiteHelpURI URI-value */ + conf->dnlists = NULL; /* GridSiteDNlists Search-path */ + conf->dnlistsuri = NULL; /* GridSiteDNlistsURI URI-value */ + conf->adminlist = NULL; /* GridSiteAdminList URI-value */ + conf->gsiproxylimit = UNSET; /* GridSiteGSIProxyLimit number */ + conf->unzip = NULL; /* GridSiteUnzip file-path */ + conf->methods = NULL; /* GridSiteMethods methods */ + conf->editable = NULL; /* GridSiteEditable types */ + conf->headfile = NULL; /* GridSiteHeadFile file name */ + conf->footfile = NULL; /* GridSiteFootFile file name */ + } + + return conf; +} + +static void *merge_gridsite_dir_config(apr_pool_t *p, void *vserver, + void *vdirect) +/* merge directory with server-wide directory configs */ +{ + mod_gridsite_cfg *conf, *server, *direct; + + server = (mod_gridsite_cfg *) vserver; + direct = (mod_gridsite_cfg *) vdirect; + conf = apr_palloc(p, sizeof(*conf)); + + if (direct->auth != UNSET) conf->auth = direct->auth; + else conf->auth = server->auth; + + if (direct->envs != UNSET) conf->envs = direct->envs; + else conf->envs = server->envs; + + if (direct->format != UNSET) conf->format = direct->format; + else conf->format = server->format; + + if (direct->indexes != UNSET) conf->indexes = direct->indexes; + else conf->indexes = server->indexes; + + if (direct->gridsitelink != UNSET) conf->gridsitelink=direct->gridsitelink; + else conf->gridsitelink=server->gridsitelink; + + if (direct->indexheader != NULL) conf->indexheader = direct->indexheader; + else conf->indexheader = server->indexheader; + + if (direct->adminfile != NULL) conf->adminfile = direct->adminfile; + else conf->adminfile = server->adminfile; + + if (direct->adminuri != NULL) conf->adminuri = direct->adminuri; + else conf->adminuri = server->adminuri; + + if (direct->helpuri != NULL) conf->helpuri = direct->helpuri; + else conf->helpuri = server->helpuri; + + if (direct->dnlists != NULL) conf->dnlists = direct->dnlists; + else conf->dnlists = server->dnlists; + + if (direct->dnlistsuri != NULL) conf->dnlistsuri = direct->dnlistsuri; + else conf->dnlistsuri = server->dnlistsuri; + + if (direct->adminlist != NULL) conf->adminlist = direct->adminlist; + else conf->adminlist = server->adminlist; + + if (direct->gsiproxylimit != UNSET) + conf->gsiproxylimit = direct->gsiproxylimit; + else conf->gsiproxylimit = server->gsiproxylimit; + + if (direct->unzip != NULL) conf->unzip = direct->unzip; + else conf->unzip = server->unzip; + + if (direct->methods != NULL) conf->methods = direct->methods; + else conf->methods = server->methods; + + if (direct->editable != NULL) conf->editable = direct->editable; + else conf->editable = server->editable; + + if (direct->headfile != NULL) conf->headfile = direct->headfile; + else conf->headfile = server->headfile; + + if (direct->footfile != NULL) conf->footfile = direct->footfile; + else conf->footfile = server->footfile; + + return conf; +} + +static const char *mod_gridsite_take1_cmds(cmd_parms *a, void *cfg, + const char *parm) +{ + int n; + char *p; + + if (strcasecmp(a->cmd->name, "GridSiteAdminFile") == 0) + { + if (index(parm, '/') != NULL) + return "/ not permitted in GridSiteAdminFile"; + + ((mod_gridsite_cfg *) cfg)->adminfile = + apr_pstrdup(a->pool, parm); + } + else if (strcasecmp(a->cmd->name, "GridSiteAdminURI") == 0) + { + if (*parm != '/') return "GridSiteAdminURI must begin with /"; + + ((mod_gridsite_cfg *) cfg)->adminuri = + apr_pstrdup(a->pool, parm); + } + else if (strcasecmp(a->cmd->name, "GridSiteHelpURI") == 0) + { + if (*parm != '/') return "GridSiteHelpURI must begin with /"; + + ((mod_gridsite_cfg *) cfg)->helpuri = + apr_pstrdup(a->pool, parm); + } + else if (strcasecmp(a->cmd->name, "GridSiteDNlists") == 0) + { + ((mod_gridsite_cfg *) cfg)->dnlists = + apr_pstrdup(a->pool, parm); + } + else if (strcasecmp(a->cmd->name, "GridSiteDNlistsURI") == 0) + { + if (*parm != '/') return "GridSiteDNlistsURI must begin with /"; + + if ((*parm != '\0') && (parm[strlen(parm) - 1] == '/')) + ((mod_gridsite_cfg *) cfg)->dnlistsuri = + apr_pstrdup(a->pool, parm); + else + ((mod_gridsite_cfg *) cfg)->dnlistsuri = + apr_pstrcat(a->pool, parm, "/", NULL); + } + else if (strcasecmp(a->cmd->name, "GridSiteAdminList") == 0) + { + ((mod_gridsite_cfg *) cfg)->adminlist = + apr_pstrdup(a->pool, parm); + } + else if (strcasecmp(a->cmd->name, "GridSiteGSIProxyLimit") == 0) + { + n = -1; + + if ((sscanf(parm, "%d", &n) == 1) && (n >= 0)) + ((mod_gridsite_cfg *) cfg)->gsiproxylimit = n; + else return "GridSiteGSIProxyLimit must be a number >= 0"; + } + else if (strcasecmp(a->cmd->name, "GridSiteUnzip") == 0) + { + if (*parm != '/') return "GridSiteUnzip must begin with /"; + + ((mod_gridsite_cfg *) cfg)->unzip = + apr_pstrdup(a->pool, parm); + } + else if (strcasecmp(a->cmd->name, "GridSiteMethods") == 0) + { + ((mod_gridsite_cfg *) cfg)->methods = + apr_psprintf(a->pool, " %s ", parm); + + for (p = ((mod_gridsite_cfg *) cfg)->methods; + *p != '\0'; + ++p) if (*p == '\t') *p = ' '; + } + else if (strcasecmp(a->cmd->name, "GridSiteEditable") == 0) + { + ((mod_gridsite_cfg *) cfg)->editable = + apr_psprintf(a->pool, " %s ", parm); + + for (p = ((mod_gridsite_cfg *) cfg)->editable; + *p != '\0'; + ++p) if (*p == '\t') *p = ' '; + } + else if (strcasecmp(a->cmd->name, "GridSiteHeadFile") == 0) + { + ((mod_gridsite_cfg *) cfg)->headfile = + apr_pstrdup(a->pool, parm); + } + else if (strcasecmp(a->cmd->name, "GridSiteFootFile") == 0) + { + ((mod_gridsite_cfg *) cfg)->footfile = + apr_pstrdup(a->pool, parm); + } + else if (strcasecmp(a->cmd->name, "GridSiteIndexHeader") == 0) + { + if (index(parm, '/') != NULL) + return "/ not permitted in GridSiteIndexHeader"; + + ((mod_gridsite_cfg *) cfg)->indexheader = + apr_pstrdup(a->pool, parm); + } + + return NULL; +} + +static const char *mod_gridsite_flag_cmds(cmd_parms *a, void *cfg, + int flag) +{ + if (strcasecmp(a->cmd->name, "GridSiteAuth") == 0) + { + ((mod_gridsite_cfg *) cfg)->auth = flag; + } + else if (strcasecmp(a->cmd->name, "GridSiteEnvs") == 0) + { + ((mod_gridsite_cfg *) cfg)->envs = flag; + } + else if (strcasecmp(a->cmd->name, "GridSiteHtmlFormat") == 0) + { + ((mod_gridsite_cfg *) cfg)->format = flag; + } + else if (strcasecmp(a->cmd->name, "GridSiteIndexes") == 0) + { + ((mod_gridsite_cfg *) cfg)->indexes = flag; + } + else if (strcasecmp(a->cmd->name, "GridSiteLink") == 0) + { + ((mod_gridsite_cfg *) cfg)->gridsitelink = flag; + } + + return NULL; +} + +static const command_rec mod_gridsite_cmds[] = +{ + AP_INIT_FLAG("GridSiteAuth", mod_gridsite_flag_cmds, + NULL, OR_FILEINFO, "on or off"), + AP_INIT_FLAG("GridSiteEnvs", mod_gridsite_flag_cmds, + NULL, OR_FILEINFO, "on or off"), + AP_INIT_FLAG("GridSiteHtmlFormat", mod_gridsite_flag_cmds, + NULL, OR_FILEINFO, "on or off"), + AP_INIT_FLAG("GridSiteIndexes", mod_gridsite_flag_cmds, + NULL, OR_FILEINFO, "on or off"), + AP_INIT_FLAG("GridSiteLink", mod_gridsite_flag_cmds, + NULL, OR_FILEINFO, "on or off"), + + AP_INIT_TAKE1("GridSiteAdminFile", mod_gridsite_take1_cmds, + NULL, OR_FILEINFO, "Ghost per-directory admin CGI"), + AP_INIT_TAKE1("GridSiteAdminURI", mod_gridsite_take1_cmds, + NULL, OR_FILEINFO, "URI of real gridsite-admin.cgi"), + AP_INIT_TAKE1("GridSiteHelpURI", mod_gridsite_take1_cmds, + NULL, OR_FILEINFO, "URI of Website Help pages"), + AP_INIT_TAKE1("GridSiteDNlists", mod_gridsite_take1_cmds, + NULL, OR_FILEINFO, "DN Lists directories search path"), + AP_INIT_TAKE1("GridSiteDNlistsURI", mod_gridsite_take1_cmds, + NULL, OR_FILEINFO, "URI of published DN lists"), + AP_INIT_TAKE1("GridSiteAdminList", mod_gridsite_take1_cmds, + NULL, OR_FILEINFO, "URI of admin DN List"), + AP_INIT_TAKE1("GridSiteGSIProxyLimit", mod_gridsite_take1_cmds, + NULL, OR_FILEINFO, "Max level of GSI proxy validity"), + AP_INIT_TAKE1("GridSiteUnzip", mod_gridsite_take1_cmds, + NULL, OR_FILEINFO, "Absolute path to unzip command"), + + AP_INIT_RAW_ARGS("GridSiteMethods", mod_gridsite_take1_cmds, + NULL, OR_FILEINFO, "permitted HTTP methods"), + AP_INIT_RAW_ARGS("GridSiteEditable", mod_gridsite_take1_cmds, + NULL, OR_FILEINFO, "editable file extensions"), + AP_INIT_TAKE1("GridSiteHeadFile", mod_gridsite_take1_cmds, + NULL, OR_FILEINFO, "filename of HTML header"), + AP_INIT_TAKE1("GridSiteFootFile", mod_gridsite_take1_cmds, + NULL, OR_FILEINFO, "filename of HTML footer"), + AP_INIT_TAKE1("GridSiteIndexHeader", mod_gridsite_take1_cmds, + NULL, OR_FILEINFO, "filename of directory header"), + + {NULL} +}; + +static int mod_gridsite_first_fixups(request_rec *r) +{ + mod_gridsite_cfg *conf; + + if (r->finfo.filetype != APR_DIR) return DECLINED; + + conf = (mod_gridsite_cfg *) + ap_get_module_config(r->per_dir_config, &gridsite_module); + + if ((conf != NULL) && + (conf->dnlistsuri != NULL) && + (strncmp(r->uri, conf->dnlistsuri, strlen(conf->dnlistsuri)) == 0) && + (strcmp(r->uri, conf->dnlistsuri) != 0)) + { + r->finfo.filetype = APR_REG; + } + + return DECLINED; +} + +static int mod_gridsite_perm_handler(request_rec *r) +/* + Do authentication/authorization here rather than in the normal module + auth functions since the results of mod_ssl are available. + + We also publish environment variables here if requested by GridSiteEnv. +*/ +{ + int retcode = DECLINED, i, n; + char *dn, *p, envname[14], *grst_cred_0 = NULL, *dir_path, + *remotehost, s[99], *grst_cred_i, *file; + const char *content_type; + time_t now, notbefore, notafter; + apr_table_t *env; + GRSTgaclCred *cred = NULL, *cred_0 = NULL; + GRSTgaclUser *user = NULL; + GRSTgaclPerm perm = GRST_PERM_NONE; + GRSTgaclAcl *acl = NULL; + mod_gridsite_cfg *cfg; + + cfg = (mod_gridsite_cfg *) + ap_get_module_config(r->per_dir_config, &gridsite_module); + + if (cfg == NULL) return DECLINED; + + if ((cfg->auth == 0) && + (cfg->envs == 0)) + return DECLINED; /* if not turned on, look invisible */ + + env = r->subprocess_env; + + if (r->connection->notes != NULL) + grst_cred_0 = (char *) apr_table_get(r->connection->notes, "GRST_CRED_0"); + + if (grst_cred_0 != NULL) /* do we have per-connection cred variable(s)? */ + { + if (((mod_gridsite_cfg *) cfg)->envs) + apr_table_setn(env, "GRST_CRED_0", grst_cred_0); + + cred_0 = GRSTx509CompactToCred(grst_cred_0); + if ((cred_0 != NULL) && + (GRSTgaclCredGetDelegation(cred_0) + <= ((mod_gridsite_cfg *) cfg)->gsiproxylimit)) + { + user = GRSTgaclUserNew(cred_0); + + /* check for VOMS GRST_CRED_i too */ + + for (i=1; ; ++i) + { + snprintf(envname, sizeof(envname), "GRST_CRED_%d", i); + if (grst_cred_i = (char *) + apr_table_get(r->connection->notes,envname)) + { + if (((mod_gridsite_cfg *) cfg)->envs) + apr_table_setn(env, + apr_pstrdup(r->pool, envname), + grst_cred_i); + + if (cred = GRSTx509CompactToCred(grst_cred_i)) + GRSTgaclUserAddCred(user, cred); + } + else break; /* GRST_CRED_i are numbered consecutively */ + } + } + } + + if ((user != NULL) && ((mod_gridsite_cfg *) cfg)->dnlists) + GRSTgaclUserSetDNlists(user, ((mod_gridsite_cfg *) cfg)->dnlists); + + /* this checks for NULL arguments itself */ + if (GRSTgaclDNlistHasUser(((mod_gridsite_cfg *) cfg)->adminlist, user)) + perm = GRST_PERM_ALL; + else + { + remotehost = (char *) ap_get_remote_host(r->connection, + r->per_dir_config, REMOTE_DOUBLE_REV, NULL); + if ((remotehost != NULL) && (*remotehost != '\0')) + { + cred = GRSTgaclCredNew("dns"); + GRSTgaclCredAddValue(cred, "hostname", remotehost); + + if (user == NULL) user = GRSTgaclUserNew(cred); + else GRSTgaclUserAddCred(user, cred); + } + + acl = GRSTgaclAclLoadforFile(r->filename); + if (acl != NULL) perm = GRSTgaclAclTestUser(acl, user); + } + + apr_table_setn(r->notes, "GRST_PERM", apr_psprintf(r->pool, "%d", perm)); + + if (((mod_gridsite_cfg *) cfg)->envs) + { + apr_table_setn(env, "GRST_PERM", apr_psprintf(r->pool, "%d", perm)); + + if (((dir_path = apr_pstrdup(r->pool, r->filename)) != NULL) && + ((p = rindex(dir_path, '/')) != NULL)) + { + *p = '\0'; + apr_table_setn(env, "GRST_DIR_PATH", dir_path); + } + + if (((mod_gridsite_cfg *) cfg)->helpuri != NULL) + apr_table_setn(env, "GRST_HELP_URI", + ((mod_gridsite_cfg *) cfg)->helpuri); + + if (((mod_gridsite_cfg *) cfg)->adminfile != NULL) + apr_table_setn(env, "GRST_ADMIN_FILE", + ((mod_gridsite_cfg *) cfg)->adminfile); + + if (((mod_gridsite_cfg *) cfg)->editable != NULL) + apr_table_setn(env, "GRST_EDITABLE", + ((mod_gridsite_cfg *) cfg)->editable); + + if (((mod_gridsite_cfg *) cfg)->headfile != NULL) + apr_table_setn(env, "GRST_HEAD_FILE", + ((mod_gridsite_cfg *) cfg)->headfile); + + if (((mod_gridsite_cfg *) cfg)->footfile != NULL) + apr_table_setn(env, "GRST_FOOT_FILE", + ((mod_gridsite_cfg *) cfg)->footfile); + + if (((mod_gridsite_cfg *) cfg)->dnlists != NULL) + apr_table_setn(env, "GRST_DN_LISTS", + ((mod_gridsite_cfg *) cfg)->dnlists); + + if (((mod_gridsite_cfg *) cfg)->dnlistsuri != NULL) + apr_table_setn(env, "GRST_DN_LISTS_URI", + ((mod_gridsite_cfg *) cfg)->dnlistsuri); + + if (((mod_gridsite_cfg *) cfg)->adminlist != NULL) + apr_table_setn(env, "GRST_ADMIN_LIST", + ((mod_gridsite_cfg *) cfg)->adminlist); + + apr_table_setn(env, "GRST_GSIPROXY_LIMIT", + apr_psprintf(r->pool, "%d", + ((mod_gridsite_cfg *)cfg)->gsiproxylimit)); + + if (((mod_gridsite_cfg *) cfg)->unzip != NULL) + apr_table_setn(env, "GRST_UNZIP", + ((mod_gridsite_cfg *) cfg)->unzip); + + if (!(((mod_gridsite_cfg *) cfg)->gridsitelink)) + apr_table_setn(env, "GRST_NO_LINK", "1"); + } + + if (((mod_gridsite_cfg *) cfg)->auth) + { + /* *** Check HTTP method to decide which perm bits to check *** */ + + if (r->filename != NULL) + { + file = rindex(r->filename, '/'); + if (file != NULL) ++file; + else file = r->filename; + } + else file = NULL; + + content_type = r->content_type; + if ((content_type != NULL) && + (strcmp(content_type, DIR_MAGIC_TYPE) == 0) && + (((mod_gridsite_cfg *) cfg)->dnlistsuri != NULL) && + (strncmp(r->uri, + ((mod_gridsite_cfg *) cfg)->dnlistsuri, + strlen(((mod_gridsite_cfg *) cfg)->dnlistsuri)) == 0) && + (strlen(r->uri) > strlen(((mod_gridsite_cfg *) cfg)->dnlistsuri))) + content_type = "text/html"; + + if ( GRSTgaclPermHasNone(perm) || + + /* first two M_GET conditions make the subtle distinction + between .../ that maps to .../index.html (governed by + Read perm) or to dir list (governed by List perm); + third M_GET condition deals with typeless CGI requests */ + + ((r->method_number == M_GET) && + !GRSTgaclPermHasRead(perm) && + (content_type != NULL) && + (strcmp(content_type, DIR_MAGIC_TYPE) != 0)) || + + ((r->method_number == M_GET) && + !GRSTgaclPermHasList(perm) && + (content_type != NULL) && + (strcmp(content_type, DIR_MAGIC_TYPE) == 0)) || + + ((r->method_number == M_GET) && + !GRSTgaclPermHasRead(perm) && + (content_type == NULL)) || + + ((r->method_number == M_POST) && !GRSTgaclPermHasRead(perm) ) || + + (((r->method_number == M_PUT) || (r->method_number == M_DELETE)) && + !GRSTgaclPermHasWrite(perm) && + ((file == NULL) || (strcmp(file, GRST_ACL_FILE) != 0)) ) || + + (((r->method_number == M_PUT) || (r->method_number == M_DELETE)) && + !GRSTgaclPermHasAdmin(perm) && + (file != NULL) && + (strcmp(file, GRST_ACL_FILE) == 0) ) ) retcode = HTTP_FORBIDDEN; + } + + return retcode; +} + +int GRST_X509_check_issued_wrapper(X509_STORE_CTX *ctx,X509 *x,X509 *issuer) +/* We change the default callback to use our wrapper and discard errors + due to GSI proxy chains (ie where users certs act as CAs) */ +{ + int ret; + ret = X509_check_issued(issuer, x); + if (ret == X509_V_OK) + return 1; + + /* Non self-signed certs without signing are ok if they passed + the other checks inside X509_check_issued. Is this enough? */ + if ((ret == X509_V_ERR_KEYUSAGE_NO_CERTSIGN) && + (X509_NAME_cmp(X509_get_subject_name(issuer), + X509_get_subject_name(x)) != 0)) return 1; + + /* If we haven't asked for issuer errors don't set ctx */ + if (!(ctx->flags & X509_V_FLAG_CB_ISSUER_CHECK)) return 0; + + ctx->error = ret; + ctx->current_cert = x; + ctx->current_issuer = issuer; + return ctx->verify_cb(0, ctx); +} + +/* Later OpenSSL versions add a second pointer ... */ +int GRST_verify_cert_wrapper(X509_STORE_CTX *ctx, void *p) + +/* Earlier ones have a single argument ... */ +// int GRST_verify_cert_wrapper(X509_STORE_CTX *ctx) + +/* Before 0.9.7 we cannot change the check_issued callback directly in + the X509_STORE, so we must insert it in another callback that gets + called early enough */ +{ + ctx->check_issued = GRST_X509_check_issued_wrapper; + + return X509_verify_cert(ctx); +} + +int GRST_callback_SSLVerify_wrapper(int ok, X509_STORE_CTX *ctx) +{ + SSL *ssl = (SSL *) X509_STORE_CTX_get_app_data(ctx); + conn_rec *conn = (conn_rec *) SSL_get_app_data(ssl); + server_rec *s = conn->base_server; + SSLConnRec *sslconn = + (SSLConnRec *) ap_get_module_config(conn->conn_config, &ssl_module); + int errnum = X509_STORE_CTX_get_error(ctx); + int errdepth = X509_STORE_CTX_get_error_depth(ctx); + int returned_ok; + int first_non_ca; + + /* + * GSI Proxy user-cert-as-CA handling: + * we skip Invalid CA errors at this stage, since we will check this + * again at errdepth=0 for the full chain using GRSTx509CheckChain + */ + if (errnum == X509_V_ERR_INVALID_CA) + { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "Skip Invalid CA error in case a GSI Proxy"); + + sslconn->verify_error = NULL; + ok = TRUE; + errnum = X509_V_OK; + X509_STORE_CTX_set_error(ctx, errnum); + } + + /* + * New style GSI Proxy handling, with critical ProxyCertInfo + * extension: we use GRSTx509KnownCriticalExts() to check this + */ +#ifndef X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION +#define X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION 34 +#endif + if (errnum == X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION) + { + if (GRSTx509KnownCriticalExts(X509_STORE_CTX_get_current_cert(ctx)) + == GRST_RET_OK) + { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "GRSTx509KnownCriticalExts() accepts previously " + "Unhandled Critical Extension (GSI Proxy?)"); + + sslconn->verify_error = NULL; + ok = TRUE; + errnum = X509_V_OK; + X509_STORE_CTX_set_error(ctx, errnum); + } + } + + returned_ok = ssl_callback_SSLVerify(ok, ctx); + + /* in case ssl_callback_SSLVerify changed it */ + errnum = X509_STORE_CTX_get_error(ctx); + + if ((errdepth == 0) && (errnum == X509_V_OK)) + /* + * We've now got the last certificate - the identity being used for + * this connection. At this point we check the whole chain for valid + * CAs or, failing that, GSI-proxy validity using GRSTx509CheckChain. + */ + { + errnum = GRSTx509CheckChain(&first_non_ca, ctx); + + if (errnum != X509_V_OK) + { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, + "Invalid certificate chain reported by " + "GRSTx509CheckChain()"); + + sslconn->verify_error = X509_verify_cert_error_string(errnum); + ok = FALSE; + } + else + { + int i, lastcred; + STACK_OF(X509) *peer_certs; + const int maxcreds = 99; + const size_t credlen = 1024; + char creds[maxcreds][credlen+1], envname[14]; + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "Valid certificate" + " chain reported by GRSTx509CheckChain()"); + + /* + * Always put result of GRSTx509CompactCreds() into environment + */ + if (peer_certs = (STACK_OF(X509) *) X509_STORE_CTX_get_chain(ctx)) + { + if (GRSTx509CompactCreds(&lastcred, maxcreds, credlen, + (char *) creds, peer_certs, GRST_VOMS_DIR) == GRST_RET_OK) + { + for (i=0; i <= lastcred; ++i) + { + apr_table_setn(conn->notes, + apr_psprintf(conn->pool, "GRST_CRED_%d", i), + apr_pstrdup(conn->pool, creds[i])); + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "store GRST_CRED_%d=%s", i, creds[i]); + } + } + /* free remaining dup'd certs? */ + } + } + } + + return returned_ok; +} + +static int mod_gridsite_server_post_config(apr_pool_t *pPool, + apr_pool_t *pLog, apr_pool_t *pTemp, server_rec *main_server) +{ + SSL_CTX *ctx; + SSLSrvConfigRec *sc; + server_rec *this_server; + + ap_add_version_component(pPool, + apr_psprintf(pPool, "mod_gridsite/%s", VERSION)); + + for (this_server = main_server; + this_server != NULL; + this_server = this_server->next) + { + sc = ap_get_module_config(this_server->module_config, &ssl_module); + + if ((sc != NULL) && + (sc->enabled) && + (sc->server != NULL) && + (sc->server->ssl_ctx != NULL)) + { + ctx = sc->server->ssl_ctx; + + /* in 0.9.7 we could set the issuer-checking callback directly */ +// ctx->cert_store->check_issued = GRST_X509_check_issued_wrapper; + + /* but in case 0.9.6 we do it indirectly with another wrapper */ + SSL_CTX_set_cert_verify_callback(ctx, + GRST_verify_cert_wrapper, + (void *) NULL); + + /* whatever version, we can set the SSLVerify wrapper properly */ + SSL_CTX_set_verify(ctx, ctx->verify_mode, + GRST_callback_SSLVerify_wrapper); + + if (main_server->loglevel >= APLOG_DEBUG) + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, main_server, + "Set mod_ssl verify callbacks to GridSite wrappers"); + } + } + + return OK; +} + +static void mod_gridsite_child_init(apr_pool_t *pPool, server_rec *pServer) +{ + GRSTgaclInit(); +} + +static int mod_gridsite_handler(request_rec *r) +{ + mod_gridsite_cfg *conf; + + conf = (mod_gridsite_cfg *) + ap_get_module_config(r->per_dir_config, &gridsite_module); + + if ((conf->dnlistsuri != NULL) && + (strncmp(r->uri, conf->dnlistsuri, strlen(conf->dnlistsuri)) == 0)) + { + if (strcmp(r->uri, conf->dnlistsuri) == 0) + return mod_gridsite_dnlistsuri_dir_handler(r, conf); + + return mod_gridsite_dnlistsuri_handler(r, conf); + } + + if (strcmp(r->handler, DIR_MAGIC_TYPE) == 0) + return mod_gridsite_dir_handler(r, conf); + + return mod_gridsite_nondir_handler(r, conf); +} + +static void register_hooks(apr_pool_t *p) +{ + ap_hook_post_config(mod_gridsite_server_post_config, NULL, NULL, + APR_HOOK_LAST); + ap_hook_child_init(mod_gridsite_child_init, NULL, NULL, APR_HOOK_MIDDLE); + + ap_hook_fixups(mod_gridsite_first_fixups,NULL,NULL,APR_HOOK_FIRST); + + ap_hook_fixups(mod_gridsite_perm_handler,NULL,NULL,APR_HOOK_REALLY_LAST); + + ap_hook_handler(mod_gridsite_handler, NULL, NULL, APR_HOOK_FIRST); +} + +module AP_MODULE_DECLARE_DATA gridsite_module = +{ + STANDARD20_MODULE_STUFF, + create_gridsite_dir_config, /* dir config creater */ + merge_gridsite_dir_config, /* dir merger */ + NULL, /* server config */ + NULL, /* merge server config */ + mod_gridsite_cmds, /* command apr_table_t */ + register_hooks /* register hooks */ +}; diff --git a/org.gridsite.core/src/mod_ssl-private.h b/org.gridsite.core/src/mod_ssl-private.h new file mode 100644 index 0000000..bcf759a --- /dev/null +++ b/org.gridsite.core/src/mod_ssl-private.h @@ -0,0 +1,106 @@ +/* + Copyright (c) 2003-4, Andrew McNab, University of Manchester + All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + o Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + o Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +/* + + Portions of this code are derived from Apache mod_ssl, and are covered + by the Apache Software License: + + * Copyright 2001-2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*---------------------------------------------------------------------------* + * This program is part of GridSite: http://www.gridpp.ac.uk/gridsite/ * + *---------------------------------------------------------------------------*/ + + +/* + * After 2.0.49, Apache mod_ssl has most of the mod_ssl structures defined + * in ssl_private.h, which is not installed along with httpd-devel (eg in + * the FC2 RPM.) This include file provides SIMPLIFIED structures for use + * by mod_gridsite: for example, pointers to unused structures are replaced + * by void * and some of the structures are truncated when only the early + * members are used. + * + * CLEARLY, THIS WILL BREAK IF THERE ARE MAJOR CHANGES TO ssl_private.h!!! + */ + +#include + +typedef enum { + SSL_SHUTDOWN_TYPE_UNSET, + SSL_SHUTDOWN_TYPE_STANDARD, + SSL_SHUTDOWN_TYPE_UNCLEAN, + SSL_SHUTDOWN_TYPE_ACCURATE +} ssl_shutdown_type_e; + +typedef struct { + SSL *ssl; + const char *client_dn; + X509 *client_cert; + ssl_shutdown_type_e shutdown_type; + const char *verify_info; + const char *verify_error; + int verify_depth; + int is_proxy; + int disabled; + int non_ssl_request; +} SSLConnRec; + +typedef struct { + void *sc; /* pointer back to server config */ + SSL_CTX *ssl_ctx; +} modssl_ctx_t; + +typedef struct { + void *mc; + unsigned int enabled; + unsigned int proxy_enabled; + const char *vhost_id; + int vhost_id_len; + int session_cache_timeout; + modssl_ctx_t *server; + modssl_ctx_t *proxy; +} SSLSrvConfigRec; + +extern module AP_MODULE_DECLARE_DATA ssl_module; diff --git a/org.gridsite.core/src/real-gridsite-admin.cgi b/org.gridsite.core/src/real-gridsite-admin.cgi new file mode 100644 index 0000000000000000000000000000000000000000..0b34aae2d9027fafada5ce3ebd3381e6ae0d0493 GIT binary patch literal 81444 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 +# Please email me improvements. +# +# You're free to do whatever you want with this script. +# +# Changes: +# +# 0.3 - Daniel Fandrich brought: +# o deal with .lp lines +# o .TH needs no section portion anymore +# o added generator meta tag in the header +# +# 0.2 - fixed the name for the SH section +# - added links from all words within \fIthis\fP or \fBthis\fP +# that has the same text as a .SH or .IP. +# + +use strict; +#use warnings; + +my $InFH = \*STDIN; +my $OutFH = \*STDOUT; +my $debugFH = \*STDERR; + +my %manpage; +my @out; + +my $indentlevel=0; # logical levels, not columns +my @p; +my $within_tp; +my $standalone=1; # by default we make stand-alone HTML pages +my $pre; +my %anchor; # hash with all anchors + +while($ARGV[0]) { + if($ARGV[0] eq "--bare") { + # don't include headers and stuff + $standalone=0; + shift @ARGV; + } + else { + printf $debugFH "unknown option: %s\n", $ARGV[0] if($ARGV[0] ne "-h"); + print $debugFH "Usage: roffit [options] < infile > outfile\n", + "Options:\n", + " --bare Do not put in HTML, HEAD, BODY tags\n"; + exit; + } +} + +sub showp { + my @p = @_; + push @out, "\n

", @p; +} + +sub defaultcss { + print $OutFH < +P.level0 { + padding-left: 2em; +} + +P.level1 { + padding-left: 4em; +} + +P.level2 { + padding-left: 6em; +} + +span.emphasis { + font-style: italic; +} + +span.bold { + font-weight: bold; +} + +span.manpage { + font-weight: bold; +} + +h2.nroffsh { + background-color: #e0e0e0; +} + +span.nroffip { + font-weight: bold; + font-size: 120%; + font-family: monospace; +} + +p.roffit { + text-align: center; + font-size: 80%; +} + +ENDOFCSS + ; +} + +sub text2name { + my ($text) = @_; + $text =~ s/^ *([^ ]*).*/$1/g; + $text =~ s/[^a-zA-Z0-9-]//g; + return $text; +} + +# scan through the file and check for sections we should convert +# to proper links +sub linkfile { + my @new; + for(@out) { + my $line=$_; + my $l; + while($line =~ s/([^<]*)<\/span>/[]/) { + my ($style, $name)=($1, $2); + + $l = text2name($name); + + #printf $debugFH "$style - $name - %s - %d\n", + #$l, $anchor{$l}; + + my $link; + if($anchor{$l}) { + $link="$name"; + } + else { + $link="$name"; + } + $line =~ s/\[\]/$link/; + } + push @new, $line; + } + return @new; +} + +sub parsefile { + + while(<$InFH>) { + my $in = $_; + my $out; + # print $debugFH "DEBUG IN: $_"; + + $in =~ s/[\r\n]//g if(!$pre); # tear off newlines + + if($in =~ /^\.([^ \n]*)(.*)/) { + # this is a line starting with a dot, that means it is special + my ($keyword, $rest) = ($1, $2); + $out = ""; + + # cut off initial spaces + $rest =~ s/^ +//g; + + if($keyword eq "\\\"") { + # this is a comment, skip this line + } + elsif($keyword =~ /^TH$/i) { + # man page header: + # curl 1 "22 Oct 2003" "Curl 7.10.8" "Curl Manual" + # NAME SECTION DATE VERSION MANUAL + if($rest =~ /([^ ]*) (\d+) \"([^\"]*)\" \"([^\"]*)\"(\"([^\"]*)\")?/) { + # strict matching only so far + $manpage{'name'} = $1; + $manpage{'section'} = $2; + $manpage{'date'} = $3; + $manpage{'version'} = $4; + $manpage{'manual'} = $6; + } + } + elsif($keyword =~ /^SH$/i) { + # Section Header + showp(@p); + @p=""; + if($pre) { + push @out, "\n"; + $pre = 0; + } + + my $name = text2name($rest); + $anchor{$name}=1; + + $rest =~ s/\"//g; # cut off quotes + $rest =~ s//>/g; + $out = "

$rest

"; + $indentlevel=0; + $within_tp=0; + } + elsif(($keyword =~ /^B$/i) || ($keyword =~ /^BI$/i)) { + # Make B and BI the same for simplicity + $rest =~ s/\"//g; # cut off quotes + $rest =~ s//>/g; + push @p, "$rest "; + } + elsif($keyword =~ /^I$/i) { + $rest =~ s/\"//g; # cut off quotes + $rest =~ s//>/g; + push @p, "$rest "; + } + elsif($keyword =~ /^RS$/i) { + # the start of another indent-level. for inlined tables + # within an "IP" + showp(@p); + @p=""; + $indentlevel++; + } + elsif($keyword =~ /^RE$/i) { + # end of the RS section + showp(@p); + @p=""; + $indentlevel--; + } + elsif($keyword =~ /^NF$/i) { + # We let nf start a
 section
+                showp(@p);
+                @p="";
+                push @out, "
\n";
+                $pre=1
+            }
+            elsif($keyword =~ /^TP$/i) {
+                # Used within an "RS" section to make a new line. The first
+                # TP as a column indicator, but we decide to do that
+                # controlling in the CSS instead.
+                $within_tp=1;
+                showp(@p);
+                @p="";                
+            }
+            elsif($keyword =~ /^IP$/i) {
+                # start of a new paragraph coming up
+                showp(@p);
+                @p="";
+
+                my $name= text2name($rest);
+                $anchor{$name}=1;
+
+                $rest =~ s/\"//g; # cut off quotes
+                $rest =~ s//>/g;
+                
+                $indentlevel-- if ($indentlevel);
+                push @p, "$rest ";
+                # make this a single-line title
+                showp(@p);
+                @p="";
+                $indentlevel++;
+                $within_tp=0;
+            }
+            elsif($keyword =~ /^ad$/i) {
+                showp(@p);
+                @p="";
+            }
+            elsif($keyword =~ /^sp$/i) {
+                showp(@p);
+                @p="";
+            }
+            elsif($keyword =~ /^lp$/i) {
+                # marks end of a paragraph
+                showp(@p);
+                @p="";
+            }
+            elsif($keyword =~ /^pp$/i) {
+                # PP ends a TP section, but some TP sections don't use it
+                $within_tp=0;
+            }
+            elsif($keyword =~ /^so$/i) {
+                # This keyword refers to a different man page, named in the
+                # $rest.
+                # We don't support this
+                push @out, "See the $rest man page.\n";
+            }
+            elsif($keyword =~ /^BR$/i) {
+                # I'm not sure what this does exactly, but this is commonly
+                # used to include pointers to other man pages. Let's assume
+                # it only does that for now.
+                # blabla (3)
+                # or "blabla (3)"
+                # or strcmp "(3), " strcasecmp "(3)"
+                # etc
+                
+                $rest =~ s/\"//g; # cut off quotes
+                my @all = split /,/, $rest;
+                for(@all) {
+                    if(/([^ ]*) *\((\d+)\)/) {
+                        # TODO: this looks like a man page, check if there's a
+                        # HTML file for it and if so make a link to it
+                    }
+
+                    push @p, "$_ ";
+                }
+            }
+            else {
+                showp(@p);
+                print $debugFH "ALERT: unknown keyword \"$keyword\"\n";
+            }
+        }
+        else {
+            # text line, decode \-stuff
+            my $txt = $in;
+
+            $txt =~ s//>/g;
+            $txt =~ s/\\&//g; # cut off \&
+            $txt =~ s/\\fI//g;
+            $txt =~ s/\\fB//g;
+            $txt =~ s/\\fP/<\/span>/g;
+            $txt =~ s/\\//g;
+
+            if($txt =~ /^[ \t\r\n]*$/) {
+                # no contents, marks end of a paragraph
+                showp(@p);
+                @p="";
+            }
+            else {
+                $txt =~ s/^ /\ \;/g;
+                push @p, "$txt ";
+            }
+            $out ="";
+        }
+
+        if($out) {
+            push @out, $out;
+   #         print $debugFH "DEBUG OUT: $out\n";
+        }
+        else {
+   #         print $debugFH "DEBUG OUT: [withheld]\n";
+        }
+    }
+    showp(@p);
+}
+
+parsefile();
+
+my @conv = linkfile();
+
+my $title=sprintf("%s man page",
+                  $manpage{'name'}?$manpage{'name'}:"secret");
+
+if($standalone) {
+    print $OutFH <
+$title
+
+MOO
+    ;
+    defaultcss();
+    print "\n";
+}
+
+print $OutFH @conv;
+print $OutFH <
+ This HTML page was made with roffit.
+ROFFIT
+    ;
+
+if($standalone) {
+    print "\n";
+}
diff --git a/org.gridsite.core/src/urlencode.c b/org.gridsite.core/src/urlencode.c
new file mode 100644
index 0000000..8890689
--- /dev/null
+++ b/org.gridsite.core/src/urlencode.c
@@ -0,0 +1,73 @@
+/*
+   Copyright (c) 2002-3, Andrew McNab, University of Manchester
+   All rights reserved.
+
+   Redistribution and use in source and binary forms, with or
+   without modification, are permitted provided that the following
+   conditions are met:
+
+     o Redistributions of source code must retain the above
+       copyright notice, this list of conditions and the following
+       disclaimer. 
+     o Redistributions in binary form must reproduce the above
+       copyright notice, this list of conditions and the following
+       disclaimer in the documentation and/or other materials
+       provided with the distribution. 
+
+   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+   CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+   MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+   BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+   OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+   POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/*------------------------------------------------------------------------*
+ * For more about GridSite: http://www.gridpp.ac.uk/gridsite/             *
+ *------------------------------------------------------------------------*/
+
+#include 
+#include 
+
+#include "gridsite.h"
+
+int main(int argn, char *argv[])
+{
+  int    i;
+
+  if (argn == 1)
+    {
+      puts("urlencode [-m|-d] string-to-encode-or-decode");
+      return 0;
+    }
+
+  if      (strcmp(argv[1], "-d") == 0) /* decode */
+   for (i = 2; i < argn; ++i) 
+      {
+        if (i > 2) fputs(" ", stdout);
+        fputs(GRSThttpUrlDecode(argv[i]), stdout);
+      }
+  else if (strcmp(argv[1], "-m") == 0) /* mild encode */
+   for (i = 2; i < argn; ++i) 
+      {
+        if (i > 2) fputs("%20", stdout);
+        fputs(GRSThttpUrlMildencode(argv[i]), stdout);
+      }
+  else /* standard encode */
+   for (i = 1; i < argn; ++i) 
+      {
+        if (i > 1) fputs("%20", stdout);
+        fputs(GRSThttpUrlEncode(argv[i]), stdout);
+      }
+
+  puts("");
+
+  return 0;
+}
-- 
1.8.2.3