/* ======================================================================
The Bodington System Software License, Version 1.0
  
Copyright (c) 2001 The University of Leeds.  All rights reserved.
  
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

1.  Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2.  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.

3.  The end-user documentation included with the redistribution, if any,
must include the following acknowledgement:  "This product includes
software developed by the University of Leeds
(http://www.bodington.org/)."  Alternately, this acknowledgement may
appear in the software itself, if and wherever such third-party
acknowledgements normally appear.

4.  The names "Bodington", "Nathan Bodington", "Bodington System",
"Bodington Open Source Project", and "The University of Leeds" must not be
used to endorse or promote products derived from this software without
prior written permission. For written permission, please contact
d.gardner@leeds.ac.uk.

5.  The name "Bodington" may not appear in the name of products derived
from this software without prior written permission of the University of
Leeds.

THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO,  TITLE,  THE IMPLIED WARRANTIES 
OF QUALITY  AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO 
EVENT SHALL THE UNIVERSITY OF LEEDS OR ITS 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 software was originally created by the University of Leeds and may contain voluntary 
contributions from others.  For more information on the Bodington Open Source Project, please 
see http://bodington.org/

====================================================================== */

package org.bodington.server.realm;

import org.apache.log4j.Logger;

import java.rmi.RemoteException;
import java.io.*;
import java.util.*;
import java.sql.*;
import java.math.BigInteger;
import java.security.cert.*;

import org.bodington.database.*;
import org.bodington.server.*;
import org.bodington.server.resources.*;
import org.bodington.servlet.EscapedHtmlWriter;
import org.bodington.util.PasswordGenerator;
import org.bodington.util.RandomPasswordGenerator;
import org.bodington.util.ISSPasswordGenerator;
import org.bodington.util.DobPasswordGenerator;
import org.bodington.util.PasswordGeneratorException;

public class UserManagementSessionImpl 
	extends org.bodington.server.SingleResourceSession
	implements UserManagementSession
	{
    
    private static Logger log = Logger.getLogger(UserManagementSessionImpl.class);
	
	private PrimaryKey alias_id=null;
	private String type_name=null;
	private PasswordGenerator password_generator;
	private PasswordGenerator iss_generator;
	private PasswordGenerator dob_generator;
	
	public UserManagementSessionImpl()
		throws RemoteException
		{
		super();
		password_generator = new RandomPasswordGenerator();
		iss_generator = new ISSPasswordGenerator();
		dob_generator = new DobPasswordGenerator();
		}
	
	private Alias getAlias()
		throws BuildingServerException
		{
		return Alias.findAlias( alias_id );
		}
	
	public synchronized void setPasswordGeneratorClass( String pgen_class )
		throws RemoteException, BuildingServerException
		{
		try
			{
			Class pg_class = Class.forName( pgen_class );
			if ( pg_class == null )
				{
				throw new BuildingServerException( "Unknown password generator." );
				}
			
			if ( !PasswordGenerator.class.isAssignableFrom( pg_class ) )
				{
				throw new BuildingServerException( "Specified module is not a type of password generator." );
				}
			
			PasswordGenerator pg = (PasswordGenerator)pg_class.newInstance();
			
			if ( pg == null )
				{
				throw new BuildingServerException( "Unable to create a Password Generator." );
				}
			password_generator = pg;
			}			
		catch ( Exception ex )
			{
			throw new BuildingServerException( "Problem setting up password generator: " + ex.toString() );
			}
		}
	

	// set the alias that will be used when creating users.
	public synchronized void setAliasName( String name )
		throws RemoteException, BuildingServerException
		{
		prepareMethodCall();
		try
			{
			AliasEditor editor = getAliasEditor();
			Zone zone = editor.getEffectiveZone();
			alias_id = null;
			type_name = null;
			Alias selected_alias=null;
			
	    	if ( name==null || name.length()==0 )
	    		throw new BuildingServerException( 
	    			"null or empty alias name.", 
	    			"ERROR: When creating users you must select a type of unique identifier." );

	    	selected_alias = Alias.findAlias( "zone_id = " + 
	      							zone.getZoneId().toString() + 
	       							" AND alias_name = '" +
	       							name + 
	       							"'" );
	    	if ( selected_alias==null )
    			throw new BuildingServerException( 
	    			"findAlias returned null.", 
    				"ERROR: The selected unique identifier couldn't be found within the administrative zone " +
    				zone.getName() + "." );

	    	if ( !selected_alias.isPrimary() )
    			throw new BuildingServerException( 
	    			"Selected alias not primary.", 
	        		"ERROR: The selected unique identifier isn't a primary unique identifier." );
		        	
	    	alias_id = selected_alias.getAliasId();
			}
		finally
			{
			disposeMethodCall();
			}
		}
	
	public synchronized void setTypeName( String type )
		throws RemoteException, BuildingServerException
		{
		prepareMethodCall();
		try
			{
			type_name = null;
			
	    	Alias alias = getAlias();
			if ( alias==null )
    			throw new BuildingServerException( 
	    			"setTypeName called while alias not set.", 
    				"ERROR: An attempt to select a type of new user was made before a valid unique identifier has been selected." );

        	if ( type==null || type.equalsIgnoreCase( "don't know" ) )
    			throw new BuildingServerException( 
	    			"null or indecisive new user type selected.", 
    				"ERROR: You have to select a valid category of user." );
		        	
        	if ( !alias.hasUserCategory( type ) )
    			throw new BuildingServerException( 
	    			"Invalid new user type selected.", 
	        		"ERROR: The selected unique identifier cannot be used to create users of the type selected." );
		    
	    	type_name = type;
			}
		finally
			{
			disposeMethodCall();
			}
		}
	
	private synchronized AliasEditor getAliasEditor()
		throws BuildingServerException
		{
		Resource r = getResource();
		if ( r == null || !(r instanceof AliasEditor) )
			throw new BuildingServerException( 
				"Wrong type of session for this type of resource.",
				"There was a technical problem attempting to access this tool." );
				
		return (AliasEditor)r;
		}
		
	private synchronized String chooseUserName( Zone zone, User user, Vector pass_phrases )
		throws BuildingServerException
		{
		int i, j;
		String name;
		PassPhrase p;
		
		// try a series of user names with increasing numbers in
		for ( i=1; i<100; i++ )
			{
			// proposed user name
			name=zone.getPrefix() + i + user.getInitials().toLowerCase();
			
			// check a list of user names that are about to be created...
			for ( j=0; j<pass_phrases.size(); j++ )
				{
				p=(PassPhrase)pass_phrases.elementAt( j );
				//is this one already assigned?
				if ( p.getUserName().equals( name ) )
					break;
				}
		    // go around again if proposed user name was assigned
		    // to another new user.
			if ( j<pass_phrases.size() )
				continue;

			p=PassPhrase.findPassPhraseByUserName( name );
			//if noone in the db has this user name it's OK
			if ( p==null )
				return name;
		    //if it is this person who already has the user name it's also OK
	        if ( p.getUserId().equals( user.getUserId() ) )
				return name;
			//otherwise go around again and try another user name
			}
		return null;
		}
		
	public synchronized String createUsers( BufferedReader reader, PrintWriter writer )
		throws RemoteException, BuildingServerException
	    {
		prepareMethodCall();
		try
			{
	    	AliasEditor editor = getAliasEditor();
	    	if ( editor == null )
	    		throw new BuildingServerException( "Can't find correct resource." );
	    	if ( !editor.checkPermission( Permission.EDIT ) )
				throw new BuildingServerException( "Edit access denied",
						"You don't have 'edit' level access to this resource." );


		    
			if ( alias_id == null )
				throw new BuildingServerException( "No alias.",
						"You have to select a unique identifier before you can create users." );
			if ( type_name == null )
				throw new BuildingServerException( "No type name.",
						"You have to select a type of new user before you can create users." );

	    	Vector group_list=new Vector();
	    	Vector entry_list=new Vector();
	    	Vector user_list=new Vector();
	    	Vector username_list=new Vector();
	    	Vector passphrase_list=new Vector();
		    
	    	Vector extras=new Vector();
		   String extra_field;
		   
			Alias alias;
			User user;
			PassPhrase otherpass;
	    	Zone zone = editor.getEffectiveZone();
	    	Group group;
	    	String passphrase, initials;
		    
	    	StringBuffer groups= new StringBuffer();
        	StringBuffer where=new StringBuffer();
        	where.append( "name IN ('allusers', '" );
        	where.append( zone.getPrefix() );
        	where.append( ".users'" );
        	where.append( ", 'all" );
        	where.append( type_name );
        	where.append( "', '" );
        	where.append( zone.getPrefix() );
        	where.append( "." );
        	where.append( type_name );
       		where.append( "' ) " );
		        
        	Enumeration enumeration = Group.findGroups( where.toString() );
        	for ( int i=0; enumeration.hasMoreElements(); i++ )
        		{
        		group = (Group)enumeration.nextElement();
        		group_list.addElement( group );
        		if ( i>0 )
        			groups.append( ", " );
        		groups.append( group.getName() );
        		}
		    

			alias = getAlias();
			if ( alias==null )
				throw new BuildingServerException( 
					"Alias not found.",
					"The specified unique identifier doesn't exist within the adminsitrative zone." );
					
			if ( !alias.getZoneId().equals( zone.getZoneId() ) )
				throw new BuildingServerException( 
					"Alias not found.",
					"The specified unique identifier doesn't exist within the adminsitrative zone." );

			String line, header, value, user_name, fieldname, extra_header;
			AliasEntry entry, other_alias;
			StringTokenizer tok;
			int id, i, j, k, n, line_no;
	    	char c;
	    	String[] values;
	    	
	    	int	alias_column=-1, 
	    			name_column=-1, 
	    			initials_column=-1, 
	    			surname_column=-1, 
	    			username_column=-1, 
	    			password_column=-1;
	    	int column_count;

	    	
	    	Hashtable alias_table = new Hashtable();
	    	Hashtable username_table = new Hashtable();
	    	
	    	
			try
				{
				header = reader.readLine();
				tok=new StringTokenizer( header, "\t" );
				if ( tok.countTokens()<4 )
					throw new BuildingServerException( "Invalid input at line 1.  At least four columns delimited by TABS expected in the header line." );

				extra_header="";
				for ( column_count=0; tok.hasMoreTokens(); column_count++ )
					{
					fieldname=tok.nextToken();
	    			for ( i=0; i<fieldname.length(); i++ )
						{
						c=fieldname.charAt(i);
						if ( !Character.isLetterOrDigit( c ) &&  c!= '_' )
							throw new BuildingServerException( "Column headings can only contain letters, digits or '_'.  Error at line 1" );
						}
					if ( fieldname.equalsIgnoreCase( "alias" ) )
						{
						if ( alias_column >= 0 )
							throw new BuildingServerException( "There are two 'alias' column headings.  Error at line 1" );
						alias_column=column_count;
						}
					else if ( fieldname.equalsIgnoreCase( "name" ) )
						{
						if ( name_column >= 0 )
							throw new BuildingServerException( "There are two 'name' column headings.  Error at line 1" );
						name_column=column_count;
						}
					else if ( fieldname.equalsIgnoreCase( "initials" ) )
						{
						if ( initials_column >= 0 )
							throw new BuildingServerException( "There are two 'initials' column headings.  Error at line 1" );
						initials_column=column_count;
						}
					else if ( fieldname.equalsIgnoreCase( "surname" ) )
						{
						if ( surname_column >= 0 )
							throw new BuildingServerException( "There are two 'surname' column headings.  Error at line 1" );
						surname_column=column_count;
						}
					else if ( fieldname.equalsIgnoreCase( "username" ) )
						{
						if ( username_column >= 0 )
							throw new BuildingServerException( "There are two 'username' column headings.  Error at line 1" );
						username_column=column_count;
						}
					else if ( fieldname.equalsIgnoreCase( "password" ) )
						{
						if ( password_column >= 0 )
							throw new BuildingServerException( "There are two 'password' column headings.  Error at line 1" );
						password_column=column_count;
						}
					else
						{
						extra_header += "\t" + fieldname;
						}
					}
					
				if ( alias_column<0 || name_column<0 || initials_column<0 || surname_column<0 )
					throw new BuildingServerException( "One or more of the four compulsory column headings are missing.  Error at line 1" );


				values = new String[column_count];

				line = reader.readLine();
				for ( line_no=2; line!=null; line_no++ )
					{
					log.debug( line );
					if ( line.trim().length()==0 )
						continue;
						
					tok=new StringTokenizer( line, "\t" );
					if ( tok.countTokens()==0 )
						continue;
					if ( tok.countTokens()!=column_count )
						throw new BuildingServerException( "Invalid input at line " + line_no + ". Exactly " + column_count + " columns delimited by TABS expected." );

					extra_field = "";
					for ( n=0; n<column_count; n++ )
						{
						values[n]=tok.nextToken();
						
						if (  n==alias_column || n==name_column || n==initials_column || n==surname_column )
							{
	    					for ( i=0; i<values[n].length(); i++ )
								{
								c=values[n].charAt(i);
								if ( !Character.isLetterOrDigit( c ) &&   
						    		c != '_'  &&  
						    		c != '-'  &&  
						    		c != ' '  && 
						    		c != '\'' && 
						    		c != '.'       )
									throw new BuildingServerException( "User data fields can only contain letters digits, '-', '_' or a space.  Error at line " + line_no );
								}
							}
						else if ( n==password_column )
							{
							if ( values[n].length()<6 )
								throw new BuildingServerException( "Passwords must contain at least six characters.  Error at line " + line_no );
	    					for ( i=0; i<values[n].length(); i++ )
								{
								c=values[n].charAt(i);
								if ( c<'!' || c>'z' )
									throw new BuildingServerException( "Passwords can only contain ASCII characters from '!' to 'Z'.  Error at line " + line_no );
								}
							}
						else if ( n==username_column )
							{

                                               /*
                                                * WebLearn commented out code; A Corfield 02/12/2003.
                                                */
/*
							if ( !values[n].startsWith( zone.getPrefix() ) )
								throw new BuildingServerException( "User names must start with '" + zone.getPrefix() + "'.  Error at line " + line_no );

*/
	    					for ( i=0; i<values[n].length(); i++ )
								{
								c=values[n].charAt(i);
								if ( !( (c>='a' && c<='z') || ( c>='0' && c<='9') ) )
									throw new BuildingServerException( "User names can only contain lower case letters or digits.  Error at line " + line_no );
								}
							}
						else
							{
							extra_field += "\t" + values[n];
							}
						}
						
					initials = values[initials_column];
					values[initials_column]="";
    				for ( i=0; i<initials.length(); i++ )
						{
						c=initials.charAt(i);
						if ( Character.isLetter( c ) )
							values[initials_column] += Character.toUpperCase( c );
						}

					if ( values[initials_column].length()<2 )
						throw new BuildingServerException( "There must be at least two initials, (most people have three or more). Error at line " + line_no   );
						
					extras.addElement( extra_field );
						
					user = new User();
					user.setName( values[name_column] );
					user.setSurname( values[surname_column] );
					user.setInitials( values[initials_column] );
					user.setZone( zone );
					user_list.addElement( user );

					if ( username_column>=0 )
						{
						otherpass = PassPhrase.findPassPhraseByUserName( values[username_column] );
						if ( otherpass != null )
							throw new BuildingServerException( "The specified user name " + values[username_column] + " is already in use.  Line " + line_no );
						user_name = values[username_column];

						if ( username_table.containsKey( user_name ) )
							throw new BuildingServerException( "The specified user name " + values[username_column] + " appears on a previous line.  Line " + line_no );
						}
					else
						{
						user_name=chooseUserName( zone, user, username_list );
						if ( user_name == null )
							throw new BuildingServerException( "Unable to create a unique user name for " + values[name_column] + " at line " + line_no );
						}
					
                    username_list.addElement( user_name );
					username_table.put( user_name, user_name );
					
					if ( password_column>=0 )
						{
						passphrase = values[password_column];
						}
					else
						{
						Properties pgen_props = new Properties();
						// may have to examine password generator and pass in other data.
						// random password generator doesn't need user properties
						passphrase = password_generator.generate( pgen_props );
						}
					
					passphrase_list.addElement( passphrase );

					
					
					if ( alias_table.containsKey( values[alias_column] ) )
						throw new BuildingServerException( "The specified alias " + values[alias_column] + " appears on a previous line.  Line " + line_no );
					alias_table.put( values[alias_column], values[alias_column] );
					
					entry = new AliasEntry();
					entry.setAlias( alias );
					entry.setUserAlias( values[alias_column] );
					//don't save entries until they are all parsed in without error
					entry_list.addElement( entry );
					
					other_alias = AliasEntry.findAliasEntry( 
									"alias_id = " + alias.getAliasId() +
									" AND user_alias = '" + values[alias_column]  +"'"     );
					
					if ( other_alias!=null )
						throw new BuildingServerException( 
							"Attempt to create duplicate user.",
							"User " + values[name_column] + " already exists.  Error at line " + line_no );
					
					line = reader.readLine();
					}
				}
			catch ( IOException ioex )
				{
				throw new BuildingServerException( "IO Error reading input.\n" + ioex );
				}
			catch ( PasswordGeneratorException pgex )
				{
				throw new BuildingServerException( "Error trying to generate a password:\n" + pgex );
				}
			
			writer.print( "uid\tusername\tpassword\tname\tsurname\tinitials\talias" );
			writer.println( extra_header );
			for ( j=0; j< user_list.size(); j++ )
				{
				user = (User)user_list.elementAt( j );
				user.save();
				passphrase = (String)passphrase_list.elementAt( j );
				PassPhrase pass =new PassPhrase(user);
                pass.setUserName( (String)username_list.elementAt( j ) );
                pass.changePassPhrase(passphrase);
				pass.save();
				entry = (AliasEntry)entry_list.elementAt( j );
				entry.setUser( user );
				entry.save();
		
				for ( k=0; k<group_list.size(); k++ )
					{
					group = (Group)group_list.elementAt( k );
					log.debug( "Adding " + user.getName() + " to " + group.getName() );
					group.addMember( user );
					}
				
				writer.print( user.getUserId().toString() );
				writer.print( "\t" );
				writer.print( pass.getUserName() );
				writer.print( "\t" );
				if ( !password_generator.isOffLine() )
					{
					writer.print( passphrase );
					writer.print( "\t" );
					}
				writer.print( user.getName() );
				writer.print( "\t" );
				writer.print( user.getSurname() );
				writer.print( "\t" );
				writer.print( user.getInitials() );
				writer.print( "\t" );
				writer.print( entry.getUserAlias() );
				
				String ext = (String)extras.elementAt( j );
				if ( ext.length()>0 )
					{
					writer.print( "\t" );
					writer.print( ext );
					}
					
				writer.println( "" );
				writer.flush();
				}

			for ( k=0; k<group_list.size(); k++ )
				{
				group = (Group)group_list.elementAt( k );
				group.save();
				}
			
			return groups.toString();
			}
		finally
			{
			disposeMethodCall();
			}
		}

public synchronized String removeAllStudentPasswords()
		throws RemoteException, BuildingServerException
		{
		PassPhrase p;
		User user;
		AliasEntry alias_entry;
		Alias alias;
		StringBuffer message = new StringBuffer();
		int last_id=0;

		try
			{
			Vector list=new Vector();
			Enumeration enumeration = PassPhrase.findPassPhrases( "pass_phrase IS NOT NULL" );
                        //Enumeration enumeration = User.findUsers( "user_id IS NOT NULL" );
			while ( enumeration.hasMoreElements() )
				list.addElement( enumeration.nextElement() );
			

			for ( int i=0; i<list.size(); i++ )
				{
				p = (PassPhrase)list.elementAt( i );

                                user = User.findUser( "user_id = " + p.getUserId());

                                alias = Alias.findAlias( "zone_id = " + user.getZoneId() + " AND user_category = 'student'" );
				if ( alias == null )
					continue;
					
				alias_entry = AliasEntry.findAliasEntry( 
					"user_id = " + user.getUserId().toString() + 
					" AND alias_id = " + alias.getAliasId().toString() );
				if ( alias_entry == null )
					continue;
				p.setPassPhrase(null);
                                p.save();
				}
					
			}
		catch ( Exception ex )
			{
			log.error( ex.getMessage(), ex );	       
			throw new BuildingServerException( "Problem removing password: " + ex );
			}
			
		return message.toString();
		}
        
	public synchronized String resetNullStudentPasswords()
		throws RemoteException, BuildingServerException
		{
		PassPhrase p;
		User user;
		UserDetail detail;
		AliasEntry alias_entry;
		Alias alias;
		StringBuffer message = new StringBuffer();
		int last_id=0;
		
		try
			{
			Vector list=new Vector();
			Enumeration enumeration = PassPhrase.findPassPhrases( "pass_phrase IS NULL" );
			while ( enumeration.hasMoreElements() )
				list.addElement( enumeration.nextElement() );
			

			for ( int i=0; i<list.size(); i++ )
				{
				p = (PassPhrase)list.elementAt( i );
				user = User.findUser( p.getUserId() );
				if ( user == null )
					continue;

				alias = Alias.findAlias( "zone_id = " + user.getZoneId() + " AND user_category = 'student'" );
				if ( alias == null )
					continue;
					
				alias_entry = AliasEntry.findAliasEntry( 
					"user_id = " + user.getUserId().toString() + 
					" AND alias_id = " + alias.getAliasId().toString() );
				if ( alias_entry == null )
					continue;
					
				detail = UserDetail.findUserDetail( "user_id = " + user.getUserId().toString() );
				if ( detail == null )
				    continue;
				    
				if ( detail.getDob() == null )
				    continue;
					
				resetUserPassword( user.getUserId() );
				}
					
			}
		catch ( Exception ex )
			{
			log.error( ex.getMessage(), ex );	       
			throw new BuildingServerException( "Problem setting password: " + ex );
			}
			
		return message.toString();
		}
	
	public synchronized String resetAllStudentPasswords()
		throws RemoteException, BuildingServerException
		{
		UserDetail ud;
		PassPhrase p;
		User user;
		AliasEntry alias_entry;
		Alias alias;
		StringBuffer message = new StringBuffer();
		int last_id=0;
		
		try
			{
			Vector list=new Vector();
			Enumeration enumeration = UserDetail.findUserDetails( "dob IS NOT NULL" );
			while ( enumeration.hasMoreElements() )
				list.addElement( enumeration.nextElement() );
			

			for ( int i=0; i<list.size(); i++ )
				{
				ud = (UserDetail)list.elementAt( i );
				user = User.findUser( ud.getUserId() );
				if ( user == null )
					continue;

				p = PassPhrase.findPassPhrase( "user_id = " + user.getUserId().toString() );
				if ( p == null )
				    continue;
				
				alias = Alias.findAlias( "zone_id = " + user.getZoneId() + " AND user_category = 'student'" );
				if ( alias == null )
					continue;
					
				alias_entry = AliasEntry.findAliasEntry( 
					"user_id = " + user.getUserId().toString() + 
					" AND alias_id = " + alias.getAliasId().toString() );
				if ( alias_entry == null )
					continue;
					
				resetUserPassword( user.getUserId() );
				}
					
			}
		catch ( Exception ex )
			{
			log.error( ex.getMessage(), ex );	       
			throw new BuildingServerException( "Problem setting password: " + ex );
			}
			
		return message.toString();
		}
	
		
        
	public String addUserCertificate( PrimaryKey uid, File cert_file )
		throws RemoteException, BuildingServerException
        {
            try
            {
                InputStream in = new FileInputStream( cert_file );
                CertificateFactory cf = CertificateFactory.getInstance( "X.509" );
                X509Certificate cert = (X509Certificate)cf.generateCertificate( in );
                in.close();

                UserX509 user_x509 = new UserX509();
                user_x509.setUserId( uid );
                user_x509.setX509Certificate( cert );
                BigInteger big = cert.getSerialNumber();
                // only bottom 32 bits are saved - this is used to 
                // help narrow searches in the database when the
                // cert is presented during authentication later
                user_x509.setSerialNumber( big.intValue() );
                user_x509.setDistinguishedName( cert.getSubjectDN().getName() );
                user_x509.save();
                
                return "Certificate saved against selected user.";
            }
            catch ( Exception ex )
            {
                log.error( ex.getMessage(),  ex );	       
                throw new BuildingServerException( "Problem setting password: " + ex );
            }
        }
        
        
		
	public synchronized String resetUserName( PrimaryKey uid )
		throws RemoteException, BuildingServerException
		{
		try
			{
			PasswordGenerator pg;
			User user = User.findUser( uid );
			if ( user == null )
				throw new BuildingServerException( "Can't find user in database." );
			
			Zone zone = user.getZone();
			
			PassPhrase pphrase = PassPhrase.findPassPhrase( "user_id = " + uid.toString() );
			if ( pphrase == null )
			    {
			    pphrase = new PassPhrase(user);
			    }
			
			Vector pass_list = new Vector();
			String user_name=chooseUserName( zone, user, pass_list );
			
			if ( user_name == null )
				throw new BuildingServerException( "Unable to create a user name for this user." );
			
            pphrase.setUserName( user_name )			;
            pphrase.save();
            
			return "User name reset to \"" + pphrase.getUserName() + "\". ";
			}
		catch ( Exception ex )
			{
			 log.error( ex.getMessage(), ex );	       
			throw new BuildingServerException( "Problem setting user name: " + ex );
			}
		}

	public synchronized String resetUserPassword( PrimaryKey uid )
		throws RemoteException, BuildingServerException
		{
		try
			{
			PasswordGenerator pg;
			User user = User.findUser( uid );
			if ( user == null )
				throw new BuildingServerException( "Can't find user in database." );
			
			Properties props = new Properties();
			PassPhrase pphrase = PassPhrase.findPassPhrase( "user_id = " + uid.toString() );
			if ( pphrase == null )
				throw new BuildingServerException( "User not found." );
			
			UserDetail detail = UserDetail.findUserDetail( "user_id = " + uid.toString() );
			AliasEntry alias_entry=null;
			Alias alias = Alias.findAlias( "zone_id = " + user.getZoneId() + " AND user_category = 'student'" );
			if ( alias!=null )	
				alias_entry = AliasEntry.findAliasEntry( 
					"user_id = " + uid.toString() + " AND alias_id = " + alias.getAliasId().toString() );
				
			if ( detail != null && alias_entry !=null && detail.getDob() != null )
				{
				if ( user.getZoneId().intValue() == 1 )
					{
					pg = iss_generator;
    				props.put( "student_id", alias_entry.getUserAlias() );
	    			props.put( "academic_year", "1996" );
					}
				else
					pg = dob_generator;

				StringBuffer s_dob = new StringBuffer();
				s_dob.append( detail.getDobDay() );
				s_dob.append( "/" );
				s_dob.append( detail.getDobMonth() );
				s_dob.append( "/" );
				s_dob.append( detail.getDobYear() );
				
    			props.put( "date_of_birth", s_dob.toString() );

				}
			else
				{
				pg = password_generator;
				}

			String newPassword = pg.generate( props );
			pphrase.changePassPhrase( newPassword );
			pphrase.save();
			
			if ( pg.isOffLine() )
				return "Password reset. " + pg.offLineAdvice();

			return "Password reset to \"" + newPassword + "\". " + pg.offLineAdvice();
			}
		catch ( Exception ex )
			{
			log.error( ex.getMessage(), ex );	       
			throw new BuildingServerException( "Problem setting password: " + ex );
			}
		}

	public synchronized void setUserPassword( PrimaryKey uid, String p )
		throws RemoteException, BuildingServerException
		{
		try
			{
			PassPhrase pphrase = PassPhrase.findPassPhrase( uid );
			if ( pphrase == null )
				throw new BuildingServerException( "User not found." );
			
			pphrase.changePassPhrase( p );
			pphrase.save();
			}
		catch ( Exception ex )
			{
			throw new BuildingServerException( "Problem setting password: " + ex );
			}
		}

	public synchronized Vector findPermittedAliasIds()
		throws RemoteException, BuildingServerException
		{
		prepareMethodCall();
		try
			{
	    	AliasEditor editor = getAliasEditor();
	    	if ( editor == null )
	    		throw new BuildingServerException( "Can't find correct resource." );
	    	if ( !editor.checkPermission( Permission.EDIT ) )
				throw new BuildingServerException( "Edit access denied",
						"You don't have 'edit' level access to this resource." );


			Vector list = new Vector();
			Enumeration enumeration = Alias.findAliases( "zone_id = " + editor.getEffectiveZoneId() + " AND alias_type = " + Alias.TYPE_PRIMARY );
			Alias alias;

			while ( enumeration.hasMoreElements() )
				{
				alias = (Alias)enumeration.nextElement();
				list.addElement( alias.getAliasId() );
				}
				
			return list;
			}
		finally
			{
			disposeMethodCall();
			}
		}
	
	public synchronized String nameOfPermittedAlias( PrimaryKey alias_id )
		throws RemoteException, BuildingServerException
		{
		prepareMethodCall();
		try
			{
	    	AliasEditor editor = getAliasEditor();
	    	if ( editor == null )
	    		throw new BuildingServerException( "Can't find correct resource." );
	    	if ( !editor.checkPermission( Permission.EDIT ) )
				throw new BuildingServerException( "Edit access denied",
						"You don't have 'edit' level access to this resource." );

			Alias alias = Alias.findAlias( alias_id );

			if ( ! alias.getZoneId().equals( editor.getEffectiveZoneId() ) )
				throw new BuildingServerException( "Alias access denied",
						"Attempt to access unique identifier type from outside administrative zone." );
			
			if ( alias.getAliasType().intValue() != Alias.TYPE_PRIMARY )
				throw new BuildingServerException( "Alias access denied",
						"Attempt to access non-primary unique identifier." );

			return alias.getAliasName();
			}
		finally
			{
			disposeMethodCall();
			}
		}
	
	public synchronized Vector categoriesOfPermittedAlias( PrimaryKey alias_id )
		throws RemoteException, BuildingServerException
		{
		prepareMethodCall();
		try
			{
	    	AliasEditor editor = getAliasEditor();
	    	if ( editor == null )
	    		throw new BuildingServerException( "Can't find correct resource." );
	    	if ( !editor.checkPermission( Permission.EDIT ) )
				throw new BuildingServerException( "Edit access denied",
						"You don't have 'edit' level access to this resource." );

			Alias alias = Alias.findAlias( alias_id );

			if ( ! alias.getZoneId().equals( editor.getEffectiveZoneId() ) )
				throw new BuildingServerException( "Alias access denied",
						"Attempt to access unique identifier type from outside administrative zone." );
			
			if ( alias.getAliasType().intValue() != Alias.TYPE_PRIMARY )
				throw new BuildingServerException( "Alias access denied",
						"Attempt to access non-primary unique identifier." );
			
			Vector list = new Vector();
			Enumeration enumeration = alias.getUserCategories();
			while ( enumeration.hasMoreElements() )
				list.addElement( enumeration.nextElement() );
				
			return list;
			}
		finally
			{
			disposeMethodCall();
			}
		}

	
	
	public synchronized void addUsersToGroups( BufferedReader reader, PrintWriter writer )
		throws RemoteException, BuildingServerException
	    {
		prepareMethodCall();
		
		try
			{
	    	AliasEditor editor = getAliasEditor();
	    	if ( editor == null )
	    		throw new BuildingServerException( "Can't find correct resource." );

	    	if ( !editor.checkPermission( Permission.EDIT ) || !editor.checkPermission( Permission.CREATE ) )
				throw new BuildingServerException( "Edit access denied",
						"You don't have both 'edit' and 'create' levels of access to this resource." );

			String prefix = editor.getPrefix();
				
			if ( prefix == null )
				throw new BuildingServerException( "No prefix",
						"It isn't possible to determine group names at this location." );

		    
			if ( alias_id == null )
				throw new BuildingServerException( "No alias.",
						"You have to select a unique identifier before you can create users." );

	    	Vector user_list=new Vector();
	    	Vector member_list=new Vector();
	    	Hashtable group_list=new Hashtable();
		    
	    
			Alias alias;
			User user;
	    	Zone zone = editor.getEffectiveZone();
	    	Group group;
		    
			alias = getAlias();
			if ( alias==null )
				throw new BuildingServerException( 
					"Alias not found.",
					"The specified unique identifier doesn't exist within the adminsitrative zone." );
					
			if ( !alias.getZoneId().equals( zone.getZoneId() ) )
				throw new BuildingServerException( 
					"Alias not found.",
					"The specified unique identifier doesn't exist within the adminsitrative zone." );

			String line, value, user_name;
			AliasEntry entry, other_alias;
			StringTokenizer tok;
			int id, i, j, k, n, line_no;
	    	char c;
	    	String[] values = new String[2];
			try
				{
				line = reader.readLine();
				for ( line_no=1; line!=null; line_no++ )
					{
					log.debug( line );
					if ( line.trim().length()==0 )
						continue;
						
					tok=new StringTokenizer( line, "\t" );
					if ( tok.countTokens()==0 )
						continue;
					if ( tok.countTokens()!=2 )
						throw new BuildingServerException( "Invalid input at line " + line_no + ": exactly two columns delimited by TABS expected.",
																		"Invalid input at line " + line_no + ": exactly two columns delimited by TABS expected." );

					for ( n=0; n<2; n++ )
						{
						values[n]=tok.nextToken();
	    				for ( i=0; i<values[n].length(); i++ )
							{
							c=values[n].charAt(i);
							if ( !Character.isLetterOrDigit( c ) &&   
						    	c != '_'  &&  
						    	c != '-' )
								throw new BuildingServerException( "Values can only contain letters, digits, '-' or '_'.  Error at line " + line_no,
																				"Values can only contain letters, digits, '-' or '_'.  Error at line " + line_no );
							}
						}
					
					log.debug( "alias_id = " + alias.getAliasId().toString() + " AND user_alias = '" + values[0] + "'" );
					entry = AliasEntry.findAliasEntry( "alias_id = " + alias.getAliasId().toString() + " AND user_alias = '" + values[0] + "'" );
					if ( entry == null )
						throw new BuildingServerException( "Unidentified user at line " + line_no + "." ,
																		"Unidentified user at line " + line_no + "." );
					
					user_list.addElement( entry.getUserId() );
					member_list.addElement( values[1] );
					group_list.put( values[1], values[1] );
					
					line = reader.readLine();
					}
				}
			catch ( IOException ioex )
				{
				throw new BuildingServerException( "IO Error reading input.\n" + ioex );
				}

			
			writer.println( "Input parsed but not acted on." );
			
			Enumeration enumeration = group_list.keys();
			String g_name, res_name, g_full_name;
			ResourceTree tree = ResourceTreeManager.getInstance();
			Resource g_res;
			Group g;
			boolean completed = false;
			Connection con=null;
			StringBuffer message = new StringBuffer();;
			
			while ( enumeration.hasMoreElements() )
				{
				g_name = (String)enumeration.nextElement();
				g_full_name = prefix + g_name;
				
				g = Group.findGroupByName(g_full_name);
				
				res_name = editor.getFullName() + g_name + "/";
				g_res = tree.findResource( res_name );
				
				writer.print( "Group: " + g_full_name );
				writer.print( g==null?" doesn't exist ":" exists already " );
				
				writer.print( " Editor: " + res_name );
				writer.print( g_res==null?" doesn't exist<BR>":" exists already<BR>" );
				
				if ( g==null && g_res==null )
					{
					// create new group in a new group editor tool
					try
						{
						synchronized ( tree )
							{
							g_res=new Resource();
							g_res.setName( g_name );
							g_res.setTitle( EscapedHtmlWriter.filter(g_full_name) );
							g_res.setDescription( editor.getDescription() );
							g_res.setIntroduction( "This is an automatically generated group editing tool." );
							g_res.setHttpFacilityNo( 6 );
							g_res.setUseParentAcl( true );

											
							con=BuildingContext.getContext().getConnection();
							//may need to rollback after several
							//operations
							con.setAutoCommit( false );
							tree.addResource( editor, g_res );

							g = new Group();
							g.setResourceId( g_res.getResourceId() );
							g.setName( g_full_name );
							g.setDescription( editor.getDescription() );
							g.save();

							con.commit();
							con.setAutoCommit( true );
							completed=true;
							
							}
						}
					catch ( Exception ex )
						{
					    log.error( ex.getMessage(), ex );	       
						message.append( "Problem storing resource." );
						message.append( ex.getMessage() );
						}
					finally
						{
						if ( !completed )
							{
							try
								{
								if ( tree!=null && g_res!=null )
									tree.removeResource( g_res );
								}
							catch ( Exception ex )
								{
							    log.error( ex.getMessage(), ex );	       
								message.append( " A problem occured cleaning up after the failure to create " +
													"the resource.  A 'phantom' resource may appear in the list "  +
													"of resources: " + ex.getMessage() );
								}

							try
								{
								if ( con!=null )
									con.rollback();
								}
							catch ( SQLException sqlex )
								{
								log.error( sqlex.getMessage(), sqlex );	       
								message.append( " Unable to roll back database operations." + sqlex.getMessage() );
								}
							
							}

						if ( message.length()>0 )
							throw new BuildingServerException( message.toString() );
						}

					}
				else
					{
					// check validity of existing group
					if ( !(g!=null && g_res!=null && g_res.getResourceId().equals( g.getResourceId() ) ) )
						{
						if ( g!=null )
							throw new BuildingServerException( 
								"Group " + g_full_name + " exists but in wrong location. ",
								"Group " + g_full_name + " exists but in wrong location. " );

						throw new BuildingServerException( 
							"Resource " + res_name + " exists but doesn't contain the group. "+ g_full_name + ".",
							"Resource " + res_name + " exists but doesn't contain the group. "+ g_full_name + "." );
						}
					writer.println( "Using group: " + g_full_name );
					}
				}

			PrimaryKey uid;
			for ( i=0; i< user_list.size(); i++ )
				{
				uid = (PrimaryKey)user_list.elementAt( i );
				user = User.findUser( uid );
				
				g_name = member_list.elementAt( i ).toString();
				g_full_name = prefix + g_name;
				g = Group.findGroupByName(g_full_name);

				writer.print( "User: " + user.getName() );
				writer.print( " Group Id: " + (g==null?"none":g.getGroupId().toString()) );
				writer.print( " Group Name: " + g_full_name + "<BR>" );
				
				if ( g==null )
					{
					writer.println( " Skipped " );
					continue;
					}
					
				g.addMember( user );
				g.save();
				writer.println( " Added " );
				}

			writer.flush();
			}
		finally
			{
			disposeMethodCall();
			}
		}



	}
	
