Index: main/trunk/greenstone2/common-src/cgi-bin/gliserver.pl
===================================================================
--- main/trunk/greenstone2/common-src/cgi-bin/gliserver.pl	(revision 28957)
+++ main/trunk/greenstone2/common-src/cgi-bin/gliserver.pl	(revision 28958)
@@ -196,6 +196,6 @@
     }
 
-    my $users_db_content;
     if($gsdl_cgi->greenstone_version() == 2) {
+	my $users_db_content;
 	my $etc_directory = &util::filename_cat($ENV{'GSDLHOME'}, "etc");
 	my $users_db_file_path = &util::filename_cat($etc_directory, "users.gdb");
@@ -208,23 +208,4 @@
 	}
 	close(USERS_DB);
-    }
-    elsif($gsdl_cgi->greenstone_version() == 3) {
-	my $gsdl3srchome = $ENV{'GSDL3SRCHOME'};
-
-	my $java = $gsdl_cgi->get_java_path();
-	my $java_gsdl3_classpath = &util::filename_cat($gsdl3srchome, "web", "WEB-INF", "lib", "gsdl3.jar");
-	my $java_derby_classpath = &util::filename_cat($gsdl3srchome, "web", "WEB-INF", "lib", "derby.jar");
-	my $java_classpath;
-	my $gsdlos = $ENV{'GSDLOS'};
-	if ($gsdlos !~ m/windows/){
-	    $java_classpath = $java_gsdl3_classpath . ":" . $java_derby_classpath;
-	}else{
-	    $java_classpath = $java_gsdl3_classpath . ";" . $java_derby_classpath;
-	}
-	my $java_args = &util::filename_cat($gsdl3srchome, "web", "sites", $site, "etc", "usersDB");
-	$gsdl_cgi->checked_chdir($java_args);
-	my $java_command="\"$java\" -classpath \"$java_classpath\" org.greenstone.gsdl3.util.usersDB2txt \"$java_args\" 2>&1";
-	$users_db_content = `$java_command`;
-    }
     
     # Get the user account information from the usersDB database
@@ -232,5 +213,5 @@
 
     # a line dividing one user entry from another is made up of 70 hyphens for GS2 (37 hyphens for GS3)
-    my $horizontal_divider = ($gsdl_cgi->greenstone_version() == 2) ? q/-{70}/ : q/-{37}/;
+    my $horizontal_divider = q/-{70}/;
     foreach my $users_db_entry (split($horizontal_divider, $users_db_content)) {	
 	if ($users_db_entry =~ m/\n?\[(.+)\]\n/ || $users_db_entry =~ m/\n?USERNAME = ([^\n]*)\n/) { # GS2 and GS3 formats
@@ -246,5 +227,5 @@
 
     # Check password
-    my $pwdLine = ($gsdl_cgi->greenstone_version() == 2) ? q/\<password\>(.*)/ : q/\n?PASSWORD = (.*)\n/;
+    my $pwdLine = q/\<password\>(.*)/;
     my ($valid_user_password) = ($user_data =~ m/$pwdLine/);
     if ($user_password ne $valid_user_password) {
@@ -253,5 +234,5 @@
 
     # Check group
-    my $groupLine = ($gsdl_cgi->greenstone_version() == 2) ? q/\<groups\>(.*)/ : q/\n?GROUPS = (.*)\n/;
+    my $groupLine = q/\<groups\>(.*)/;
     my ($user_groups) = ($user_data =~ m/$groupLine/);
 
@@ -276,4 +257,36 @@
     }
     $gsdl_cgi->generate_error("Authentication failed: user is not in the required group.");
+	}
+	
+	# "GS3\web\WEB-INF\lib\gsdl3.jar;GS3\web\WEB-INF\lib\derby.jar" 
+	# org.greenstone.gsdl3.util.usersDBRealm2txt "GSDL3SRCHOME" username pwd <col> 2>&1
+	elsif($gsdl_cgi->greenstone_version() == 3) {
+		my $gsdl3srchome = $ENV{'GSDL3SRCHOME'};
+
+		my $java = $gsdl_cgi->get_java_path();
+		my $java_gsdl3_classpath = &util::filename_cat($gsdl3srchome, "web", "WEB-INF", "lib", "gsdl3.jar");
+		my $java_derby_classpath = &util::filename_cat($gsdl3srchome, "web", "WEB-INF", "lib", "derby.jar");
+		my $java_classpath;
+		my $gsdlos = $ENV{'GSDLOS'};
+		if ($gsdlos !~ m/windows/){
+			$java_classpath = $java_gsdl3_classpath . ":" . $java_derby_classpath;
+		}else{
+			$java_classpath = $java_gsdl3_classpath . ";" . $java_derby_classpath;
+		}		
+		my $java_args = "\"$gsdl3srchome\" \"$username\" \"$user_password\"";
+		if ($collection ne "") {
+			$java_args += " \"$collection\"";
+		}
+		
+		$gsdl_cgi->checked_chdir($gsdl3srchome);	
+		my $java_command="\"$java\" -classpath \"$java_classpath\" org.greenstone.gsdl3.util.ServletRealmCheck $java_args 2>&1"; # call it ServletRealmCheck
+		my $java_output = `$java_command`;
+		if ($java_output =~ m/^Authentication failed:/) { # $java_output contains the error message
+			$gsdl_cgi->generate_error($java_output); # "\nJAVA_COMMAND: $java_command\n"
+		}
+		else { # success, $java_output is the user_groups list			
+			return $java_output; 
+		}
+    }
 }
 
@@ -898,5 +911,5 @@
     foreach $sites_dir(@sites_dir)
     {
-	if (!(($sites_dir eq ".") || ($sites_dir eq "..") || ($sites_dir eq "CVS") || ($sites_dir eq ".DS_Store")))
+	if (!(($sites_dir eq ".") || ($sites_dir eq "..") || ($sites_dir eq "CVS") || ($sites_dir eq ".DS_Store") || ($sites_dir eq "ADDING-A-SITE.txt")))
 	{
 	    my $site_dir_path= &util::filename_cat($sites_directory,$sites_dir);
Index: main/trunk/greenstone3/src/java/org/greenstone/gsdl3/action/SystemAction.java
===================================================================
--- main/trunk/greenstone3/src/java/org/greenstone/gsdl3/action/SystemAction.java	(revision 28957)
+++ main/trunk/greenstone3/src/java/org/greenstone/gsdl3/action/SystemAction.java	(revision 28958)
@@ -50,4 +50,7 @@
 			to = coll;
 		}
+		else if(subaction.equals("authenticated-ping")) {
+			to = "RemoteAuthentication"; // not "Authentication/RemoteAuthentication": MessageRouter knows to map the RemoteAuthentication service to the Authentication module
+		}
 
 		Element mr_request_message = doc.createElement(GSXML.MESSAGE_ELEM);
@@ -85,4 +88,21 @@
 			system.setAttribute(GSXML.TYPE_ATT, GSXML.SYSTEM_TYPE_PING);
 		}
+		else if (subaction.equals("authenticated-ping")) { // can check whether a given username and password authenticates
+		
+			String username = (String) params.get(GSParams.UN);
+			String password = (String) params.get(GSParams.PW);
+			
+			
+			system.setAttribute(GSXML.TYPE_ATT, GSXML.SYSTEM_TYPE_AUTHENTICATED_PING);
+			system.setAttribute(GSXML.USERNAME_ATT, username);
+			system.setAttribute(GSXML.PASSWORD_ATT, password);
+			
+			if(params.containsKey("col")) {//params.containsKey(GSParams.COLLECTION)) {
+				String collection = (String) params.get("col");//(String) params.get(GSParams.COLLECTION);
+				system.setAttribute(GSXML.COLLECTION_ATT, collection);
+			}
+			
+		}
+		
 		//else if (subaction.equals("is-persistent")){
 		//	system.setAttribute(GSXML.TYPE_ATT, GSXML.SYSTEM_TYPE_ISPERSISTENT);
Index: main/trunk/greenstone3/src/java/org/greenstone/gsdl3/service/Authentication.java
===================================================================
--- main/trunk/greenstone3/src/java/org/greenstone/gsdl3/service/Authentication.java	(revision 28957)
+++ main/trunk/greenstone3/src/java/org/greenstone/gsdl3/service/Authentication.java	(revision 28958)
@@ -128,4 +128,5 @@
 	protected static final String GET_USER_INFORMATION_SERVICE = "GetUserInformation";
 	protected static final String CHANGE_USER_EDIT_MODE_SERVICE = "ChangeUserEditMode";
+	protected static final String REMOTE_AUTHENTICATION_SERVICE = "RemoteAuthentication";
 
 	protected static boolean _derbyWrapperDoneForcedShutdown = false;
@@ -181,4 +182,10 @@
 		changeEditMode_service.setAttribute(GSXML.NAME_ATT, CHANGE_USER_EDIT_MODE_SERVICE);
 		this.short_service_info.appendChild(changeEditMode_service);
+		
+		Element remoteAuthentication_service = this.doc.createElement(GSXML.SERVICE_ELEM);
+		remoteAuthentication_service.setAttribute(GSXML.TYPE_ATT, GSXML.SERVICE_TYPE_PROCESS);
+		remoteAuthentication_service.setAttribute(GSXML.NAME_ATT, REMOTE_AUTHENTICATION_SERVICE);
+		this.short_service_info.appendChild(remoteAuthentication_service);
+		
 
 		DerbyWrapper.createDatabaseIfNeeded();
@@ -227,4 +234,9 @@
 			authen_service.setAttribute(GSXML.NAME_ATT, CHANGE_USER_EDIT_MODE_SERVICE);
 		}
+		else if (service_id.equals(REMOTE_AUTHENTICATION_SERVICE))
+		{
+			authen_service.setAttribute(GSXML.TYPE_ATT, GSXML.SERVICE_TYPE_PROCESS);
+			authen_service.setAttribute(GSXML.NAME_ATT, REMOTE_AUTHENTICATION_SERVICE);
+		}		
 		else
 		{
@@ -287,4 +299,76 @@
 	}
 
+	/**
+	 * This method replaces the gliserver.pl code for authenticating a user against the derby database
+	 * gliserver.pl needed to instantiate its own JVM to access the derby DB, but the GS3 already has
+	 * the Derby DB open and 2 JVMs are not allowed concurrent access to an open embedded Derby DB.
+	 * Gliserver.pl now goes through this method (via ServletRealmCheck.java), thereby using the same 
+	 * connection to the DerbyDB. This method reproduces the same behaviour as gliserver.pl used to,
+	 * by returning the user_groups on successful authentication, else returns the specific 
+	 * "Authentication failed" messages that glisever.pl would produce.
+	 * http://remote-host-name:8383/greenstone3/library?a=s&sa=authenticated-ping&excerptid=gs_content&un=admin&pw=<PW>&col=demo
+	*/
+	protected Element processRemoteAuthentication(Element request) {
+		//logger.info("*** Authentication::processRemoteAuthentication");	
+		
+		String message = "";
+		
+		Element system = (Element) GSXML.getChildByTagName(request, GSXML.REQUEST_TYPE_SYSTEM);		
+		String username = system.hasAttribute("username") ? system.getAttribute("username") : "";
+		String password = system.hasAttribute("password") ? system.getAttribute("password") : "";
+		
+		
+		// If we're not editing a collection then the user doesn't need to be in a particular group
+		String collection = system.hasAttribute("collection") ? system.getAttribute("collection") : "";
+				
+		
+		if(username.equals("") || password.equals("")) {
+			message = "Authentication failed: no (username or) password specified.";
+			//logger.error("*** Remote login failed. No username or pwd provided");
+		}		
+		else {		
+			String storedPassword = retrieveDataForUser(username, "password");
+			if(storedPassword != null && (password.equals(storedPassword) || hashPassword(password).equals(storedPassword))) {
+				
+				// gliserver.pl used to return the groups when authentication succeeded
+				String groups = retrieveDataForUser(username, "groups"); //comma-separated list
+				
+				if(collection.equals("")) {
+					message = groups;
+				} else { 					
+					
+					if(groups.indexOf("all-collections-editor") != -1) { // Does this user have access to all collections?
+						message = groups;
+					} else if(groups.indexOf("personal-collections-editor") != -1 && collection.startsWith(username+"-")) { // Does this user have access to personal collections, and is this one?
+						message = groups;
+					} else if(groups.indexOf(collection+"-collection-editor") != -1) { //  Does this user have access to this collection?
+						message = groups;
+					}
+					else {
+						message = "Authentication failed: user is not in the required group.";
+						//logger.error("*** Remote login failed. Groups did not match for the collection specified");
+					}
+				}
+				
+			} else {
+				
+				if(storedPassword == null) {
+					message = "Authentication failed: no account for user '" + username + "'";
+					//logger.error("*** Remote login failed. User not found or password not set for user.");
+				} else {
+					message = "Authentication failed: incorrect password.";
+					//logger.error("*** Remote login failed. Password did not match for user");
+				}
+			}
+		}
+		
+		Element result = this.doc.createElement(GSXML.RESPONSE_ELEM);
+		result.setAttribute(GSXML.FROM_ATT, REMOTE_AUTHENTICATION_SERVICE);
+		result.setAttribute(GSXML.TYPE_ATT, GSXML.REQUEST_TYPE_PROCESS);		
+		Element s = GSXML.createTextElement(this.doc, GSXML.STATUS_ELEM, message);
+		result.appendChild(s);
+		return result;
+	}
+	
 	protected Element processGetUserInformation(Element request)
 	{
Index: main/trunk/greenstone3/src/java/org/greenstone/gsdl3/util/GSParams.java
===================================================================
--- main/trunk/greenstone3/src/java/org/greenstone/gsdl3/util/GSParams.java	(revision 28957)
+++ main/trunk/greenstone3/src/java/org/greenstone/gsdl3/util/GSParams.java	(revision 28958)
@@ -32,4 +32,7 @@
 	public static final String OUTPUT = "o"; // if processing is to be done, what type of output - html/xml/other??
 	public static final String SERVICE = "s"; // the name of the service
+	
+	public static final String UN = "un"; // username for authenticated-ping
+	public static final String PW = "pw"; // pwd for authenticated-ping
 
 	public static final String CLUSTER = "c"; // these two are the same
Index: main/trunk/greenstone3/src/java/org/greenstone/gsdl3/util/GSXML.java
===================================================================
--- main/trunk/greenstone3/src/java/org/greenstone/gsdl3/util/GSXML.java	(revision 28957)
+++ main/trunk/greenstone3/src/java/org/greenstone/gsdl3/util/GSXML.java	(revision 28958)
@@ -243,4 +243,5 @@
 	public static final String SYSTEM_TYPE_DEACTIVATE = "deactivate";
 	public static final String SYSTEM_TYPE_PING = "ping";
+	public static final String SYSTEM_TYPE_AUTHENTICATED_PING = "authenticated-ping";
 	//public static final String SYSTEM_TYPE_ISPERSISTENT = "is-persistent";
 
@@ -287,4 +288,7 @@
 	public static final String BASE_URL = "baseURL";
 
+	// only for authenticated-ping
+	public static final String PASSWORD_ATT = "password";
+	
 	//for classifiers
 	public static final String CHILD_TYPE_ATT = "childType";
Index: main/trunk/greenstone3/src/java/org/greenstone/gsdl3/util/ServletRealmCheck.java
===================================================================
--- main/trunk/greenstone3/src/java/org/greenstone/gsdl3/util/ServletRealmCheck.java	(revision 28958)
+++ main/trunk/greenstone3/src/java/org/greenstone/gsdl3/util/ServletRealmCheck.java	(revision 28958)
@@ -0,0 +1,136 @@
+/*
+ *    ServletRealmCheck.java
+ *    Copyright (C) 2008 New Zealand Digital Library, http://www.nzdl.org
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License
+ *    along with this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package org.greenstone.gsdl3.util;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Properties;
+
+/**
+ * Commandline script that is used by gliserver.pl to authenticate a username and password and 
+ * return the user's groups, while the derby server is running. Because 2 JVM instances can't
+ * access the same embedded derby server at the same time, gliserver can't call usersDB2txt.java.
+ * If a collection parameter is additionally provided, this script will check the user's groups
+ * to see if any of these allow the user to edit that collection.
+ *
+ * Run as java org.greenstone.gsdl3.util.ServletRealmCheck <GS3SRCHOME> <un> <pwd> [colname]
+ *
+ * GS3\src\java>"C:\Program Files\Java\jdk1.6.0_22\bin\java" 
+ * 		-classpath "GS3\web\WEB-INF\lib\gsdl3.jar;GS3\web\WEB-INF\lib\derby.jar" 
+ * 		org.greenstone.gsdl3.util.ServletRealmCheck "GS3" admin <pw> 2>&1
+ *
+ * Tries URL: http://hostname:8383/greenstone3/library?a=s&sa=authenticated-ping&excerptid=gs_content&un=admin&pw=<pw>[&col=demo] 
+ * The &excerptid=gs_content in the URL will return just the <div id="gs_content" /> part of the 
+ * page that we're interested in.
+ *
+ * Result: either prints out an error message ("Authentication failed...") or a positive result, 
+ * which is the user's groups. For the admin user example: administrator,all-collections-editor.
+ *
+*/
+public class ServletRealmCheck
+{	
+    public static void main(String[] args) {
+	
+	if (args.length < 3 || args.length > 4){
+		System.out.println("Run with: <GSDL3SRCHOME> <un> <pwd> [collection-name]");	    
+	    System.exit(0);
+	}
+	
+	String gsdl3srchome = args[0];
+	String username = args[1];
+	String password = args[2];
+	String collection = (args.length > 3) ? args[3] : null;		
+	
+	//System.err.println("gsdl3srchome: " + gsdl3srchome);
+	//System.err.println("username: " + username);
+	//System.err.println("password: " + password);
+	//System.err.println("collection: " + collection);
+	
+	
+	// Load the build.properties file, get the GS3 server URL and send authenticated-ping and print the return result
+	
+	//http://www.mkyong.com/java/java-properties-file-examples/
+	Properties buildProps = new Properties();
+	InputStream input = null;
+ 
+	try {
+		File buildPropsFile = new File(gsdl3srchome, "build.properties");
+		input = new FileInputStream(buildPropsFile);
+ 
+		// load a properties file
+		buildProps.load(input);
+ 
+		// get the property value and print it out
+		String servername = buildProps.getProperty("tomcat.server");
+		String port = buildProps.getProperty("tomcat.port");
+		int portNum = Integer.parseInt(port);
+		
+		// Appending &excerptid=gs_content will get just the <div ... id="gs_content"/> from the final web page:
+		String urlSuffix = "/greenstone3/library?a=s&sa=authenticated-ping&excerptid=gs_content&un="+username+"&pw="+password;
+		if(collection != null) {
+			urlSuffix = urlSuffix + "&col="+collection;
+		}
+		URL authenticationUrl = new URL("http", servername, portNum, urlSuffix);
+		
+		HttpURLConnection conn = (HttpURLConnection)authenticationUrl.openConnection();		
+		BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
+		String result = "";
+		String line = null;
+		
+		while((line = reader.readLine()) != null) {
+			result += line;
+		}
+		
+		//System.err.println("** Sent: " + authenticationUrl);
+		//System.err.println("** Got result:\n" + result);
+		
+		// Parse out the content nested inside <div ... id="gs_content"> </div>
+		int start = result.indexOf("id=\"gs_content\">");
+		if(start != -1) {
+			start = result.indexOf(">", start);
+			int end = result.indexOf("<", start);
+			result = result.substring(start+1, end);
+			result = result.trim();
+		}		
+		
+		// Now we finally have what we actually want to print out for the caller to use
+		System.out.print(result); // don't add newline to end
+		
+	} catch (IOException ex) {
+		ex.printStackTrace();
+	} finally {
+		if (input != null) {
+			try {
+				input.close();
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+		}
+	}
+	
+  }	
+   
+}
