First version of this file
authorAlberto Di Meglio <alberto.di.meglio@cern.ch>
Tue, 26 Oct 2004 17:54:24 +0000 (17:54 +0000)
committerAlberto Di Meglio <alberto.di.meglio@cern.ch>
Tue, 26 Oct 2004 17:54:24 +0000 (17:54 +0000)
52 files changed:
org.gridsite.core/CHANGES [new file with mode: 0644]
org.gridsite.core/INSTALL [new file with mode: 0644]
org.gridsite.core/LICENSE [new file with mode: 0644]
org.gridsite.core/README [new file with mode: 0644]
org.gridsite.core/VERSION [new file with mode: 0644]
org.gridsite.core/build.xml [new file with mode: 0644]
org.gridsite.core/doc/admin.html [new file with mode: 0644]
org.gridsite.core/doc/build-apache2.sh [new file with mode: 0644]
org.gridsite.core/doc/config.html [new file with mode: 0644]
org.gridsite.core/doc/gacl.html [new file with mode: 0644]
org.gridsite.core/doc/htcp.1 [new file with mode: 0644]
org.gridsite.core/doc/htll.1 [new file with mode: 0644]
org.gridsite.core/doc/htls.1 [new file with mode: 0644]
org.gridsite.core/doc/htmkdir.1 [new file with mode: 0644]
org.gridsite.core/doc/htrm.1 [new file with mode: 0644]
org.gridsite.core/doc/httpd-fileserver.conf [new file with mode: 0644]
org.gridsite.core/doc/httpd-webserver.conf [new file with mode: 0644]
org.gridsite.core/doc/index.html [new file with mode: 0644]
org.gridsite.core/doc/install.html [new file with mode: 0644]
org.gridsite.core/doc/library.html [new file with mode: 0644]
org.gridsite.core/doc/module.html [new file with mode: 0644]
org.gridsite.core/doc/urlencode.1 [new file with mode: 0644]
org.gridsite.core/doc/user.html [new file with mode: 0644]
org.gridsite.core/interface/gridsite-gacl.h [new file with mode: 0644]
org.gridsite.core/interface/gridsite.h [new file with mode: 0644]
org.gridsite.core/project/build.properties [new file with mode: 0644]
org.gridsite.core/project/configure.properties.xml [new file with mode: 0644]
org.gridsite.core/project/dependencies.properties [new file with mode: 0644]
org.gridsite.core/project/gridsite.core.csf.xml [new file with mode: 0644]
org.gridsite.core/project/properties.xml [new file with mode: 0644]
org.gridsite.core/project/taskdefs.xml [new file with mode: 0644]
org.gridsite.core/project/version.properties [new file with mode: 0644]
org.gridsite.core/src/Doxyfile [new file with mode: 0644]
org.gridsite.core/src/Makefile [new file with mode: 0644]
org.gridsite.core/src/doxygen.css [new file with mode: 0644]
org.gridsite.core/src/doxyheader.html [new file with mode: 0644]
org.gridsite.core/src/gaclexample.c [new file with mode: 0644]
org.gridsite.core/src/gridsite.spec [new file with mode: 0644]
org.gridsite.core/src/grst_admin.h [new file with mode: 0644]
org.gridsite.core/src/grst_admin_file.c [new file with mode: 0644]
org.gridsite.core/src/grst_admin_gacl.c [new file with mode: 0644]
org.gridsite.core/src/grst_admin_main.c [new file with mode: 0644]
org.gridsite.core/src/grst_gacl.c [new file with mode: 0644]
org.gridsite.core/src/grst_http.c [new file with mode: 0644]
org.gridsite.core/src/grst_x509.c [new file with mode: 0644]
org.gridsite.core/src/htcp [new file with mode: 0644]
org.gridsite.core/src/htcp.c [new file with mode: 0644]
org.gridsite.core/src/mod_gridsite.c [new file with mode: 0644]
org.gridsite.core/src/mod_ssl-private.h [new file with mode: 0644]
org.gridsite.core/src/real-gridsite-admin.cgi [new file with mode: 0644]
org.gridsite.core/src/roffit [new file with mode: 0755]
org.gridsite.core/src/urlencode.c [new file with mode: 0644]

diff --git a/org.gridsite.core/CHANGES b/org.gridsite.core/CHANGES
new file mode 100644 (file)
index 0000000..c7c17d5
--- /dev/null
@@ -0,0 +1,114 @@
+* Thu Jul 22 2004 Andrew McNab <mcnab@hep.man.ac.uk>
+- ==== GridSite version 1.0.4 ====
+* Mon Jul 19 2004 Andrew McNab <mcnab@hep.man.ac.uk>
+- Changes in line with EGEE SCM - most importantly
+  the top level directory becomes org.gridsite.core
+* Mon Jul 19 2004 Andrew McNab <mcnab@hep.man.ac.uk>
+- ==== GridSite version 1.0.3 ====
+* Mon Jun 28 2004 Andrew McNab <mcnab@hep.man.ac.uk>
+- 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 <mcnab@hep.man.ac.uk>
+- ==== GridSite version 1.0.2 ====
+* Sun Jun 27 2004 Andrew McNab <mcnab@hep.man.ac.uk>
+- Fix for Bug #2860 (so can now read DN Lists over
+  HTTPS when have no user certificate if relevant
+  .gacl gives <read> permission but not <list>)
+- Include gridsite-gacl.h mods from Daniel Kouril
+  <kouril@ics.muni.cz> to fix faulty definitions
+  of GACLnewEntry() and GACLnewAcl() and to make
+  a legacy non-static GACLparseEntry() wrapper.
+* Thu Jun 17 2004 Andrew McNab <mcnab@hep.man.ac.uk>
+- 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 <mcnab@hep.man.ac.uk>
+- Incorporate EGEE CVS layout changes in production
+  branch.
+* Wed Jun 9 2004 Andrew McNab <mcnab@hep.man.ac.uk>
+- ==== GridSite version 1.0.1 ====
+* Sun Dec 14 2003 Andrew McNab <mcnab@hep.man.ac.uk>
+- 1.0.0 is first full production release
+  (development now in 1.1.x branch)
+* Sun Dec 14 2003 Andrew McNab <mcnab@hep.man.ac.uk>
+- ==== GridSite version 1.0.0 ====
+* Sat Dec 13 2003 Andrew McNab <mcnab@hep.man.ac.uk>
+- 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 <mcnab@hep.man.ac.uk>
+- ==== GridSite version 0.9.11 ====
+* Thu Dec 11 2003 Andrew McNab <mcnab@hep.man.ac.uk>
+- Simplify checking of cert/proxy chain in 
+  mod_ssl-gridsite: rely on mod_ssl/OpenSSL more.
+* Wed Dec 2 2003 Andrew McNab <mcnab@hep.man.ac.uk>
+- ==== GridSite version 0.9.10 ====
+* Tue Dec 1 2003 Andrew McNab <mcnab@hep.man.ac.uk>
+- GACL ignores leading/trailing spaces in values.
+* Sat Nov 29 2003 Andrew McNab <mcnab@hep.man.ac.uk>
+- 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 <mcnab@hep.man.ac.uk>
+- ==== GridSite version 0.9.8 ====
+* Thu Nov 27 2003 Andrew McNab <mcnab@hep.man.ac.uk>
+- Shiv's updated GACL editor, with redirects.
+* Wed Nov 26 2003 Andrew McNab <mcnab@hep.man.ac.uk>
+- 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 <mcnab@hep.man.ac.uk>
+- ==== GridSite version 0.9.7 ====
+* Thu Nov 20 2003 Andrew McNab <mcnab@hep.man.ac.uk>
+- Major updates to htcp (htrm/htls/htll)
+- GACL now recurses subdirectories when examining
+  the DN List directories path.
+* Sat Nov 15 2003 Andrew McNab <mcnab@hep.man.ac.uk>
+- ==== GridSite version 0.9.6 ====
+* Fri Nov 14 2003 Andrew McNab <mcnab@hep.man.ac.uk>
+- Function call fixes in grst-admin.cgi
+* Thu Nov 13 2003 Andrew McNab <mcnab@hep.man.ac.uk>
+- Add htcp (curl-url-get reborn)
+* Thu Nov 13 2003 Andrew McNab <mcnab@hep.man.ac.uk>
+- ==== GridSite version 0.9.5 ====
+* Thu Nov 13 2003 Andrew McNab <mcnab@hep.man.ac.uk>
+- 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 <mcnab@hep.man.ac.uk>
+- One RPM instead of three, with version from VERSION
+- Textarea for HTML/Text editing now 80 columns
+* Mon Nov 10 2003 Andrew McNab <mcnab@hep.man.ac.uk>
+- Add delegation level and GridSiteGSIProxyLimit
+  support.
+- Add GridSiteAdminList handling to mod_gridsite
+  and real-gridsite-admin.cgi
+* Sun Nov 9 2003 Andrew McNab <mcnab@hep.man.ac.uk>
+- 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 <mcnab@hep.man.ac.uk>
+- 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 <mcnab@hep.man.ac.uk>
+- Include GACL editor in real-gridsite-admin.cgi
+  from Shiv Kaushal <shiv@hep.man.ac.uk>
+* Sun Oct 26 2003 Andrew McNab <mcnab@hep.man.ac.uk>
+- Reorganise into a single build tree, including
+  Apache 2.0 .h files to remove circular dependency.
+* Sun Oct 26 2003 Andrew McNab <mcnab@hep.man.ac.uk>
+- ==== GridSite version 0.9.4 ====
+* Sun Oct 19 2003 Andrew McNab <mcnab@hep.man.ac.uk>
+- Include many pieces of GridSite code from 0.3.x (CGI)
+  fileGridSite and mod_gridsite 0.9.0
+* Sun Oct 19 2003 Andrew McNab <mcnab@hep.man.ac.uk>
+- ==== GridSite version 0.9.3 ====
diff --git a/org.gridsite.core/INSTALL b/org.gridsite.core/INSTALL
new file mode 100644 (file)
index 0000000..68e150a
--- /dev/null
@@ -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 (file)
index 0000000..ce67433
--- /dev/null
@@ -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 (file)
index 0000000..5fbb1db
--- /dev/null
@@ -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 (file)
index 0000000..10c4a0f
--- /dev/null
@@ -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 (file)
index 0000000..3ade883
--- /dev/null
@@ -0,0 +1,195 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+       Copyright (c) 2004 on behalf of the EU EGEE Project: 
+       The European Organization for Nuclear Research (CERN), 
+       Istituto Nazionale di Fisica Nucleare (INFN), Italy
+       Datamat Spa, Italy
+       Centre National de la Recherche Scientifique (CNRS), France
+       CS Systeme d'Information (CSSI), France
+       Royal Institute of Technology, Center for Parallel Computers (KTH-PDC), Sweden
+       Universiteit van Amsterdam (UvA), Netherlands
+       University of Helsinki (UH.HIP), Finland
+       University of Bergen (UiB), Norway
+       Council for the Central Laboratory of the Research Councils (CCLRC), United Kingdom
+
+       Build file for the Gridsite Core Subsystem
+       
+       Authors: Alberto Di Meglio <alberto.di.meglio@cern.ch>          
+       Version info: $Id$
+       Release: $Name$
+
+       Revision history:
+       $Log$
+-->
+
+<project name="gridsite-core" default="dist">
+
+       <description>
+               Ant build file to build the Gridsite Core Component
+       </description>
+       
+       <!-- =========================================
+            Import properties (order is important) 
+            ========================================= -->
+       
+       <!-- Import baseline & user properties -->
+       <import file="../org.glite/project/baseline.properties.xml" />
+
+       <!-- Import subsystem build properties, 
+                             subsystem properties & 
+                             subsystem common properties -->
+       <import file="./project/properties.xml" />
+
+       <!-- Import global build properties and global properties -->
+       <import file="${global.properties.file}" />
+                               
+       <!-- =========================================
+                Load dependencies properties files (order is important) 
+            ========================================= -->
+       <property file="${user.dependencies.file}"/>
+       <property file="${subsystem.dependencies.file}"/>
+       <property file="${global.dependencies.file}"/>  
+       
+       <!-- =========================================
+                Load configure options
+               ========================================= -->
+        <import file="${global.configure.options.file}"/>
+               <import file="${component.configure.options.file}"/>
+               
+       <!-- =========================================
+                Import global task definitions
+            ========================================= -->
+       <import file="${global.taskdefs.file}" />
+       
+       <!-- =========================================
+            Import global compiler definitions
+            ========================================= -->
+       <import file="${global.compilerdefs.file}" />
+               
+       <!-- =========================================
+                Import targets 
+            ========================================= -->
+       <import file="${global.targets-common.file}" />
+       
+       <!-- =========================================
+                Load version file 
+            ========================================= -->
+       <property file="${module.version.file}"/>
+       
+       <!-- ===============================================
+                 Public common targets
+            =============================================== -->
+       
+       <target name="localinit" depends="envcheck">
+               <mkdir dir="${stage.dir}" />
+               <mkdir dir="${dist.dir}" />
+       </target>
+
+       <target name="init" depends="localinit">
+       </target>
+
+       <target name="checkstyle" depends="init">
+       </target>
+
+       <target name="compile" depends="checkstyle">
+               <if>
+                       <isset property="build.make.arguments"/>         
+                       <then>
+                               <!-- Call make default compile target -->       
+                               <make target="build" dir="${module.src.dir}" failonerror="${failonerror}" args="${build.make.arguments}"/>
+                       </then>          
+                       <else>   
+                               <!-- Call make default compile target -->       
+                               <make target="build" dir="${module.src.dir}" failonerror="${failonerror}"/>
+                       </else>          
+               </if>    
+       </target>
+
+       <target name="compiletest" depends="compile">
+       </target>
+
+       <target name="unittest" depends="compiletest">
+       </target>
+
+       <target name="unitcoverage" depends="unittest">
+       </target>
+
+       <target name="doc" depends="unitcoverage">
+       </target>
+
+    <target name="stage" depends="doc">
+               <if>
+                       <isset property="build.make.arguments"/>         
+                       <then>
+                               <!-- Call make default compile target -->       
+                               <make target="install" dir="${module.src.dir}" failonerror="${failonerror}" args="${build.make.arguments}"/>
+                       </then>          
+                       <else>   
+                               <!-- Call make default compile target -->       
+                               <make target="install" dir="${module.src.dir}" failonerror="${failonerror}"/>
+                       </else>          
+               </if>    
+    </target>
+
+       <target name="dist" depends="stage">
+               <make target="rpm" dir="${module.src.dir}" failonerror="${failonerror}"/>
+               <exec dir="${module.dir}/RPMTMP/BUILDROOT/usr" executable="tar">
+                       <arg line="-czf ${module.dir}/gridsite-${module.version}_bin.tar.gz ." />
+               </exec>
+               <copy file="gridsite-${module.version}_bin.tar.gz" todir="${dist.dir}"/>
+               <copy file="gridsite-${module.version}.src.tar.gz" tofile="${dist.dir}/gridsite-${module.version}_src.tar.gz"/>
+               <copy todir="${dist.dir}/rhel30/1386/RPMS">
+                       <fileset dir="${module.dir}/RPMTMP/RPMS/i386">
+                               <include name="*.rpm"/>
+                       </fileset>
+               </copy>
+               <delete>
+                       <fileset dir="${module.dir}">
+                               <include name="*.tar.gz"/>
+                       </fileset>
+               </delete>
+               <delete dir="RPMTMP"/>
+       </target>
+
+       <target name="install" depends="localinit">
+               <make target="install" dir="${module.src.dir}" failonerror="${failonerror}"/>
+       </target>
+
+       <target name="all" depends="dist">
+       </target>
+
+       <target name="clean" depends="envcheck">
+               <property name="offline.repository" value="true" />
+               <make target="clean" dir="${module.src.dir}" failonerror="false"/>
+               <delete dir="${module.dir}/src/doxygen"/>
+               <delete>
+                       <fileset dir="${module.dir}/src">
+                               <include name="*.o"/>
+                               <include name="*.so"/>
+                               <include name="*.so.*"/>
+                               <include name="*.a"/>
+                       </fileset>
+               </delete>
+       </target>
+
+       <target name="cleanAll" depends="clean"/>
+
+       <!-- ===============================================
+                Private targets
+            =============================================== -->
+       
+       <!-- ===============================================
+                Modules proxy targets
+            =============================================== -->
+
+       <!-- component targets definitions tag = do not remove = -->
+
+
+       <!-- Main proxy -->
+       <target name="buildmodules" depends="">
+               <echo append="true" file="${global.project.dir}/cruisecontrol-stub.xml">
+       &lt;project name="${subsystem.name}" type="post-subsystem" packageName="gridsite-${subsystem.prefix}"/&gt;
+               </echo>
+       </target>
+
+</project>
diff --git a/org.gridsite.core/doc/admin.html b/org.gridsite.core/doc/admin.html
new file mode 100644 (file)
index 0000000..1f7f422
--- /dev/null
@@ -0,0 +1,103 @@
+<title>GridSite Admin Guide</title>
+<body>
+<h1 align=center>GridSite Admin Guide</h1>
+
+<p> 
+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.
+
+<p>
+ There is a separate 
+<a href="user.html">User Guide</a>
+ 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. 
+
+<p>
+ You may also find the 
+<a href="config.html">Config Guide</a> 
+ 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.
+
+<h2>Groups and DN Lists</h2>
+
+<p>
+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.)
+
+<p>
+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/&nbsp;)
+
+<p>
+If you have permission to modify a DN List, you can start changing it by
+going to /dn-lists/ (via HTTPS), using the &quot;Manage directory&quot;
+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.)
+
+<p>
+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.)
+
+<h2>Access Control Lists</h2>
+
+<p>
+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.
+
+<p>
+The GridSite <a href="gacl.html">GACL Reference</a> 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.
+
+<p>
+If you have this permission in a given directory, when you view directory
+listings or files in that directory you will see the option &quot;Manage
+Directory&quot; 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.
+
+<p>
+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.
+
+<p>
+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.)
+
+<p>
+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.)
+
+</body>
diff --git a/org.gridsite.core/doc/build-apache2.sh b/org.gridsite.core/doc/build-apache2.sh
new file mode 100644 (file)
index 0000000..f1246d4
--- /dev/null
@@ -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 (file)
index 0000000..6e747e2
--- /dev/null
@@ -0,0 +1,192 @@
+<title>GridSite Config Guide</title>
+<body>
+<h1 align=center>GridSite Config Guide</h1>
+
+<p> 
+This Guide is intended for webmasters setting up 
+<a href="http://www.gridpp.ac.uk/">GridSite</a> with an Apache 2.0
+webserver. We assume you have root access to the server machine to do this.
+There is a separate <a href="admin.html">Admin Guide</a> 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.
+
+<h2>Installation</h2>
+
+<p>
+We assume you have installed Apache 2.0 and GridSite, using the
+<a href="install.html">Building and Installation Guide</a> 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.
+
+<p>
+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.
+
+<p>
+GridSite also includes some commands and man pages in /usr/bin and
+/usr/share/man/man1: <a href="urlencode.1.html">urlencode</a> and
+<a href="htcp.1.html">htcp</a>.
+
+<h2>Certificates</h2>
+
+<p>
+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
+<a href="https://datagrid.in2p3.fr/distribution/datagrid/security/">
+https://datagrid.in2p3.fr/distribution/datagrid/security/</a>
+
+<p>
+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.)
+
+<p>
+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 <a href="http://ca.grid-support.ac.uk/">UK e-Science
+CA</a>) 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 <b>not</b> /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.
+
+<p>
+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:
+<pre>
+openssl pkcs12 -in ck.p12 -clcerts -nokeys -out hostcert.pem
+openssl pkcs12 -in ck.p12 -nodes  -nocerts -out hostkey.pem
+</pre>
+
+<p>
+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):
+
+<pre>
+chown root.root hostkey.pem hostcert.pem
+chmod 400 hostkey.pem
+chmod 444 hostcert.pem
+</pre>
+
+<h2>httpd.conf</h2>
+
+<p>
+/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.
+
+<p>
+The easiest way to get started is to examine the example httpd.conf files we
+provide. 
+
+<!--
+virtual servers
+directory sections
+order of loadable modules
+-->
+
+<h2>httpd-fileserver.conf</h2>
+
+<p>
+<a href="httpd-fileserver.conf">httpd-fileserver.conf</a> 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.
+
+<h2>httpd-webserver.conf</h2>
+
+<p>
+<a href="httpd-webserver.conf">httpd-webserver.conf</a> 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.
+
+<h2>GridSite Directives</h2>
+
+<p>
+The <a href="module.html">mod_gridsite reference</a> lists all the GridSite
+httpd.conf directives. 
+
+<p>
+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 &lt;Directory&gt; section:
+
+<p>
+GridSiteMethods GET PUT DELETE
+
+<p>
+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.)
+
+<h2>GACL access control</h2>
+
+<p>
+The <a href="gacl.html">GACL reference</a> 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.
+
+<p>
+For example, to give all clients read and list permission:
+<p>
+<pre>
+&lt;gacl&gt;
+&lt;entry&gt;
+  &lt;any-user/&gt;
+  &lt;allow&gt;&lt;read/&gt;&lt;list/&gt;&lt;/allow&gt;
+&lt;/entry&gt;
+&lt;/gacl&gt;
+</pre>
+
+<p>
+To enable writing, add DN List, Person or VOMS entries to the file.
+For example:
+
+<p>
+<pre>
+&lt;gacl&gt;
+&lt;entry&gt;
+  &lt;any-user/&gt;
+  &lt;allow&gt;&lt;read/&gt;&lt;list/&gt;&lt;/allow&gt;
+&lt;/entry&gt;
+&lt;entry&gt;
+  &lt;person&gt;
+  &lt;dn&gt;/C=UK/O=eScience/OU=Manchester/L=HEP/CN=Andrew McNab&lt;/dn&gt;
+  &lt;/person&gt;
+  &lt;allow&gt;&lt;write/&gt;&lt;/allow&gt;
+&lt;/entry&gt;
+&lt;/gacl&gt;
+</pre>
+
+<p>
+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.
+
+<!--
+<h2>DN Lists</h2>
+<h2>gridsite-admin.cgi</h2>
+<h2>Other CGI</h2>
+-->
+
+</body>
diff --git a/org.gridsite.core/doc/gacl.html b/org.gridsite.core/doc/gacl.html
new file mode 100644 (file)
index 0000000..12e58c7
--- /dev/null
@@ -0,0 +1,84 @@
+<title>GridSite: Grid Access Control Language</title>
+<body>
+<h1 align=center>GridSite: Grid Access Control Language</h1>
+
+<p> 
+GACL is the authorization policy language used by 
+<a href="http://www.gridpp.ac.uk/gridsite/">GridSite</a> 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.
+
+<p>
+GridSite both uses GACL policies and provides a GACL manipulation API for
+C/C++ in the GridSite library.
+
+<h2>Credentials</h2>
+
+<p>
+In GridSite 1.0.x, four credential types are supported:
+
+<p>
+&lt;person&gt;
+&lt;dn&gt;/O=Grid/CN=Name&lt;/dn&gt;
+&lt;/person&gt;
+
+<p>
+&lt;voms&gt;
+&lt;fqan&gt;/vo.dom.ain/group&lt;/fqan&gt;
+&lt;/voms&gt;
+
+<p>
+&lt;dn-list&gt;
+&lt;url&gt;https://www.vo.dom.ain/dn-lists/group&lt;/url&gt;
+&lt;/dn-list&gt;
+
+<p>
+&lt;dns&gt;
+&lt;hostname&gt;host*.dom.ain&lt;/hostname&gt;
+&lt;/dns&gt;
+
+<h2>Permissions</h2>
+
+<p>
+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.
+
+<p>
+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.)
+
+<p>
+In GACL files, the permissions are represented by single tags:
+&lt;admin/&gt;, &lt;write/&gt;, &lt;list/&gt;, &lt;exec/&gt;, &lt;read/&gt;.
+Permission
+tags are contained within Allow or Deny blocks. For example: 
+&lt;allow&gt;&lt;read/&gt;&lt;list/&gt;&lt;/allow&gt; or 
+&lt;deny&gt;&lt;admin/&gt;&lt;/deny&gt;.
+
+<h2>Entries</h2>
+
+<p>
+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.)
+
+<h2>Access Control Lists</h2>
+
+<p>
+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.)
+
+</body>
diff --git a/org.gridsite.core/doc/htcp.1 b/org.gridsite.core/doc/htcp.1
new file mode 100644 (file)
index 0000000..05b0718
--- /dev/null
@@ -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 <X.509 cert path>  and  --key <X.509 key path>"
+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 <X.509 CA root certs directory or file>"
+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 <mcnab@hep.man.ac.uk>
+
+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 (file)
index 0000000..11a60d1
--- /dev/null
@@ -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 (file)
index 0000000..11a60d1
--- /dev/null
@@ -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 (file)
index 0000000..11a60d1
--- /dev/null
@@ -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 (file)
index 0000000..11a60d1
--- /dev/null
@@ -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 (file)
index 0000000..1b197e7
--- /dev/null
@@ -0,0 +1,145 @@
+##############################################################################
+## GridSite httpd-fileserver.conf - Andrew McNab <mcnab@hep.man.ac.uk>
+##
+## 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:
+##
+## <gacl>
+## <entry>
+##   <any-user/>
+##   <allow><read/><list/></allow>
+## </entry>
+## </gacl>
+##
+## To enable writing, add DN List, Person or VOMS entries to the GACL
+## (see the GridSite GACL document for the syntax.) 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>
+##
+## 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.)
+##
+## 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"
+
+<Directory />
+    AllowOverride None
+</Directory>
+
+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
+<VirtualHost *:80>
+
+<Directory "/var/www/htdocs">
+ GridSiteIndexes       on
+ GridSiteAuth          on
+ GridSiteDNlists       /etc/grid-security/dn-lists/
+</Directory>
+
+</VirtualHost>
+
+######################################################################
+# Secured and possibly authenticated HTTPS on port 443
+######################################################################
+Listen 443
+<VirtualHost *: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
+
+<Directory "/var/www/htdocs">
+ GridSiteIndexes       on
+ GridSiteAuth          on
+ GridSiteDNlists       /etc/grid-security/dn-lists/
+ GridSiteGSIProxyLimit 0
+# GridSiteMethods      GET PUT DELETE
+</Directory>
+</VirtualHost>
diff --git a/org.gridsite.core/doc/httpd-webserver.conf b/org.gridsite.core/doc/httpd-webserver.conf
new file mode 100644 (file)
index 0000000..0662f15
--- /dev/null
@@ -0,0 +1,217 @@
+##############################################################################
+## GridSite httpd-webserver.conf - Andrew McNab <mcnab@hep.man.ac.uk>
+##
+## 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:
+##
+## <gacl>
+## <entry>
+##   <any-user/>
+##   <allow><read/><list/></allow>
+## </entry>
+## </gacl>
+##
+## To enable writing, add DN List, Person or VOMS entries to the GACL
+## (see the GridSite GACL document for the syntax.) 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>
+##
+## 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.)
+##
+## 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"
+
+<Directory />
+    AllowOverride None
+</Directory>
+
+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
+<VirtualHost *: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
+
+<Directory "/var/www/htdocs">
+ ## 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 <body> and </body> 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
+</Directory>
+</VirtualHost>
+
+######################################################################
+# Secured and possibly authenticated HTTPS on port 443
+######################################################################
+Listen 443
+<VirtualHost *: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
+
+<Directory "/var/www/htdocs">
+ ## 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 <body> and </body> 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
+</Directory>
+</VirtualHost>
diff --git a/org.gridsite.core/doc/index.html b/org.gridsite.core/doc/index.html
new file mode 100644 (file)
index 0000000..11aaa86
--- /dev/null
@@ -0,0 +1,92 @@
+<title>GridSite 1.0.x Documentation</title>
+<body>
+<h1 align=center>GridSite 1.0.x Documentation</h1>
+
+<p>
+<a href="http://www.gridpp.ac.uk/gridsite/">GridSite</a>
+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.
+
+<h2>Guides</h2>
+
+<p>
+<dl>
+<dt><b><a href="user.html">User Guide</a></b>
+<dd>End-user documentation for people managing webpages and files on
+    GridSite servers, either through the web interface or with command
+    line clients like htcp.
+<p>
+
+<dt><b><a href="admin.html">Admin Guide</a></b>
+<dd>For people administering areas of GridSite websites or fileservers, or
+    managing GridSite's support for DN List groups.
+<p>
+
+<dt><b><a href="install.html">Building and Installation</a></b>
+<dd>Instructions for building GridSite from source, and installing from
+    binaries or RPMs.
+<p>
+
+<dt><b><a href="config.html">Config Guide</a></b>
+<dd>For webmasters setting up Apache 2.0 and GridSite, and writing the
+    Apache httpd.conf file.
+<p>
+
+<dt><b><a href="httpd-fileserver.conf">httpd-fileserver.conf</a></b> and
+    <b><a href="httpd-webserver.conf">httpd-webserver.conf</a></b>
+<dd>Example configuration files for simple HTTP(S) fileservers and
+    webservers, with explanatory comments.
+<p>
+
+</dl>
+
+<h2>Reference</h2>
+
+<p>
+<dl>
+<dt><b><a href="gacl.html">Grid Access Control Lists</a></b>
+<dd>Syntax and usage of the XML Grid Access Control Lists used by GridSite.
+<p>
+
+<dt><b><a href="htcp.1.html">htcp</a></b> and
+    <b><a href="urlencode.1.html">urlencode</a></b> man pages
+<dd>Command line tools for copying files to or from HTTP(S) servers, and
+    for URL-encoding strings. 
+<p>
+
+<!--
+<dt><b><a href="gridsite-admin.html">gridsite-admin.cgi</a></b>
+<dd>A CGI program providing site administration functions for users with
+    standard web browsers, via HTTPS. gridsite-admin.cgi includes a file
+    manager, support for file uploading, and editors for HTML, text and
+    Grid Access Control List files.
+<p>
+-->
+
+<dt><b><a href="module.html">mod_gridsite</a></b>
+<dd>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. 
+<p>
+
+<!--
+<dt><b><a href="library.html">libgridsite</a></b>
+<dd>The GridSite library provides common functions for other components of
+    the GridSite system, and utilities for programs using CGI, X.509, GSI,
+    VOMS and HTTP. 
+<p>
+-->
+
+<dt><b><a href="gridsite_8h.html">gridsite.h API reference</a></b>
+<dd>A detailed description of the C API provided by libgridsite, generated
+    from the sources by doxygen.
+<p>
+
+</dl>
+
+</body>
diff --git a/org.gridsite.core/doc/install.html b/org.gridsite.core/doc/install.html
new file mode 100644 (file)
index 0000000..6755b1e
--- /dev/null
@@ -0,0 +1,148 @@
+<title>GridSite: Building and Installation Guide</title>
+<body>
+<h1 align=center>GridSite: Building and Installation Guide</h1>
+
+<p>
+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 <a href="config.html">Config Guide</a> 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.
+
+<h2>Installing with RPM</h2>
+
+<p>
+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.
+
+<p>
+We currently distribute GridSite RPMs for RedHat Linux versions 9 and 7.3
+from our download area at 
+<a href="https://www.gridpp.ac.uk/gridsite/download/">
+https://www.gridpp.ac.uk/gridsite/download/</a>
+
+<p>
+<b>RedHat 9</b>:
+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. 
+
+<p>
+<b>RedHat 7.3</b>:
+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 
+<a href="build-apache2.sh">build-apache2.sh</a> 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.
+
+<p>
+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. 
+
+<p>
+With the RPMs installed, you can proceed to the
+<a href="config.html">Config Guide</a>.
+
+<h2>Requirements for building GridSite from source</h2>
+
+<p>
+GridSite is currently only supported on Linux, but should be
+straightforwardly
+portable to other Unix platforms where the GNU build tools are available.
+
+<p>
+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.)
+
+<p>
+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.) 
+
+<h2>Building GridSite with Make</h2>
+
+<p>
+Our download area at
+<a href="https://www.gridpp.ac.uk/gridsite/download/">
+https://www.gridpp.ac.uk/gridsite/download/</a> 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.)
+
+<p>
+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
+<b>MYCFLAGS=-I/usr/local/include/httpd</b> 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.
+
+<p>
+<pre>
+make 
+make install
+</pre>
+
+<p>
+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.)
+
+<h2>Building GridSite with RPM</h2>
+
+<p>
+For RedHat Linux and derivatives, building with RPM is recommended.
+The command <b>make rpm</b> 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.
+
+<p>
+<b>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.</b>
+
+<p>
+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.
+
+<h2>Building Apache 2.0</h2>
+
+<p>
+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 &quot;configure&nbsp;-C&quot; in the .spec file
+and builds the RPMs under the current directory.)
+
+<p>
+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 
+<a href="http://www.gridpp.ac.uk/gridsite/discuss.html">GridSite
+Discussion List</a>, although 
+<a href="http://www.apache.org/">www.apache.org</a> is a better starting
+point for purely Apache problems.
+</body>
diff --git a/org.gridsite.core/doc/library.html b/org.gridsite.core/doc/library.html
new file mode 100644 (file)
index 0000000..28458ae
--- /dev/null
@@ -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 (file)
index 0000000..7f2096e
--- /dev/null
@@ -0,0 +1,271 @@
+<title>GridSite Apache module: mod_gridsite</title>
+<body>
+<h1 align=center>GridSite Apache module: mod_gridsite</h1>
+
+<p> 
+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.
+
+<p>
+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.
+
+<p>
+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:
+
+<p>
+<b>LoadModule gridsite_module /PATH/TO/MODULES/mod_gridsite.so</b>
+
+<p>
+The module's behaviour is then controlled by GridSite... directives within
+Apache &lt;Directory ...&gt; sections, allowing different directories to use
+GridSite features in different ways.
+
+<h2>GridSite directives</h2>
+
+<dl>
+<dt><b>GridSiteIndexes on|off</b>
+<dd>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. 
+    <br>
+    (Default: GridSiteIndexes off)
+<p>
+
+<dt><b>GridSiteIndexHeader file</b>
+<dd>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.) 
+    <br>
+    (Default: none)
+<p>
+
+<dt><b>GridSiteHtmlFormat on|off</b>
+<dd>Determines where HTML pages receive additional formatting before being
+    sent to the client. This includes the &quot;Last modified&quot;,
+    &quot;View page history&quot;, &quot;Switch to HTTP(S)&quot;,
+    &quot;Print View&quot; and &quot;Built with GridSite&quot; footer
+    elements. If header and footer files are found, they will be used too.
+    <br>
+    (Default: GridSiteHtmlFormat off)
+<p>
+
+<dt><b>GridSiteHeadFile file</b><br>
+    <b>GridSiteFootFile file</b>
+<dd>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
+    &lt;body[ ...]&gt; tags; footer files in place of &lt;/body&gt;. (These
+    standard files should each include the appropriate body tag as a
+    replacement.)
+    <br>
+    (Defaults: GridSiteHeadFile gridsitehead.txt, 
+     GridSiteFootFile gridsitefoot.txt)
+<p>
+
+<dt><b>GridSiteAuth on|off</b>
+<dd>Enables GridSite access control features, using 
+    <a href="gacl.html">GACL</a> 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. 
+    <br>
+    (Default: GridSiteAuth off)
+<p>
+
+<dt><b>GridSiteAdminList uri</b>
+<dd>All members of the DN List with name &quot;uri&quot; receive the full set
+    of permissions, irrespective of per-directory .gacl files. People in
+    this group have full control over the whole site.
+    <br>
+    (Default: none)
+<p>
+
+<dt><b>GridSiteGSIProxyLimit limit</b>
+<dd>When using GSI Proxy credentials,
+    proxies with delegation depth greater than &quot;limit&quot; 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.
+    <br>
+    (Default: GridSiteGSIProxyLimit 1)
+<p>
+
+<dt><b>GridSiteMethods [GET] [PUT] [DELETE]</b>
+<dd>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.
+    <br>
+    (Default: GridSite GET)
+<p>
+
+<dt><b>GridSiteDNlists directory1[:directory2[:directory3]...]</b>
+<dd>Sets up the DN List path used by <a href="gacl.html">GACL</a> for
+    evaluating &lt;dn-list&gt; 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.
+    <br>
+    (Default: none)
+<p>
+
+<dt><b>GridSiteDNlistsURI uri</b>
+<dd>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.    
+    <br>
+    (Default: none)
+<p>
+
+<dt><b>GridSiteAdminURI uri</b>
+<dd>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:
+    <br>
+    <b>ScriptAlias /real-gridsite-admin.cgi
+    /PATH/TO/real-gridsite-admin.cgi</b>
+    <br>
+    This URI is always reached by an internal redirection from the value
+    set by GridSiteAdminFile, and is never visible to users.
+    <br>
+    (Default: none)
+<p>
+
+<dt><b>GridSiteAdminFile cgifilename</b>
+<dd>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. 
+    <br>
+    (Default: GridSiteAdminFile gridsite-admin.cgi)
+<p>
+
+<dt><b>GridSiteEnvs on|off</b>
+<dd>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.
+    <br>
+    (Default: GridSiteEnvs on)
+<p>
+
+<dt><b>GridSiteEditable [ext1 [ext2 [ext3] ...]]]</b>
+<dd>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.
+    <br>
+    (Default: GridSiteEditable txt shtml html htm css js php jsp)
+<p>
+
+<dt><b>GridSiteHelpURI uri</b>
+<dd>If set, gives the URI to use for &quot;Website Help&quot; links in HTML
+    page footers.
+    <br>
+    (Default: none)
+<p>
+
+<dt><b>GridSiteLink on|off</b>
+<dd>Turns off the link in the HTML page footers which gives credit to
+    GridSite.
+    <br>
+    (Default: GridSiteLink on)
+<p>
+
+<dt><b>GridSiteUnzip path</b>
+<dd>If &quot;path&quot; 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 &quot;path&quot; must point
+    to the location of the 
+    <a href="http://www.info-zip.org/UnZip.html">unzip</a> binary.
+    <br>
+    (Default: none)
+<p>
+
+</dl>
+
+<h2>Environment variables</h2>
+
+<p>
+The following variables are present in the environment of CGI programs and
+other dynamic content systems if the <b>GridSiteEnvs on</b> directive is
+in effect.
+
+<p>
+<dl>
+<dt><b>GRST_PERM</b>
+<dd>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.)
+<p>
+
+<dt><b>GRST_ADMIN_LIST</b>
+<dd>URI of the DN List, listing people with full admin and write access
+    to the whole site.
+<p>
+
+<dt><b>GRST_GSIPROXY_LIMIT</b>
+<dd>Maximum valid delegation level for GSI Proxies.
+<p>
+
+<dt><b>GRST_DIR_PATH</b>
+<dd>Absolute path in the local filesystem to the directory holding the
+    file being requested.
+<p>
+
+<dt><b>GRST_HELP_URI</b>
+<dd>URI of website help pages set by GridSiteHelpURI directive.
+<p>
+
+<dt><b>GRST_ADMIN_FILE</b>
+<dd>Filename of per-directory ghost gridsite-admin.cgi program. (This is
+    used by real-gridsite-admin.cgi to construct links in its pages.)
+<p>
+
+<dt><b>GRST_EDITABLE</b>
+<dd>Space-separated list of extensions which can safely be edited with a
+    Text/HTML editor.
+<p>
+
+<dt><b>GRST_HEAD_FILE</b> and <b>GRST_FOOT_FILE</b>
+<dd>Filenames of standard header and footer files.
+<p>
+
+<dt><b>GRST_DN_LISTS</b>
+<dd>DN lists search path.
+<p>
+
+<dt><b>GRST_DN_LISTS_URI</b>
+<dd>Directory of virtual URIs used to publish this site's DN Lists.
+<p>
+
+<dt><b>GRST_UNZIP</b>
+<dd>Full path to the unzip binary, used to list and unpack .zip files.
+<p>
+
+<dt><b>GRST_NO_LINK</b>
+<dd>If set, do not include credit links to GridSite in page footers.
+<p>
+
+</dl>
+
+</body>
diff --git a/org.gridsite.core/doc/urlencode.1 b/org.gridsite.core/doc/urlencode.1
new file mode 100644 (file)
index 0000000..47d2b91
--- /dev/null
@@ -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 <mcnab@hep.man.ac.uk>
+
+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 (file)
index 0000000..ae37cdd
--- /dev/null
@@ -0,0 +1,302 @@
+<title>GridSite User Guide</title>
+<body>
+<h1 align=center>GridSite User Guide</h1>
+
+<p><i>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
+<small>/usr/share/doc/gridsite-VERSION/</small>) 
+to somewhere on your website like
+<b>/gridsite-doc/</b> and add <b>GridSiteHelpURI /gridsite-doc/user.html</b>
+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.</i>
+
+<p> 
+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 
+<a href="admin.html">Administration Guide</a>
+ 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. 
+
+<h2>Reading from HTTP and HTTPS servers</h2>
+
+<p>
+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 &quot;http://&quot; or 
+&quot;https://&quot; 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.
+
+<p>
+ 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, 
+<!-- if  GridSiteHelpURI uri  is set --> 
+ and to switch between HTTP and HTTPS versions of the page. Pages may also
+ have a link to the page History, 
+<!-- GridSiteAdminURI uri  must be set and gridsite-admin.cgi working for
+ the history-viewing mechanism to work -->
+ showing the dates of changes to that page and names of its authors.
+
+<p>
+ 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 <a href="http://ca.grid-support.ac.uk/">from their
+ website</a>.
+<!-- if most of your users use one or two CAs, you could add a link to their
+CA root cert loading instructions here -->
+
+<h2>Authenticating</h2>
+
+<p>
+ 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, 
+ <a href="http://ca.grid-support.ac.uk/">from their website</a> has links to
+ the procedure for applying for a certificate from within a web browser.
+<!-- again, a link to your CA would be good here -->
+
+<p>
+A user certificate usually has a version of your name and affiliation as its
+Distinguished Name (DN) - for example,
+&quot;/C=UK/O=eScience/OU=Manchester/L=HEP/CN=Andrew McNab&quot;
+
+<p>
+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.)
+
+<p>
+Browsers want the certificate and private key in the PKCS#12 format, which
+is normally a single file with the extension &quot;.p12&quot;.
+Many programs which are based on OpenSSL, such as Globus and curl, prefer
+the PEM (&quot;.pem&quot;) format for certificates, with separate
+certificate and key files (&quot;usercert.pem&quot; and
+&quot;userkey.pem&quot;, 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:
+<pre>
+openssl pkcs12 -in usercert.pem -inkey userkey.pem -export -out certkey.p12
+</pre>
+
+<p>
+<b>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.</b>
+
+<p>
+ 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 &quot;Switch to HTTP&quot; link
+ present. If GridSite understands your user certificate, it displays a
+ &quot;You are ...&quot; line in the footer. (However, the Apache webserver
+ must also be set up with your CAs root certificate for this to work. The
+ <a href="https://www.gridpp.ac.uk/">GridPP HTTPS home page</a> is set up
+ to recognise a good range of European and North American Grid CAs.)
+<!-- the CA root certificates normally go somewhere like
+/etc/grid-security/certificates and httpd.conf should reflect this with the
+directive   SSLCACertificatePath /etc/grid-security/certificates  -->
+
+<h2>Authorization</h2>
+
+<p>
+ 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 
+<a href="gacl.html">GACL</a>
+ access control files. (The
+<a href="admin.html">Administration Guide</a>
+ 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.)
+
+<h2>Managing Directories and Files</h2>
+
+<p>
+If you have list permission for the directory containing a page, you should
+see an extra link &quot;Manage Directory&quot; 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.
+
+<p>
+The real power of GridSite becomes available if you have write access to a
+directory. In that case, the &quot;Manage Directory&quot; 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.
+
+<p>
+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 &quot;in
+place&quot; and to see the result of your changes immediately, in context.
+
+<p>
+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.
+
+<p>
+ For people with write access, the &quot;Manage Directory&quot; 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.)
+<!-- This needs the  GridSiteUnzip path  directive in httpd.conf -->
+
+<h2>HTML Formatting in GridSite</h2>
+
+<p>
+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.)
+
+<p>
+ 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. 
+<!-- GridSiteHtmlFormat on  turns this on and  GridSiteHeadFile file  and
+GridSiteFootFile file  can change the names of the header and footer. If 
+you change from the defaults, you need to change this paragraph. -->
+
+<p>
+The &lt;body&gt; and &lt;/body&gt; 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 &lt;body&gt;
+or &lt;/body&gt; 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
+&lt;title&gt; can end up after a &lt;body&gt; tag, and can get ignored by
+browsers - so always include &lt;body&gt; ... &lt;/body&gt; in your pages.)
+
+<p>
+This simple system is suprisingly flexible, and allows a variety of top and
+bottom, or sidebar navigation layouts of pages. Since the &lt;body ...&gt;
+tag is under full control of the author of the gridsitehead.txt file,
+backgrounds, colour schemes and style sheets can easily be specified. 
+
+<p>
+For example:
+
+<p>
+<table border=1 cellpadding=3>
+<tr><th>Source</th><th>HTML</th></tr>
+<tr><td>page.html</td><td>&lt;title&gt;PAGE TITLE&lt;/title&gt;</td></tr>
+<tr><td>page.html<br>(replaced)</td><td>&lt;body&gt;</td></tr>
+<tr><td>gridsitehead.txt</td>
+    <td>&lt;body text=blue&gt;<br>
+        Heading text<br>
+        &lt;table border=1&gt;<br>&lt;tr&gt;<br>&lt;td&gt;Standard&lt;br&gt;<br>
+        sidebar&lt;/td&gt;<br>&lt;td&gt;</td></tr>
+<tr><td>page.html</td><td>&lt;p&gt;<br>Page content...</td></tr>
+<tr><td>page.html<br>(replaced)</td><td>&lt;/body&gt;</td></tr>
+<tr><td>gridsitefoot.txt</td><td>&lt;/td&gt;<br>&lt;/tr&gt;<br>
+        &lt;/table&gt;<br>Footer text<br>&lt;/body&gt;</td></tr>
+</table>
+
+<p>
+produces pages with a layout like:
+
+<p>
+<table border=1 cellpadding=3>
+<tr><td colspan=2>Heading text</td></tr>
+<tr><td>Standard<br>sidebar</td><td>Page content...</td><tr>
+<tr><td colspan=2>Footer text</td></tr>
+</table>
+
+<h2>Command line use</h2>
+
+<p>
+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 
+<a href="http://curl.haxx.se/">curl</a> 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.
+
+<p>
+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.)
+
+<p>
+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 <b>id
+-u</b>.) The GSI proxy contains a
+temporary private key and certificate signed by your long-term user
+certificate.
+
+<p>
+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
+<a href="https://datagrid.in2p3.fr/distribution/datagrid/security/">
+https://datagrid.in2p3.fr/distribution/datagrid/security/</a>
+
+<p>
+To upload a file with curl:
+<pre>
+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
+</pre>
+
+<p>
+The equivalent htcp command is:
+<pre>
+htcp /tmp/new.file.txt https://server/new.file.txt
+</pre>
+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 
+<a href="htcp.1.html">htcp(1)</a>  man page.
+
+<p>
+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.)
+
+<p>
+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.
+
+<p>
+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:
+<pre>
+htcp /tmp/new.*.txt https://server/
+</pre>
+
+</body>
diff --git a/org.gridsite.core/interface/gridsite-gacl.h b/org.gridsite.core/interface/gridsite-gacl.h
new file mode 100644 (file)
index 0000000..2eec2fa
--- /dev/null
@@ -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 (file)
index 0000000..9742d15
--- /dev/null
@@ -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 <ssl.h>
+#endif
+
+#ifndef HEADER_CRYPTO_H
+#include <crypto.h>
+#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 (file)
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 (file)
index 0000000..82737d0
--- /dev/null
@@ -0,0 +1,8 @@
+       <!-- ======================================================
+                Define extra properties here ...
+                ====================================================== -->
+
+       <project name="configure options">
+        <property name="build.make.arguments"
+               value="prefix=${stage.abs.dir}"/>
+       </project>
diff --git a/org.gridsite.core/project/dependencies.properties b/org.gridsite.core/project/dependencies.properties
new file mode 100644 (file)
index 0000000..2a7383b
--- /dev/null
@@ -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 (file)
index 0000000..c8f21a7
--- /dev/null
@@ -0,0 +1,255 @@
+<?xml version="1.0"?>
+<!--
+       Copyright (c) 2004 on behalf of the EU EGEE Project: 
+       The European Organization for Nuclear Research (CERN), 
+       Istituto Nazionale di Fisica Nucleare (INFN), Italy
+       Datamat Spa, Italy
+       Centre National de la Recherche Scientifique (CNRS), France
+       CS Systeme d'Information (CSSI), France
+       Royal Institute of Technology, Center for Parallel Computers (KTH-PDC), Sweden
+       Universiteit van Amsterdam (UvA), Netherlands
+       University of Helsinki (UH.HIP), Finland
+       University of Bergen (UiB), Norway
+       Council for the Central Laboratory of the Research Councils (CCLRC), United Kingdom
+
+       GLite Middleware WMS Configuration Specification File
+       
+       Authors: Alberto Di Meglio <alberto.di.meglio@cern.ch>          
+                Joachim Flammer <Joachim.Flammer@cern.ch>
+       Version info: $Id$
+       Release: $Name$
+
+       Revision history:
+       $Log$
+       Revision 1.7  2004/10/18 23:01:18  dimeglio
+       Added oscheck to various targets
+       
+       Revision 1.6  2004/10/12 14:21:21  eronchie
+       Removed ssl_utils dependency
+       
+       Revision 1.5  2004/08/20 09:51:39  eronchie
+       Updated buildmodules orders
+       
+       Revision 1.4  2004/08/04 07:30:29  eronchie
+       Added cppunit
+       
+       Revision 1.3  2004/07/23 14:50:08  eronchie
+       Added exception
+       
+       Revision 1.2  2004/07/23 08:27:03  eronchie
+       Updated
+       
+       
+-->
+
+<project name="GLite Middleware WMS Utils CSF" default="all">
+
+       <!-- overwrite default workspace directory -->
+       <property name="workspace.dir" value="../.." />
+
+       <!-- ===============================================
+                Load properties
+                =============================================== -->
+
+       <!-- load baseline and user properties -->
+       <import file="${workspace.dir}/org.glite/project/baseline.properties.xml" />
+
+       <!-- define build properties file location since we are already in project dir -->
+       <property name="subsystem.build.properties.file" value="./build.properties" />
+
+       <!-- Load subsytem-specific property files -->
+       <import file="./properties.xml"/>
+
+       <!-- load global properties -->
+       <import file="${global.properties.file}" />
+
+       <!-- ===============================================
+                Load dependencies 
+                =============================================== -->
+
+       <!-- Load user dependencies file -->
+       <property file="${user.dependencies.file}" />
+               
+       <!-- Load subsystem dependencies file -->
+       <property file="./dependencies.properties" />
+
+       <!-- Load global dependencies file -->
+       <property file="${global.dependencies.file}" />
+
+       <!-- ===============================================
+                Load targets 
+                =============================================== -->
+       <import file="${global.targets-envchecks.file}" />
+       <import file="${global.targets-external-dependencies.file}" />
+
+       <!-- ===============================================
+                Evaluate CVS tags
+                =============================================== -->
+
+       <target name="evaluate.cvs.tags" description="Figure out if we need tagged CVS checkout">
+               <condition property="glite.head">
+                       <and>
+                               <equals arg1="${org.glite.version}" arg2="HEAD" />
+                               <or>
+                                       <istrue value="${update}" />
+                                       <not>
+                                               <available file="${global.dependencies.file}" type="file" />
+                                       </not>
+                               </or>
+                       </and>
+               </condition>
+               <condition property="glite.tag">
+                       <and>
+                               <not>
+                                       <equals arg1="${org.glite.version}" arg2="HEAD" />
+                               </not>
+                               <or>
+                                       <istrue value="${update}" />
+                                       <not>
+                                               <available file="${global.dependencies.file}" type="file" />
+                                       </not>
+                               </or>
+                       </and>
+               </condition>
+               <condition property="glite-wms-utils.head">
+                       <and>
+                               <equals arg1="${org.glite.wms-utils.version}" arg2="HEAD" />
+                               <istrue value="${update}" />
+                       </and>
+               </condition>
+               <condition property="glite-wms-utils.tag">
+                       <and>
+                               <not>
+                                       <equals arg1="${org.glite.wms-utils.version}" arg2="HEAD" />
+                               </not>
+                               <istrue value="${update}" />
+                       </and>
+               </condition>
+       
+               <!-- condition property tag = do not remove = -->
+
+               <condition property="tls.head">
+                        <equals arg1="${org.glite.wms-utils.tls.version}" arg2="HEAD" />
+                </condition>
+
+               <condition property="jobid.head">
+                        <equals arg1="${org.glite.wms-utils.jobid.version}" arg2="HEAD" />
+                </condition>
+
+               <condition property="exception.head">
+                        <equals arg1="${org.glite.wms-utils.exception.version}" arg2="HEAD" />
+                </condition>
+       </target>
+
+       <presetdef name="cvs-co">
+               <cvs command="checkout" dest="${workspace.dir}" />
+       </presetdef>
+
+       <!-- =====================================================
+                Self-update if required
+                ===================================================== -->
+               
+       <!-- Update main GLite module -->
+       <target name="org.glite" depends="get.glite.head, get.glite.tag"/>
+       <target name="get.glite.head" if="glite.head">
+               <cvs-co package="org.glite" />
+       </target>
+       <target name="get.glite.tag" if="glite.tag">
+               <cvs-co package="org.glite"
+                       tag="${org.glite.version}" />
+       </target>
+
+       <!-- Update the current module -->
+       <target name="org.glite.wms-utils" depends="get.glite-wms-utils.head, get.glite-wms-utils.tag"/>
+       <target name="get.glite-wms-utils.head" if="glite-wms-utils.head">
+               <cvs-co package="org.glite.wms-utils" />
+               <fail>The org.glite and org.glite.wms-utils modules have been updated, please rerun the configuration file</fail>
+       </target>
+       <target name="get.glite-wms-utils.tag" if="glite-wms-utils.tag">
+               <cvs-co package="org.glite.wms-utils"
+                       tag="${org.glite.wms-utils.version}" />
+               <fail>The org.glite and org.glite.wms-utils modules have been updated, please rerun the configuration file</fail>
+       </target>
+
+       <!-- *****************************************************-->
+       <!-- Development tools                                    -->
+       <!-- *****************************************************-->
+       
+       <!-- All development tools -->
+       <target name="devtools" depends="oscheck,
+                                        junitcheck,
+                                        junit,
+                                    chkstyle,
+                                        jalopy,
+                                        ant-contrib,
+                                        cpptasks,
+                                        egee-ant-ext"/>                
+       
+       <!-- =====================================================
+                External libraries
+                ===================================================== -->
+       
+       <!--  All external libraries -->
+       <target name="external" depends="oscheck,
+                                        log4j"/>
+       
+       <!-- =====================================================
+                GLite WMS modules
+                ===================================================== -->
+
+       <!-- component targets tag = do not remove = -->
+
+       <!-- Tls -->
+       <target name="tls" depends="boost,classads,globus,cppunit,exception,get.tls.head, get.tls.tag"/>
+       <target name="get.tls.head" if="tls.head">
+            <cvs-co package="org.glite.wms-utils.tls" />
+       </target>
+       <target name="get.tls.tag" unless="tls.head">
+            <cvs-co package="org.glite.wms-utils.tls"
+                    tag="${org.glite.wms-utils.tls.version}" />
+       </target>
+
+       <!-- Jobid -->
+       <target name="jobid" depends="boost,classads,globus,cppunit,exception,get.jobid.head, get.jobid.tag"/>
+       <target name="get.jobid.head" if="jobid.head">
+            <cvs-co package="org.glite.wms-utils.jobid" />
+       </target>
+       <target name="get.jobid.tag" unless="jobid.head">
+            <cvs-co package="org.glite.wms-utils.jobid"
+                    tag="${org.glite.wms-utils.jobid.version}" />
+       </target>
+
+       <!-- exception -->
+        <target name="exception" depends="cppunit,get.exception.head, get.exception.tag"/>
+        <target name="get.exception.head" if="exception.head">
+            <cvs-co package="org.glite.wms-utils.exception" />
+        </target>
+        <target name="get.exception.tag" unless="exception.head">
+            <cvs-co package="org.glite.wms-utils.exception"
+                    tag="${org.glite.wms-utils.exception.version}" />
+        </target>      
+               
+       <!-- All project modules -->
+       <target name="project" depends="exception,
+                                       tls,
+                                       jobid"/>
+
+
+       <!-- ====================================================
+                Checkout all
+                ==================================================== -->
+       
+       <!-- All libraries -->
+       <target name="all" depends="oscheck,evaluate.cvs.tags,defaultenvchecks,org.glite,org.glite.wms-utils,devtools,external,project" />              
+
+       <!-- ====================================================
+                Print dependecies to console
+                ==================================================== -->
+       
+       <target name="dependencies">
+               <concat>
+                       <fileset dir="." includes="dependencies.properties" />
+               </concat>
+       </target>               
+
+</project>
diff --git a/org.gridsite.core/project/properties.xml b/org.gridsite.core/project/properties.xml
new file mode 100644 (file)
index 0000000..74f88dc
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+       Copyright (c) 2004 on behalf of the EU EGEE Project: 
+       The European Organization for Nuclear Research (CERN), 
+       Istituto Nazionale di Fisica Nucleare (INFN), Italy
+       Datamat Spa, Italy
+       Centre National de la Recherche Scientifique (CNRS), France
+       CS Systeme d'Information (CSSI), France
+       Royal Institute of Technology, Center for Parallel Computers (KTH-PDC), Sweden
+       Universiteit van Amsterdam (UvA), Netherlands
+       University of Helsinki (UH.HIP), Finland
+       University of Bergen (UiB), Norway
+       Council for the Central Laboratory of the Research Councils (CCLRC), United Kingdom
+
+       Common build properties file for the Gridsite Core modules
+       
+       Authors: Alberto Di Meglio <alberto.di.meglio@cern.ch>          
+       Version info: $Id$
+       Release: $Name$
+
+       Revision history:
+       $Log$
+-->
+
+<project name="Gridsite Core common properties">
+
+       <!-- Include build properties to allow overwriting 
+            of properties for subsystem                    -->
+       <property name="subsystem.build.properties.file" value="./project/build.properties" />
+       <property file="${subsystem.build.properties.file}" />  
+
+       <!-- ======================================================
+          Define subsystem properties
+                ====================================================== -->
+       
+       <!-- Subsystem name -->
+       <property name="subsystem.name" value="${gridsite-core.subsystem.name}"/>
+       
+       <!-- Subsystem prefix -->
+       <property name="subsystem.prefix" value="${gridsite-core.subsystem.prefix}"/>
+       
+       <!-- ======================================================
+          Define general subsystem properties
+                ====================================================== -->
+               
+       <!-- Include common subsystem properties -->
+       <import file="${subsystem.general.properties.file}" />
+       
+       <!-- ======================================================
+                Define extra properties here ...
+                ====================================================== -->
+                                       
+</project>
diff --git a/org.gridsite.core/project/taskdefs.xml b/org.gridsite.core/project/taskdefs.xml
new file mode 100644 (file)
index 0000000..9c35cef
--- /dev/null
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+       Copyright (c) 2004 on behalf of the EU EGEE Project: 
+       The European Organization for Nuclear Research (CERN), 
+       Istituto Nazionale di Fisica Nucleare (INFN), Italy
+       Datamat Spa, Italy
+       Centre National de la Recherche Scientifique (CNRS), France
+       CS Systeme d'Information (CSSI), France
+       Royal Institute of Technology, Center for Parallel Computers (KTH-PDC), Sweden
+       Universiteit van Amsterdam (UvA), Netherlands
+       University of Helsinki (UH.HIP), Finland
+       University of Bergen (UiB), Norway
+       Council for the Central Laboratory of the Research Councils (CCLRC), United Kingdom
+
+       Common Ant task definition file for the Gridsite Core modules
+       
+       Authors: Alberto Di Meglio <alberto.di.meglio@cern.ch>          
+       Version info: $Id$
+       Release: $Name$
+
+       Revision history:
+       $Log$
+-->
+
+<project name="Gridsite Core common tasks and types definitions">
+
+<!-- ======================================================
+        Subsystem task definitions
+        ====================================================== -->
+
+</project>
\ 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 (file)
index 0000000..1eaa9d4
--- /dev/null
@@ -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 (file)
index 0000000..e47d005
--- /dev/null
@@ -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 <filter> <input-file>, where <filter> 
+# is the value of the INPUT_FILTER tag, and <input-file> 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 (file)
index 0000000..a2bba0b
--- /dev/null
@@ -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 (file)
index 0000000..97ebc25
--- /dev/null
@@ -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 (file)
index 0000000..ad33993
--- /dev/null
@@ -0,0 +1 @@
+<p><a href=http://www.gridpp.ac.uk/authz/>GridSite</a> Version 0.9.1
diff --git a/org.gridsite.core/src/gaclexample.c b/org.gridsite.core/src/gaclexample.c
new file mode 100644 (file)
index 0000000..4cab5bc
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <gridsite.h>
+
+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 (file)
index 0000000..a6633d3
--- /dev/null
@@ -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 <mcnab@hep.man.ac.uk>
+
+%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 (file)
index 0000000..4da3d11
--- /dev/null
@@ -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 (file)
index 0000000..fed6c42
--- /dev/null
@@ -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 <stdio.h>
+#include <time.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <fcntl.h>
+
+// when porting: remember that sendfile() is very OS-specific!
+#include <sys/sendfile.h>
+
+#include <gridsite.h>
+
+#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,"<title>Forbidden filename %s</title>\n", filename);
+          GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE);
+                                 
+          GRSThttpPrintf(&bp, "<h1 align=center>Forbidden filename %s</h1>\n",
+                         filename);
+                                                                                
+          GRSThttpPrintf(&bp,
+                      "<p align=center>New file names cannot include slashes "
+                      "or use the reserved ACL name, %s\n", GRST_ACL_FILE);
+                                                                                
+          GRSThttpPrintf(&bp,"<p align=center>"
+                     "<a href=\"%s%s?cmd=managedir\">Return to "
+                     "directory listing</a>\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, "<title>Failed to upload</title>\n");
+
+  GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE);
+
+  GRSThttpPrintf(&bp, "<h1 align=center>Failed to upload</h1>\n");
+  
+  GRSThttpPrintf(&bp, "<p align=center>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,"<p align=center>"
+                     "<a href=\"%s%s?cmd=managedir\">Return to "
+                     "directory listing</a>\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, "<title>Error deleting %s%s</title>\n", dir_uri, file);
+
+  GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE);
+
+  GRSThttpPrintf(&bp, "<h1 align=center>Error deleting %s%s</h1>\n", 
+                      dir_uri, file);
+  
+  GRSThttpPrintf(&bp, "<p align=center>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,"<p align=center>"
+                     "<a href=\"%s%s?cmd=managedir\">Return to "
+                     "directory listing</a>\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, "<title>Delete %s</title>\n", file);
+
+  GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE);
+
+  GRSThttpPrintf(&bp, "<h1 align=center>Delete %s</h1>\n", file);
+  
+  GRSThttpPrintf(&bp,"<form action=\"%s%s\" method=post>\n",dir_uri,admin_file);
+  GRSThttpPrintf(&bp,"<h2 align=center>Do you really want to delete %s?", file);
+  GRSThttpPrintf(&bp,"<p align=center><input type=submit value=\"Yes, delete %s\"></h2>\n", file);
+  GRSThttpPrintf(&bp,"<input type=hidden name=file value=\"%s\">\n", file);
+  GRSThttpPrintf(&bp,"<input type=hidden name=cmd value=deleteaction>\n");
+  GRSThttpPrintf(&bp,"</form>\n");
+
+  GRSThttpPrintf(&bp,"<p align=center>Or "
+                     "<a href=\"%s%s?cmd=managedir\">return to "
+                     "directory listing</a>\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, "<title>Rename %s</title>\n", file);
+
+  GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE);
+
+  GRSThttpPrintf(&bp, "<h1 align=center>Rename %s%s</h1>\n", dir_uri, file);
+  
+  GRSThttpPrintf(&bp,"<form action=\"%s%s\" method=post>\n",dir_uri,admin_file);
+  GRSThttpPrintf(&bp,"<h2 align=center>What do you want to rename %s to?</h2>", file);
+  GRSThttpPrintf(&bp,"<input type=hidden name=file value=\"%s\">\n", file);
+  GRSThttpPrintf(&bp,"<p align=center>New name: <input type=text name=newfile value=\"%s\">\n", file);
+  GRSThttpPrintf(&bp,"<input type=submit value=\"Rename\">\n");
+  GRSThttpPrintf(&bp,"<input type=hidden name=cmd value=renameaction>\n");
+  GRSThttpPrintf(&bp,"</form>\n");
+
+  GRSThttpPrintf(&bp,"<p align=center>Or "
+                     "<a href=\"%s%s?cmd=managedir&diruri=%s\">return to "
+                     "directory listing</a>\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,"<title>Error writing %s%s</title>\n", dir_uri, file);
+      GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE);
+
+      GRSThttpPrintf(&bp, "<h1 align=center>Error writing %s%s</h1>\n", 
+                      dir_uri, file);
+  
+      GRSThttpPrintf(&bp, 
+                      "<p align=center>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,"<p align=center>"
+                     "<a href=\"%s%s?cmd=managedir\">Return to "
+                     "directory listing</a>\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,"<title>Error creating %s%s</title>\n", dir_uri, 
+                                                             GRST_ACL_FILE);
+  GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE);
+
+  GRSThttpPrintf(&bp, "<h1 align=center>Error creating %s%s</h1>\n", 
+                      dir_uri, GRST_ACL_FILE);
+  
+  GRSThttpPrintf(&bp, "<p align=center>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,"<p align=center>"
+                     "<a href=\"%s%s?cmd=managedir\">Return to "
+                     "directory listing</a>\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,"<title>Error renaming %s%s</title>\n", dir_uri, file);
+  GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE);
+
+  GRSThttpPrintf(&bp, "<h1 align=center>Error renaming %s%s</h1>\n", 
+                      dir_uri, file);
+  
+  GRSThttpPrintf(&bp, "<p align=center>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,"<p align=center>"
+                     "<a href=\"%s%s?cmd=managedir\">Return to "
+                     "directory listing</a>\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,"<title>Error create %s%s</title>\n", dir_uri, file);
+  GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE);
+
+  GRSThttpPrintf(&bp, "<h1 align=center>Error creating directory %s%s</h1>\n",
+                      dir_uri, file);
+  
+  GRSThttpPrintf(&bp, 
+                      "<p align=center>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,"<p align=center>"
+                     "<a href=\"%s%s?cmd=managedir\">Return to "
+                     "parent directory listing</a>\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,"<title>Error writing %s</title>\n", file);
+      GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE);
+
+      GRSThttpPrintf(&bp, "<h1 align=center>Error writing %s to %s</h1>\n", 
+                     file, dir_uri);
+  
+      GRSThttpPrintf(&bp, "<p align=center>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,"<p align=center>"
+                     "<a href=\"%s%s?cmd=managedir\">Return to "
+                     "directory listing</a>\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,"<title>Error writing %s%s</title>\n", dir_uri, file);
+  GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE);
+
+  GRSThttpPrintf(&bp, "<h1 align=center>Error writing %s%s</h1>\n", 
+                      dir_uri, file);
+  
+  GRSThttpPrintf(&bp, "<p align=center>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,"<p align=center>"
+                     "<a href=\"%s%s?cmd=managedir\">Return to "
+                     "directory listing</a>\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, "<title>History of %s%s</title>\n", dir_uri, file);
+  GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE);
+  GRSThttpPrintf(&bp,
+   "<h1 align=center>History of <a href=\"%s%s\">%s%s</a></h1>\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&nbsp;%e&nbsp;%b&nbsp;%Y&nbsp;%k:%M", &file_tm);
+
+      GRSThttpPrintf(&bp, "<p align=center>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, 
+                       "<p align=center><table border=1 cellpadding=5>\n"
+                       "<tr><td>Date</td><td>Size after</td>"
+                       "<td colspan=2>Changed by</td></tr>\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&nbsp;%e&nbsp;%b&nbsp;%Y&nbsp;%k:%M", &file_tm);
+
+               GRSThttpPrintf(&bp, 
+                 "<tr><td>%s</td><td align=right>%d</td><td>%s</td>\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, "<td><a href=\"");
+                    if (strcmp (file, GRST_ACL_FILE)==0)
+                        GRSThttpPrintf(&bp, "%s%s?cmd=acl_history&dir_uri=%s&file=%s\">View</a></td></tr>\n",
+                           dir_uri, admin_file, dir_uri, namelist[i]->d_name);
+                    else GRSThttpPrintf(&bp, "%s%s\">View</a></td></tr>\n",
+                       dir_uri, namelist[i]->d_name);
+               }
+               else GRSThttpPrintf(&bp, "<td>&nbsp;</td></tr>");
+                 
+               free(vfile);
+             }
+         }      
+    }
+  
+  if (num > 0) GRSThttpPrintf(&bp, "</table>\n");
+  else GRSThttpPrintf(&bp, "<p align=center>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, "<title>Contents of %s%s</title>\n", dir_uri, file);
+  GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE);
+  GRSThttpPrintf(&bp,
+   "<h1 align=center>Contents of ZIP file <a href=\"%s%s\">%s%s</a></h1>\n",
+   dir_uri, file, dir_uri, file);
+
+  unzip = getenv("GRST_UNZIP");
+  if (unzip == NULL) unzip = getenv("REDIRECT_GRST_UNZIP");
+
+  if (unzip != NULL)
+    {  
+      GRSThttpPrintf(&bp, "<center><table><tr><td><pre>\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, "</pre></td></tr></table></center>\n");
+
+      if (GRSTgaclPermHasWrite(perm))
+           GRSThttpPrintf(&bp, 
+            "<p><center><form action=\"%s%s\" method=post>"
+            "<input type=submit value=\"Unzip this file\"> in %s"
+            "<input type=hidden name=cmd value=unzipfile>"
+            "<input type=hidden name=file value=\"%s\"></form>"
+            "<p>(All files are placed in the same directory and files "
+            "beginning with &quot;.&quot; are ignored.)</center>\n",
+            dir_uri, admin_file, dir_uri, file);
+    }
+  else GRSThttpPrintf(&bp, "<p align=center>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, "<title>Unzipping %s%s</title>\n", dir_uri, file);
+  GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE);
+  GRSThttpPrintf(&bp,
+   "<h1 align=center>Unzipping <a href=\"%s%s\">%s%s</a></h1>\n",
+   dir_uri, file, dir_uri, file);
+
+  unzip = getenv("GRST_UNZIP");
+  if (unzip == NULL) unzip = getenv("REDIRECT_GRST_UNZIP");
+
+  if (unzip != NULL)
+    {  
+      GRSThttpPrintf(&bp, "<center><table><tr><td><pre>\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, "</pre></td></tr></table></center>\n");      
+
+      if (GRSTgaclPermHasList(perm))
+                GRSThttpPrintf(&bp, "<p align=center>"
+                                    "<b><a href=\"%s%s?cmd=managedir\">Back to "
+                                    "directory</a></b>", dir_uri, admin_file);
+    }
+  else GRSThttpPrintf(&bp, "<p align=center>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, "<title>Edit file %s</title>\n", file);
+
+  GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE);
+
+  GRSThttpPrintf(&bp, "<h1>Edit file %s</h1>\n", file);
+  
+  GRSThttpPrintf(&bp,"<form action=\"%s%s\" method=post>\n",dir_uri,admin_file);
+  GRSThttpPrintf(&bp,"<p><input type=submit value=\"Save changes\">\n");
+  GRSThttpPrintf(&bp,"<p>File name: <input type=text name=file value=\"%s\">\n", file);
+  GRSThttpPrintf(&bp,"<input type=hidden name=cmd value=editaction>\n");
+  GRSThttpPrintf(&bp,"<p><textarea name=pagetext cols=80 rows=22>");
+
+  if (fp != NULL)
+    {  
+      rawpagesize = statbuf.st_size + 1000;
+      rawpage = malloc(rawpagesize);
+  
+      i = 0;
+  
+      while ((c = fgetc(fp)) != EOF)
+           {
+             if (c == '<')      { strcpy(&rawpage[i], "&lt;");
+                                  i += 4; }
+             else if (c == '>') { strcpy(&rawpage[i], "&gt;");
+                                  i += 4; }
+             else if (c == '&') { strcpy(&rawpage[i], "&amp;");
+                                  i += 5; }
+             else if (c == '"') { strcpy(&rawpage[i], "&quot;");
+                                  i += 6; }
+             else               { rawpage[i] = c;
+                                  i += 1; }
+           
+             if (i >= rawpagesize - 7)
+               {
+                 rawpagesize += 1000;
+                 rawpage = realloc(rawpage, rawpagesize);           
+               }
+           }
+
+      rawpage[i] = '\0';
+  
+      GRSThttpPrintf(&bp, "%s", rawpage);
+    }
+    
+  GRSThttpPrintf(&bp, "</textarea>\n");  
+  GRSThttpPrintf(&bp, "<p><input type=submit value=\"Save changes\">\n");
+  GRSThttpPrintf(&bp, "</form>\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, "<title>Edit DN List %s</title>\n", file);
+
+  GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE);
+
+  GRSThttpPrintf(&bp, "<h1>Edit DN List</h1>\n");
+  
+  GRSThttpPrintf(&bp,"<form action=\"%s%s\" method=post>\n",dir_uri,admin_file);
+  GRSThttpPrintf(&bp,"<p><input type=submit value=\"Update\">\n");
+  GRSThttpPrintf(&bp,"<p>List URL: <input type=text name=file value=\"%s\" "
+                     "size=%d>\n", file, strlen(file));
+  GRSThttpPrintf(&bp,"<input type=hidden name=cmd value=editdnlistaction>\n");
+
+  if (fp != NULL)
+    {
+      GRSThttpPrintf(&bp, "<p><table>\n<tr><th>Keep?</th>"
+                          "<th>Name</th></tr>\n");
+
+      while (fgets(oneline, sizeof(oneline), fp) != NULL)
+           {
+             ++numdn;
+         
+             p = rindex(oneline, '\n');
+             if (p != NULL) *p = '\0';
+         
+             GRSThttpPrintf(&bp, "<tr><td align=center><input type=checkbox "
+                             "name=\"dn%d\" value=\"%s\" checked></td>"
+                             "<td>%s</td></tr>\n", numdn, oneline, oneline);
+           }
+
+      GRSThttpPrintf(&bp,"</table>\n");
+    }
+    
+  GRSThttpPrintf(&bp,"<input type=hidden name=numdn value=\"%d\">\n", numdn);
+
+  GRSThttpPrintf(&bp, "<p>Add new DN: <input type=text name=add "
+                      "size=60 maxlength=512>\n");
+
+  GRSThttpPrintf(&bp,"<p><input type=submit value=\"Update\">\n");
+  GRSThttpPrintf(&bp, "</form>\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,"<title>Manage directory %s</title>\n", dir_uri);
+
+  GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_HEADFILE);
+    
+  GRSThttpPrintf(&bp, "<h1>Manage directory %s</h1>\n<table>\n", dir_uri);
+  
+  if (dir_uri[1] != '\0')
+     GRSThttpPrintf(&bp, 
+      "<tr><td colspan=3>[<a href=\"../%s?cmd=managedir\">Parent "
+       "directory</a>]</td></tr>\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), 
+           "<td align=right>%R</td><td align=right>%e&nbsp;%b&nbsp;%y</td>",
+                        &mtime_tm);    
+
+          if (!is_dnlists_dir)
+            {
+              GRSThttpPrintf(&bp,
+                      "<tr><td><a href=\"%s\">%s</a></td>"
+                      "<td align=right>%ld</td>%s\n",
+                      GRST_ACL_FILE,
+                      GRST_ACL_FILE,
+                      statbuf.st_size, modified);
+
+              GRSThttpPrintf(&bp,
+                   "<td><a href=\"%s%s?cmd=history&file=%s\">"
+                      "History</a></td>",
+                      dir_uri, admin_file, GRST_ACL_FILE);
+            }
+          else GRSThttpPrintf(&bp,
+                      "<tr><td>%s</td>"
+                      "<td align=right>%ld</td>%s\n",
+                      GRST_ACL_FILE,
+                      statbuf.st_size, modified);
+
+          if (GRSTgaclPermHasAdmin(perm)) 
+               GRSThttpPrintf(&bp,
+                   "<td><a href=\"%s%s?cmd=admin_acl\">Edit</a></td>"
+                   "<td><a href=\"%s%s?cmd=delete&file=%s\">Delete</a></td>",
+                   dir_uri, admin_file,
+                   dir_uri, admin_file, GRST_ACL_FILE);
+          else if (GRSTgaclPermHasRead(perm))
+               GRSThttpPrintf(&bp,
+                   "<td><a href=\"%s%s?cmd=show_acl\">View</a></td>"
+                   "<td>&nbsp;</td>", dir_uri, admin_file);
+          else GRSThttpPrintf(&bp, "<td>&nbsp;</td><td>&nbsp;</td>\n");
+
+          GRSThttpPrintf(&bp, "<td>&nbsp;</td></tr>\n");
+        }
+      else if (GRSTgaclPermHasAdmin(perm))
+          GRSThttpPrintf(&bp, "<form method=post action=\"%s%s\">\n"
+        "<tr><td colspan=8><input type=submit value=\"Create .gacl\"></td>\n"
+        "<input type=hidden name=cmd value=\"create_acl\"></tr></form>\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), 
+               "<td align=right>%R</td><td align=right>%e&nbsp;%b&nbsp;%y</td>",
+                        &mtime_tm);    
+                              
+               if (S_ISDIR(statbuf.st_mode)) 
+                 {
+                   GRSThttpPrintf(&bp,
+                      "<tr><td><a href=\"%s%s/%s?cmd=managedir\">"
+                      "%s/</a></td>"
+                      "<td align=right>%ld</td>%s\n<td colspan=2>&nbsp;</td>",
+                      dir_uri, namelist[n]->d_name, admin_file,
+                      namelist[n]->d_name,
+                      statbuf.st_size, modified);
+
+                   if (numfiles == 0)
+                        GRSThttpPrintf(&bp,
+                        "<td><a href=\"%s%s?cmd=delete&file=%s\">"
+                        "Delete</a></td>\n", 
+                        dir_uri, admin_file, namelist[n]->d_name);
+                   else GRSThttpPrintf(&bp, "<td>&nbsp;</td>\n");
+                      
+                   GRSThttpPrintf(&bp, "<td>&nbsp;</td></tr>\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, "<tr><td><a href=\"%s\">%s</a></td>"
+                                       "<td align=right>%ld</td>%s"
+                                       "<td>&nbsp;</td>",
+                                       d_name, d_name,
+                                       statbuf.st_size, modified);
+
+                   if (GRSTgaclPermHasWrite(perm))
+                     GRSThttpPrintf(&bp, "<form action=\"%s%s\" method=post>"
+                        "<td><input type=submit value=Edit></td>"
+                        "<input type=hidden name=cmd value=editdnlist>"
+                        "<input type=hidden name=file value=\"%s\">"
+                        "</form>\n",
+                        dir_uri, admin_file, d_name);
+                   else GRSThttpPrintf(&bp, "<td>&nbsp;</td>\n");
+                   
+                   if (GRSTgaclPermHasWrite(perm))
+                     GRSThttpPrintf(&bp, "<form action=\"%s%s\" method=post>"
+                        "<td><input type=submit value=Delete></td>"
+                        "<input type=hidden name=cmd value=delete>"
+                        "<input type=hidden name=file value=\"%s\">"
+                        "</form>\n",
+                        dir_uri, admin_file, d_name);
+                   else GRSThttpPrintf(&bp, "<td>&nbsp;</td>\n");
+
+                   GRSThttpPrintf(&bp, "<td>&nbsp;</td></tr>");
+                 }
+               else /* regular directory, not DN Lists */
+                 {        
+                   d_name   = namelist[n]->d_name;
+
+                   GRSThttpPrintf(&bp,
+                          "<tr><td><a href=\"%s%s\">%s</a></td>"
+                          "<td align=right>%ld</td>%s",
+                          dir_uri, d_name, 
+                          d_name, 
+                          statbuf.st_size, modified);                                        
+
+                   GRSThttpPrintf(&bp,
+                     "<td><a href=\"%s%s?cmd=history&file=%s\">"
+                      "History</a></td>",
+                      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,
+                               "<td><a href=\"%s%s?cmd=ziplist&file=%s\">"
+                               "List</a></td>\n",
+                               dir_uri, admin_file, d_name);                   
+                   else if ((p != NULL) && 
+                       (strstr(editable, &p[1]) != NULL) &&
+                       GRSTgaclPermHasWrite(perm))
+                         GRSThttpPrintf(&bp,
+                               "<td><a href=\"%s%s?cmd=edit&file=%s\">"
+                               "Edit</a></td>\n",
+                               dir_uri, admin_file, d_name);
+                   else  GRSThttpPrintf(&bp, "<td>&nbsp;</td>");
+
+                   if (GRSTgaclPermHasWrite(perm))
+                    GRSThttpPrintf(&bp,
+                     "<td><a href=\"%s%s?cmd=delete&file=%s\">"
+                     "Delete</a></td>\n", dir_uri, admin_file, d_name);
+                   else
+                    GRSThttpPrintf(&bp, "<td>&nbsp;</td>\n");
+
+                   if (GRSTgaclPermHasWrite(perm))
+                    GRSThttpPrintf(&bp,
+                     "<td><a href=\"%s%s?cmd=rename&file=%s\">"
+                     "Rename</a></td></tr>\n", dir_uri, admin_file, d_name);
+                   else
+                    GRSThttpPrintf(&bp, "<td>&nbsp;</td></tr>");
+                 }
+           }
+
+         free(namelist[n]);
+       }
+                    
+      free(namelist);
+    }
+
+  if (GRSTgaclPermHasWrite(perm))
+    {
+      if (is_dnlists_dir)
+        {
+          GRSThttpPrintf(&bp, "<form method=post action=\"%s%s\">\n"
+        "<tr><td colspan=4>New list name: "
+        "<input type=text name=file value=\"%sNEW_LIST\" size=%d>\n"
+        "<input type=hidden name=cmd value=editdnlist></td>"
+        "<td colspan=2 align=center><input type=submit value=Create></td>\n"
+        "</tr></form>\n",
+        dir_uri, admin_file, fulluri, strlen(fulluri)+8);
+
+          GRSThttpPrintf(&bp, "<form method=post action=\"%s%s\">\n"
+        "<tr><td colspan=4>New directory: "
+        "<input type=text name=file>\n"
+        "<td colspan=2 align=center><input type=submit name=button value=\"Create\"></td>\n"
+        "<input type=hidden name=cmd value=edit></td></tr></form>\n",
+        dir_uri, admin_file);      
+        }
+      else
+        {
+          GRSThttpPrintf(&bp, "<form method=post action=\"%s%s\">\n"
+        "<tr><td colspan=8><hr width=\"75%\"></td></tr>\n"
+        "<tr><td>New name:</td>"
+        "<td colspan=3><input type=text name=file size=25>\n"
+        "<td colspan=2 align=center><input type=submit name=button value=\"New file\"></td>\n"
+        "<td colspan=2 align=center><input type=submit name=button value=\"New directory\"></td>\n"
+        "<input type=hidden name=cmd value=edit></td></tr></form>\n",
+        dir_uri, admin_file);
+      
+          GRSThttpPrintf(&bp,
+        "<form method=post action=\"%s%s\" enctype=\"multipart/form-data\">\n"
+        "<tr><td colspan=8><hr width=\"75%\"></td></tr>\n"
+        "<tr><td rowspan=2>Upload file:</td>"
+        "<td colspan=2>New name:</td>"
+        "<td colspan=6><input type=text name=file size=25> "
+        "<input type=submit value=Upload></td></tr>\n"
+        "<tr><td colspan=2>Local name:</td>"
+        "<td colspan=6><input type=file name=uploadfile size=25></td></tr>\n"
+        "</form>\n", dir_uri, admin_file);
+        }
+    }
+
+  GRSThttpPrintf(&bp, "</table>\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 (file)
index 0000000..318277e
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <gridsite.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+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 !!!<br>\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<br>\n");
+    adminfooter(&bp, dn, help_uri, dir_uri, NULL);
+    GRSThttpPrintHeaderFooter(&bp, dir_path, GRST_FOOTFILE); 
+    GRSThttpWriteOut(&bp);
+    return;
+  }
+
+  if (admin) GRSThttpPrintf (&bp,"<a href=\"%s%s?cmd=new_entry_form&diruri=%s&timestamp=%d\">New&nbsp;Entry</a><br>\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,"<br>Entry %d:\n", entry_no);
+    if (admin){
+      GRSThttpPrintf (&bp,"<a href=\"%s%s?cmd=edit_entry_form&entry_no=%d&diruri=%s&timestamp=%d\">Edit&nbsp;Entry</a> ", dir_uri, admin_file, entry_no, dir_uri, timestamp );
+      GRSThttpPrintf (&bp,"<a href=\"%s%s?cmd=del_entry_sure&entry_no=%d&diruri=%s&timestamp=%d\">Delete&nbsp;Entry</a> ",dir_uri, admin_file, entry_no, dir_uri, timestamp );
+      GRSThttpPrintf (&bp,"<p>\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,"<a href=\"%s%s?cmd=admin_acl&diruri=%s&timestamp=%d\">Admin&nbsp;Mode</a>",  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,"<a href=\"%s%s?cmd=revert_acl&diruri=%s&timestamp=%d&file=%s\">Revert to this Version</a>",  dir_uri, admin_file, dir_uri, timestamp, file );
+    GRSThttpPrintf (&bp, "<input type=\"hidden\" name=\"file\" value=\"%s\">\n", file);
+    // Revert Button
+    GRSThttpPrintf (&bp, "<p align=center><input type=\"submit\" value=\"Revert to this ACL\" name=\"B1\"></p>\n</form>\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, "<font size=\"4\"><b>NEW ENTRY IN ACL FOR %s </b></font></p>\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<br>\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, "<b><font size=\"4\">EDITING ENTRY %d IN ACL FOR %s </font></b></p>\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, "<input type=\"hidden\" name=\"entry_no\" value=\"%d\">\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 <any-user> 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 <br>\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, " <font size=\"4\"><b>NEW CREDENTIAL IN ENTRY %d OF ACL FOR %s</b></font></p>\n", entry_no, dir_uri);
+  StartForm(&bp, dir_uri, dir_path, admin_file, timestamp, "add_cred");
+
+  GRSThttpPrintf (&bp, " <input type=\"hidden\" name=\"entry_no\" value=\"%d\">\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<br><a href=\"%s%s?diruri=%s&cmd=admin_acl&timestamp=%d\">Click&nbsp;Here</a> 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, "<h1 align=center>Do you really want to delete the following entry?</h1><br><br>\n");
+  GRSThttpPrintf (&bp,"<br>Entry %d:<br>\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, "<input type=\"hidden\" name=\"entry_no\" value=\"%d\">\n", entry_no);
+  GRSThttpPrintf (&bp, " <p align=center><input type=\"submit\" value=\"Yes\" name=\"B1\"></p>\n</form>\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, "<h1 align=center>Do you really want to delete the following credential from entry %d?</h1><br><br>", 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,"<br>\n");
+
+  // Yes Button
+  StartForm(&bp, dir_uri, dir_path, admin_file, atol(GRSThttpGetCGI("timestamp")), "del_cred");
+  GRSThttpPrintf (&bp, "<input type=\"hidden\" name=\"entry_no\" value=\"%d\">\n", entry_no);
+  GRSThttpPrintf (&bp, "<input type=\"hidden\" name=\"cred_no\" value=\"%d\">\n", cred_no);
+  GRSThttpPrintf (&bp, " <p align=center><input type=\"submit\" value=\"Yes\" name=\"B1\"></p>\n</form>\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, "<title>Access Control List for %s</title>\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, "<form method=\"POST\" action=\"%s%s?diruri=%s\">\n", dir_uri, admin_file, dir_uri);
+  GRSThttpPrintf (bp, " <input type=\"hidden\" name=\"cmd\" value=\"%s\">\n", target_function);
+  GRSThttpPrintf (bp, " <input type=\"hidden\" name=\"timestamp\" value=\"%d\">\n", timestamp);
+  return;
+}
+
+void EndForm(GRSThttpBody *bp){
+  GRSThttpPrintf (bp, " <br><input type=\"submit\" value=\"Submit\" name=\"B1\"><input type=\"reset\" value=\"Reset\" name=\"B2\"></p>\n");
+  GRSThttpPrintf (bp, "</form>\n");
+  return;
+}
+
+void GRSTgaclCredTableStart(GRSThttpBody *bp){
+  //Starts an HTML table of credentials by setting the column widths and inputting the headings
+  GRSThttpPrintf (bp,"<table border=\"1\" cellpadding=\"2\" cellspacing=\"0\" style=\"border-collapse: collapse\" bordercolor=\"#111111\" width=\"100%\" id=\"CredentialTable\">");
+  GRSThttpPrintf (bp,"<tr><td align=center width=\"10%\"><b>Credential No.</td><td align=center width=\"15%\"><b>Type</td><td align=left width=\"75%\"><b>Value</td></tr>");
+  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 <person> 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,"<tr><td align=center >New</td>");
+     GRSThttpPrintf(bp,"<td align=center >");
+     GRSThttpPrintf (bp, " <select size=\"1\" name=\"type\">\n");
+     GRSThttpPrintf (bp, " <option selected value=\"not_chosen\">(choose)</option>\n");
+     if (allow_new_person) GRSThttpPrintf (bp, " <option value=\"person\">Person &lt;dn&gt; &lt;/dn&gt;</option>\n");
+     GRSThttpPrintf (bp, " <option value=\"dn-list\">DN-List &lt;url&gt; &lt;/url&gt;</option>\n");
+     GRSThttpPrintf (bp, " <option value=\"dns\">DNS &lt;hostname&gt; &lt;/hostname&gt;</option>\n");
+     GRSThttpPrintf (bp, " <option value=\"voms\">VOMS &lt;fqan&gt; &lt;/fqan&gt;</option>\n");
+     // Only alow any-user credential to be chosen if it is  new entry
+     if (strcmp(cmd, "new_entry_form")==0) GRSThttpPrintf (bp, " <option value=\"any-user\">Any User</option>\n");
+     GRSThttpPrintf (bp, " </select></td>");
+   }
+
+  else { //Print out type and descriptor for existing cred
+
+    GRSThttpPrintf(bp,"<tr><td align=center >%d", cred_no);
+    if (admin) GRSThttpPrintf (bp,"<a href=\"%s%s?diruri=%s&cmd=del_cred_sure&entry_no=%d&cred_no=%d&timestamp=%d\">(Delete)</a>", dir_uri,admin_file,dir_uri, entry_no, cred_no, timestamp);
+    GRSThttpPrintf(bp, "</td><td align=center >%s ", cred->type);
+  }
+
+  if (strcmp(cred->type, "any-user")==0) GRSThttpPrintf (bp, "</td><td>&nbsp;"); /* Do not print out namevalue for any-user credential*/
+  else{
+    if (edit_values){ // Place namevalue in an editable box if appropriate
+      GRSThttpPrintf (bp, "<td align=left><input type=\"text\" name=\"cred%d_value\"\n", cred_no);
+      GRSThttpPrintf (bp, "size=\"50\" value=\"");
+      StringHTMLEncode(namevalue->value, bp);
+      GRSThttpPrintf (bp, "\">");
+    }
+    else if (strcmp(cred->type, "dn-list")==0){
+         GRSThttpPrintf(bp, "<td align=left ><a href=\"");
+        StringHTMLEncode(namevalue->value, bp);
+        GRSThttpPrintf(bp, " \">");
+        StringHTMLEncode(namevalue->value, bp);
+        GRSThttpPrintf(bp, "</a>");
+      }
+    else { GRSThttpPrintf(bp, "<td align=left> "); 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, "<font color=red><b>&nbsp;&lt;--</b></font>");
+  GRSThttpPrintf(bp, "</td></tr>");
+}
+
+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,"</table><br>\n"); return;}
+
+  GRSThttpPrintf (bp,"<tr><td align=center>");
+
+  if (admin) GRSThttpPrintf (bp,"<a href=\"%s%s?diruri=%s&cmd=add_cred_form&entry_no=%d&timestamp=%d\">Add&nbsp;Credential</a>", dir_uri,admin_file,dir_uri, entry_no, timestamp);
+
+  GRSThttpPrintf (bp, "</td>\n<td>&nbsp;</td><td align=left>");
+
+  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, "<b>Allowed:</b>  ");
+  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<input type=\"checkbox\" name=\"allow_%s\" value=\"ON\" checked>&nbsp;&nbsp;&nbsp;\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<input type=\"checkbox\" name=\"allow_%s\" value=\"ON\" unchecked>&nbsp;&nbsp;&nbsp;\n", grst_perm_syms[i],grst_perm_syms[i]);
+  }
+
+  if (edit_perms) GRSThttpPrintf (bp, "<p>");
+  GRSThttpPrintf (bp, "<b>Denied:&nbsp;</b>");
+  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<input type=\"checkbox\" name=\"deny_%s\" value=\"ON\" checked>&nbsp;&nbsp;&nbsp;\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<input type=\"checkbox\" name=\"deny_%s\" value=\"ON\" unchecked>&nbsp;&nbsp;&nbsp;\n", grst_perm_syms[i],grst_perm_syms[i]);
+  }
+
+  GRSThttpPrintf (bp, "</td></tr>");
+  GRSThttpPrintf (bp,"</table><br>\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<p><p> The ACL has been modified since it was last viewed\n<p>");
+    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<p><p> 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<p><p> You still have Admin permissions<p>\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,"&lt;");
+    else if (*current_char == '>')  GRSThttpPrintf (bp,"&gt;");
+    else if (*current_char == '&')  GRSThttpPrintf (bp,"&amp;");
+    else if (*current_char == '\'') GRSThttpPrintf (bp,"&apos;");
+    else if (*current_char == '"')  GRSThttpPrintf (bp,"&quot;");
+    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 (file)
index 0000000..9390dd1
--- /dev/null
@@ -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 <stdio.h>
+#include <time.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <fcntl.h>
+
+// when porting: remember that sendfile() is very OS-specific!
+#include <sys/sendfile.h>
+
+#include <gridsite.h>
+
+#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("<head><title>%s</title></head>\n", status);
+  printf("<body><h1   >%s</h1   ></body>\n", status);
+   
+  exit(0);
+}
+
+void adminfooter(GRSThttpBody *bp, char *dn, char *help_uri, char *dir_uri,
+                 char *admin_file)
+{
+  GRSThttpPrintf(bp, "<p><small>\n");  
+
+  if (dn != NULL) GRSThttpPrintf(bp, "<hr>You are %s<br>\n", dn);
+  else            GRSThttpPrintf(bp, "<hr>\n");
+
+  if (admin_file != NULL)
+       GRSThttpPrintf(bp, "<a href=\"%s%s?cmd=managedir\">"
+                      "Manage&nbsp;directory</a> .\n", 
+                      dir_uri, admin_file);
+  else GRSThttpPrintf(bp, "<a href=\"%s\">"
+                      "Back&nbsp;to&nbsp;directory</a> .\n", dir_uri);
+  
+  if (help_uri != NULL) 
+    GRSThttpPrintf(bp, "<a href=\"%s\">Website&nbsp;Help</a> .\n", help_uri);
+
+  if ((getenv("GRST_NO_LINK") == NULL) &&
+      (getenv("REDIRECT_GRST_NO_LINK") == NULL))
+    GRSThttpPrintf(bp, "Built with "
+     "<a href=\"http://www.gridpp.ac.uk/gridsite/\">GridSite</a> %s\n",
+     VERSION);
+   
+  GRSThttpPrintf(bp, "</small>\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 (file)
index 0000000..3c99002
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>              
+#include <string.h>
+#include <dirent.h>
+#include <ctype.h>
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#include <fnmatch.h>
+
+#include <libxml/xmlmemory.h>
+#include <libxml/tree.h>
+#include <libxml/parser.h>
+
+#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("&lt;",   fp);
+              else if (*q == '>')  fputs("&gt;",   fp);
+              else if (*q == '&')  fputs("&amp;" , fp);
+              else if (*q == '\'') fputs("&apos;", fp);
+              else if (*q == '"')  fputs("&quot;", fp);
+              else                 fputc(*q, fp);
+
+           fprintf(fp, "</%s>\n", p->name);
+
+           p = (GRSTgaclNamevalue *) p->next;
+         
+         } while (p != NULL);
+
+      fprintf(fp, "</%s>\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("<entry>\n", fp);
+  
+  for (cred = entry->firstcred; cred != NULL; cred = cred->next)
+                                            GRSTgaclCredPrint(cred, fp);
+
+  if (entry->allowed)
+    {
+      fputs("<allow>", fp);
+
+      for (i=GRST_PERM_READ; i <= GRST_PERM_ADMIN; ++i)
+       if ((entry->allowed) & i) GRSTgaclPermPrint(i, fp);
+
+      fputs("</allow>\n", fp);
+    }
+    
+
+  if (entry->denied)
+    {
+      fputs("<deny>", fp);
+
+      for (i=GRST_PERM_READ; i <= GRST_PERM_ADMIN; ++i)
+       if (entry->denied & i) GRSTgaclPermPrint(i, fp);
+
+      fputs("</deny>\n", fp);
+    }
+    
+  fputs("</entry>\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("<gacl version=\"0.0.1\">\n", fp);
+  
+  for (entry = acl->firstentry; entry != NULL; entry = entry->next)
+                                            GRSTgaclEntryPrint(entry, fp);
+
+  fputs("</gacl>\n", fp);
+
+  return 1;
+}
+
+int GRSTgaclAclSave(GRSTgaclAcl *acl, char *filename)
+{
+  int   ret;
+  FILE *fp;
+  
+  fp = fopen(filename, "w");
+  if (fp == NULL) return 0;
+  
+  fputs("<?xml version=\"1.0\"?>\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 <any-user/> */
+       
+       /* 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 (file)
index 0000000..c7b375e
--- /dev/null
@@ -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 <stdio.h>
+
+#include <time.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#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 (file)
index 0000000..797786d
--- /dev/null
@@ -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 <stdio.h>
+#include <sys/stat.h>
+#include <unistd.h>       
+#include <sys/types.h>
+#include <dirent.h>
+#include <string.h>
+#include <openssl/evp.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+
+#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", 
+                              &notbefore, &notafter, &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", 
+                              &notbefore, &notafter, &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 (file)
index 0000000..77f51e3
Binary files /dev/null and b/org.gridsite.core/src/htcp differ
diff --git a/org.gridsite.core/src/htcp.c b/org.gridsite.core/src/htcp.c
new file mode 100644 (file)
index 0000000..8f0ba7f
--- /dev/null
@@ -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 <pwd.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <string.h>
+#include <malloc.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <curl/curl.h>
+
+/* 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 (file)
index 0000000..05ae8ff
--- /dev/null
@@ -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 <apr_strings.h>
+
+#include <ap_config.h>
+#include <httpd.h>
+#include <http_config.h>
+#include <http_core.h>
+#include <http_log.h>
+#include <http_protocol.h>
+#include <http_request.h>
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>              
+#include <malloc.h>
+#include <string.h>
+#include <dirent.h>
+
+#include <time.h>
+
+#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, "<p><small>\n");
+
+    if (!isdirectory)
+      {
+        mtime_time = apr_time_sec(r->finfo.mtime);
+
+        localtime_r(&mtime_time, &mtime_tm);
+        strftime(modified, sizeof(modified), 
+                 "%a&nbsp;%e&nbsp;%B&nbsp;%Y", &mtime_tm);    
+        temp = apr_psprintf(r->pool,"<hr>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, 
+                            ". <a href=\"%s?cmd=history&file=%s\">"
+                            "View&nbsp;page&nbsp;history</a>\n",
+                            conf->adminfile, file);
+            out = apr_pstrcat(r->pool, out, temp, NULL);
+          }
+      }
+
+    out = apr_pstrcat(r->pool, out, "<hr>", 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<br>\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, 
+                     "<a href=\"%s?cmd=edit&file=%s\">"
+                     "Edit&nbsp;page</a> .\n", conf->adminfile, file);
+                out = apr_pstrcat(r->pool, out, temp, NULL);
+              }
+                 
+            if (GRSTgaclPermHasList(perm) || GRSTgaclPermHasWrite(perm))
+              {
+                temp = apr_psprintf(r->pool, 
+                 "<a href=\"%s%s?cmd=managedir\">Manage&nbsp;directory</a> .\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,
+                   "<a href=\"http://%s%s\">Switch&nbsp;to&nbsp;HTTP</a> \n", 
+                   r->server->server_hostname, r->unparsed_uri);
+    else temp = apr_psprintf(r->pool,
+                   "<a href=\"https://%s%s\">Switch&nbsp;to&nbsp;HTTPS</a> \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,
+                   ". <a href=\"%s\">Website&nbsp;Help</a>\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, ". <a href=\"%s?cmd=print&file=%s\">"
+               "Print&nbsp;View</a>\n", conf->adminfile, file);
+        out = apr_pstrcat(r->pool, out, temp, NULL);
+      }
+
+    if (conf->gridsitelink)
+      {
+        temp = apr_psprintf(r->pool,
+           ". Built with <a href=\"http://www.gridpp.ac.uk/gridsite/\">"
+           "GridSite</a>&nbsp;%s\n", VERSION);
+        out = apr_pstrcat(r->pool, out, temp, NULL);
+      }
+                     
+    out = apr_pstrcat(r->pool, out, "\n</small>\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, "<body");
+        if (p == NULL) p = strstr(buf, "<BODY");
+        if (p == NULL) p = strstr(buf, "<Body");
+
+        if (p == NULL) 
+          {
+            head_formatted = apr_pstrdup(r->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 </body> tag from body **** */
+
+    p = strstr(body_formatted, "</body");
+    if (p == NULL) p = strstr(body_formatted, "</BODY");
+    if (p == NULL) p = strstr(body_formatted, "</Body");
+    
+    if (p != NULL) *p = '\0';
+
+    /* **** set up dynamic part of footer to go at end of body **** */
+
+    admin_formatted = make_admin_footer(r, conf, FALSE);
+    
+    /* **** 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 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, 
+      "<head><title>Directory listing %s</title></head>\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, "<body bgcolor=white>");
+          }
+        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 bgcolor=white>");
+            
+    body_formatted = apr_psprintf(r->pool, 
+      "<h1>Directory listing %s</h1>\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, "<p><table>\n", NULL);
+
+    if (r->unparsed_uri[1] != '\0')
+     body_formatted = apr_pstrcat(r->pool, body_formatted, 
+        "<tr><td colspan=3>[<a href=\"../\">Parent directory</a>]</td></tr>\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), 
+              "<td align=right>%R</td><td align=right>%e&nbsp;%b&nbsp;%y</td>",
+                        &mtime_tm);    
+                              
+               if (S_ISDIR(statbuf.st_mode))
+                    temp = apr_psprintf(r->pool, 
+                      "<tr><td><a href=\"%s/\" "
+                      "last-modified=\"%ld\">%s/</a></td>"
+                      "<td align=right>%ld</td>%s</tr>\n", 
+                      namelist[n]->d_name, statbuf.st_mtime,
+                      namelist[n]->d_name, 
+                      statbuf.st_size, modified);
+               else temp = apr_psprintf(r->pool, 
+                      "<tr><td><a href=\"%s\" content-length=\"%ld\" "
+                      "last-modified=\"%ld\">"
+                      "%s</a></td>"
+                      "<td align=right>%ld</td>%s</tr>\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, "</table>\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, "</body>");
+          }
+        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, "</body>");
+      }
+
+    /* **** 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), 
+              "<td align=right>%R</td><td align=right>%e&nbsp;%b&nbsp;%y</td>",
+                       &mtime_tm);
+
+                  oneline = apr_psprintf(pool,
+                                     "<tr><td><a href=\"%s\" "
+                                     "content-length=\"%ld\" "
+                                     "last-modified=\"%ld\">"
+                                     "%s</a></td>"
+                                     "<td align=right>%ld</td>%s</tr>\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, 
+      "<head><title>Directory listing %s</title></head>\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, "<body bgcolor=white>");
+          }
+        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 bgcolor=white>");
+            
+    body = apr_psprintf(r->pool, 
+      "<h1>Directory listing %s</h1>\n<table>", r->uri);
+
+    if ((r->uri)[1] != '\0')
+     body = apr_pstrcat(r->pool, body, 
+       "<tr><td>[<a href=\"../\">Parent directory</a>]</td></tr>\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,
+           "<form action=\"%s%s\" method=post>\n"
+           "<input type=hidden name=cmd value=managedir>"
+           "<tr><td colspan=4 align=center><small><input type=submit "
+           "value=\"Manage directory\"></small></td></tr></form>\n",
+           r->uri, conf->adminfile);
+          
+        body = apr_pstrcat(r->pool, body, oneline, NULL);
+      } 
+
+    body = apr_pstrcat(r->pool, body, "</table>\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, "</body>");
+          }
+        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, "</body>");
+
+    /* **** 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 (file)
index 0000000..bcf759a
--- /dev/null
@@ -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 <ssl.h>
+
+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 (file)
index 0000000..0b34aae
Binary files /dev/null and b/org.gridsite.core/src/real-gridsite-admin.cgi differ
diff --git a/org.gridsite.core/src/roffit b/org.gridsite.core/src/roffit
new file mode 100755 (executable)
index 0000000..d1c7263
--- /dev/null
@@ -0,0 +1,370 @@
+#!/usr/bin/env perl 
+#
+# roffit: convert man page source files to HTML
+#
+# Read an nroff file. Output a HTML file.
+#
+# This is a very simple script, but I use it on very simple man pages and I've
+# found no other script that makes beautiful web pages.
+#
+my $version = "0.3"; # (14 November 2003)
+# Author:  Daniel Stenberg <daniel@haxx.se>
+# 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 <a name> name for the SH section
+#     - added <a href> 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 class=\"level$indentlevel\">", @p;
+}
+
+sub defaultcss {
+    print $OutFH <<ENDOFCSS
+<STYLE type="text/css">
+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%;
+}
+</STYLE>
+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 <span> sections we should convert
+# to proper links
+sub linkfile {
+    my @new;
+    for(@out) {
+        my $line=$_;
+        my $l;
+        while($line =~ s/<span class=\"(emphasis|bold)\">([^<]*)<\/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="<a class=\"$style\" href=\"#$l\">$name</a>";
+            }
+            else {
+                $link="<span Class=\"$style\">$name</span>";
+            }
+            $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, "</pre>\n";
+                    $pre = 0;
+                }
+
+                my $name = text2name($rest);
+                $anchor{$name}=1;
+
+                $rest =~ s/\"//g; # cut off quotes
+                $rest =~ s/</&lt;/g;
+                $rest =~ s/>/&gt;/g;
+                $out = "<a name=\"$name\"></a><h2 class=\"nroffsh\">$rest</h2>";
+                $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/</&lt;/g;
+                $rest =~ s/>/&gt;/g;
+                push @p, "<span class=\"bold\">$rest</span> ";
+            }
+            elsif($keyword =~ /^I$/i) {
+                $rest =~ s/\"//g; # cut off quotes
+                $rest =~ s/</&lt;/g;
+                $rest =~ s/>/&gt;/g;
+                push @p, "<span class=\"emphasis\">$rest</span> ";
+            }
+            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 <pre> section
+                showp(@p);
+                @p="";
+                push @out, "<pre>\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/</&lt;/g;
+                $rest =~ s/>/&gt;/g;
+                
+                $indentlevel-- if ($indentlevel);
+                push @p, "<a name=\"$name\"></a><span class=\"nroffip\">$rest</span> ";
+                # 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, "<span class=\"manpage\">$_</span> ";
+                }
+            }
+            else {
+                showp(@p);
+                print $debugFH "ALERT: unknown keyword \"$keyword\"\n";
+            }
+        }
+        else {
+            # text line, decode \-stuff
+            my $txt = $in;
+
+            $txt =~ s/</&lt;/g;
+            $txt =~ s/>/&gt;/g;
+            $txt =~ s/\\&//g; # cut off \&
+            $txt =~ s/\\fI/<span class=\"emphasis\">/g;
+            $txt =~ s/\\fB/<span class=\"bold\">/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/^ /\&nbsp\;/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 <<MOO
+<html><head>
+<title>$title</title>
+<meta name="generator" content="roffit $version">
+MOO
+    ;
+    defaultcss();
+    print "</head><body>\n";
+}
+
+print $OutFH @conv;
+print $OutFH <<ROFFIT
+<p class="roffit">
+ This HTML page was made with <a href="http://daniel.haxx.se/projects/roffit/">roffit</a>.
+ROFFIT
+    ;
+
+if($standalone) {
+    print "</body></html>\n";
+}
diff --git a/org.gridsite.core/src/urlencode.c b/org.gridsite.core/src/urlencode.c
new file mode 100644 (file)
index 0000000..8890689
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+
+#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;
+}