Browse Source

added source of the jlan project, still work to do(dependencies)

Daniel Lazar 9 years ago
parent
commit
d4a371c9b4
100 changed files with 16508 additions and 1 deletions
  1. 3 1
      build.gradle
  2. BIN
      libs/hazelcast/hazelcast-2.4.jar
  3. 192 0
      libs/hazelcast/hazelcastConfig.xml
  4. 97 0
      libs/service/jlansrv.conf
  5. 54 0
      libs/service/license.txt
  6. BIN
      libs/service/linux/jlanserver
  7. 212 0
      libs/service/linux/jlanserver.sh
  8. BIN
      libs/service/linux/libwrapper.so
  9. BIN
      libs/service/macosx/jlanserver
  10. BIN
      libs/service/macosx/libwrapper.jnilib
  11. 5 0
      libs/service/readme.txt
  12. BIN
      libs/service/solaris/jlanserver
  13. BIN
      libs/service/solaris/libwrapper.so
  14. BIN
      libs/service/windows/jlanserver.exe
  15. BIN
      libs/service/windows/wrapper.dll
  16. BIN
      libs/service/wrapper.jar
  17. 97 0
      src/javax/security/auth/AuthPermission.java
  18. 44 0
      src/javax/security/auth/DestroyFailedException.java
  19. 43 0
      src/javax/security/auth/Destroyable.java
  20. 130 0
      src/javax/security/auth/Policy.java
  21. 395 0
      src/javax/security/auth/PrivateCredentialPermission.java
  22. 31 0
      src/javax/security/auth/RefreshFailedException.java
  23. 26 0
      src/javax/security/auth/Refreshable.java
  24. 782 0
      src/javax/security/auth/Subject.java
  25. 121 0
      src/javax/security/auth/SubjectDomainCombiner.java
  26. 65 0
      src/javax/security/auth/callback/.svn/all-wcprops
  27. 368 0
      src/javax/security/auth/callback/.svn/entries
  28. 5 0
      src/javax/security/auth/callback/.svn/prop-base/Callback.java.svn-base
  29. 5 0
      src/javax/security/auth/callback/.svn/prop-base/CallbackHandler.java.svn-base
  30. 5 0
      src/javax/security/auth/callback/.svn/prop-base/ChoiceCallback.java.svn-base
  31. 5 0
      src/javax/security/auth/callback/.svn/prop-base/ConfirmationCallback.java.svn-base
  32. 5 0
      src/javax/security/auth/callback/.svn/prop-base/LanguageCallback.java.svn-base
  33. 5 0
      src/javax/security/auth/callback/.svn/prop-base/NameCallback.java.svn-base
  34. 5 0
      src/javax/security/auth/callback/.svn/prop-base/PasswordCallback.java.svn-base
  35. 5 0
      src/javax/security/auth/callback/.svn/prop-base/TextInputCallback.java.svn-base
  36. 5 0
      src/javax/security/auth/callback/.svn/prop-base/TextOutputCallback.java.svn-base
  37. 5 0
      src/javax/security/auth/callback/.svn/prop-base/UnsupportedCallbackException.java.svn-base
  38. 25 0
      src/javax/security/auth/callback/.svn/text-base/Callback.java.svn-base
  39. 54 0
      src/javax/security/auth/callback/.svn/text-base/CallbackHandler.java.svn-base
  40. 109 0
      src/javax/security/auth/callback/.svn/text-base/ChoiceCallback.java.svn-base
  41. 234 0
      src/javax/security/auth/callback/.svn/text-base/ConfirmationCallback.java.svn-base
  42. 41 0
      src/javax/security/auth/callback/.svn/text-base/LanguageCallback.java.svn-base
  43. 74 0
      src/javax/security/auth/callback/.svn/text-base/NameCallback.java.svn-base
  44. 124 0
      src/javax/security/auth/callback/.svn/text-base/PasswordCallback.java.svn-base
  45. 74 0
      src/javax/security/auth/callback/.svn/text-base/TextInputCallback.java.svn-base
  46. 56 0
      src/javax/security/auth/callback/.svn/text-base/TextOutputCallback.java.svn-base
  47. 64 0
      src/javax/security/auth/callback/.svn/text-base/UnsupportedCallbackException.java.svn-base
  48. 25 0
      src/javax/security/auth/callback/Callback.java
  49. 54 0
      src/javax/security/auth/callback/CallbackHandler.java
  50. 109 0
      src/javax/security/auth/callback/ChoiceCallback.java
  51. 234 0
      src/javax/security/auth/callback/ConfirmationCallback.java
  52. 41 0
      src/javax/security/auth/callback/LanguageCallback.java
  53. 74 0
      src/javax/security/auth/callback/NameCallback.java
  54. 124 0
      src/javax/security/auth/callback/PasswordCallback.java
  55. 74 0
      src/javax/security/auth/callback/TextInputCallback.java
  56. 56 0
      src/javax/security/auth/callback/TextOutputCallback.java
  57. 64 0
      src/javax/security/auth/callback/UnsupportedCallbackException.java
  58. 100 0
      src/javax/security/auth/kerberos/DelegationPermission.java
  59. 120 0
      src/javax/security/auth/kerberos/KerberosKey.java
  60. 167 0
      src/javax/security/auth/kerberos/KerberosPrincipal.java
  61. 374 0
      src/javax/security/auth/kerberos/KerberosTicket.java
  62. 320 0
      src/javax/security/auth/kerberos/KeyImpl.java
  63. 140 0
      src/javax/security/auth/kerberos/KrbDelegationPermissionCollection.java
  64. 144 0
      src/javax/security/auth/kerberos/KrbServicePermissionCollection.java
  65. 188 0
      src/javax/security/auth/kerberos/ServicePermission.java
  66. 31 0
      src/javax/security/auth/login/AccountException.java
  67. 31 0
      src/javax/security/auth/login/AccountExpiredException.java
  68. 32 0
      src/javax/security/auth/login/AccountLockedException.java
  69. 32 0
      src/javax/security/auth/login/AccountNotFoundException.java
  70. 95 0
      src/javax/security/auth/login/AppConfigurationEntry.java
  71. 94 0
      src/javax/security/auth/login/Configuration.java
  72. 32 0
      src/javax/security/auth/login/CredentialException.java
  73. 32 0
      src/javax/security/auth/login/CredentialExpiredException.java
  74. 32 0
      src/javax/security/auth/login/CredentialNotFoundException.java
  75. 32 0
      src/javax/security/auth/login/FailedLoginException.java
  76. 548 0
      src/javax/security/auth/login/LoginContext.java
  77. 45 0
      src/javax/security/auth/login/LoginException.java
  78. 38 0
      src/javax/security/auth/spi/LoginModule.java
  79. 218 0
      src/javax/security/auth/x500/X500Principal.java
  80. 78 0
      src/javax/security/auth/x500/X500PrivateCredential.java
  81. 35 0
      src/javax/security/sasl/AuthenticationException.java
  82. 79 0
      src/javax/security/sasl/AuthorizeCallback.java
  83. 33 0
      src/javax/security/sasl/RealmCallback.java
  84. 30 0
      src/javax/security/sasl/RealmChoiceCallback.java
  85. 204 0
      src/javax/security/sasl/Sasl.java
  86. 37 0
      src/javax/security/sasl/SaslClient.java
  87. 30 0
      src/javax/security/sasl/SaslClientFactory.java
  88. 69 0
      src/javax/security/sasl/SaslException.java
  89. 37 0
      src/javax/security/sasl/SaslServer.java
  90. 30 0
      src/javax/security/sasl/SaslServerFactory.java
  91. 2617 0
      src/org/alfresco/jlan/app/CifsOnlyXMLServerConfiguration.java
  92. 111 0
      src/org/alfresco/jlan/app/DriveMappingsConfigSection.java
  93. 658 0
      src/org/alfresco/jlan/app/JLANCifsServer.java
  94. 761 0
      src/org/alfresco/jlan/app/JLANServer.java
  95. 464 0
      src/org/alfresco/jlan/app/JLANServerService.java
  96. 89 0
      src/org/alfresco/jlan/app/Portmap.java
  97. 1022 0
      src/org/alfresco/jlan/app/XMLServerConfiguration.java
  98. 208 0
      src/org/alfresco/jlan/client/AsynchRequest.java
  99. 409 0
      src/org/alfresco/jlan/client/AuthenticateSession.java
  100. 2237 0
      src/org/alfresco/jlan/client/CIFSDiskSession.java

+ 3 - 1
build.gradle

@@ -25,8 +25,10 @@ dependencies {
     compile files('libs/sshlib-v1.1.jar')
     compile files('libs/chart-library2.2.jar')
     //compile files('libs/jlan-5.jar')
-    compile files('libs/alfresco-jlan.jar')
+    //compile files('libs/alfresco-jlan.jar')
     compile files('libs/cryptix-jce-provider.jar')
+    compile files('libs/hazelcast/hazelcast-2.4.jar')
+    compile files('libs/service/wrapper.jar')
 }
 
 android {

BIN
libs/hazelcast/hazelcast-2.4.jar


+ 192 - 0
libs/hazelcast/hazelcastConfig.xml

@@ -0,0 +1,192 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<hazelcast xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-basic.xsd"
+           xmlns="http://www.hazelcast.com/schema/config"
+           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+    <group>
+        <name>dev</name>
+        <password>dev-pass</password>
+    </group>
+    <network>
+        <port auto-increment="true">5701</port>
+        <join>
+            <multicast enabled="true">
+                <multicast-group>224.2.2.3</multicast-group>
+                <multicast-port>54327</multicast-port>
+            </multicast>
+            <tcp-ip enabled="false">
+                <interface>127.0.0.1</interface>
+            </tcp-ip>
+        </join>
+        <interfaces enabled="true">
+            <interface>192.168.1.*</interface>
+        </interfaces>
+        <symmetric-encryption enabled="false">
+            <!--
+               encryption algorithm such as
+               DES/ECB/PKCS5Padding,
+               PBEWithMD5AndDES,
+               AES/CBC/PKCS5Padding,
+               Blowfish,
+               DESede
+            -->
+            <algorithm>PBEWithMD5AndDES</algorithm>
+            <!-- salt value to use when generating the secret key -->
+            <salt>thesalt</salt>
+            <!-- pass phrase to use when generating the secret key -->
+            <password>thepass</password>
+            <!-- iteration count to use when generating the secret key -->
+            <iteration-count>19</iteration-count>
+        </symmetric-encryption>
+        <asymmetric-encryption enabled="false">
+            <!-- encryption algorithm -->
+            <algorithm>RSA/NONE/PKCS1PADDING</algorithm>
+            <!-- private key password -->
+            <keyPassword>thekeypass</keyPassword>
+            <!-- private key alias -->
+            <keyAlias>local</keyAlias>
+            <!-- key store type -->
+            <storeType>JKS</storeType>
+            <!-- key store password -->
+            <storePassword>thestorepass</storePassword>
+            <!-- path to the key store -->
+            <storePath>keystore</storePath>
+        </asymmetric-encryption>
+    </network>
+    <executor-service>
+        <core-pool-size>16</core-pool-size>
+        <max-pool-size>64</max-pool-size>
+        <keep-alive-seconds>60</keep-alive-seconds>
+    </executor-service>
+    <queue name="default">
+        <!--
+            Maximum size of the queue. When a JVM's local queue size reaches the maximum,
+            all put/offer operations will get blocked until the queue size
+            of the JVM goes down below the maximum.
+            Any integer between 0 and Integer.MAX_VALUE. 0 means
+            Integer.MAX_VALUE. Default is 0.
+        -->
+        <max-size-per-jvm>0</max-size-per-jvm>
+        <!--
+            Maximum number of seconds for each item to stay in the queue. Items that are
+            not consumed in <time-to-live-seconds> will automatically
+            get evicted from the queue.
+            Any integer between 0 and Integer.MAX_VALUE. 0 means
+            infinite. Default is 0.
+        -->
+        <time-to-live-seconds>0</time-to-live-seconds>
+    </queue>
+    <map name="default">
+        <!--
+            Number of backups. If 1 is set as the backup-count for example,
+            then all entries of the map will be copied to another JVM for
+            fail-safety. Valid numbers are 0 (no backup), 1, 2, 3.
+        -->
+        <backup-count>1</backup-count>
+        <!--
+            Valid values are:
+            NONE (no eviction),
+            LRU (Least Recently Used),
+            LFU (Least Frequently Used).
+            NONE is the default.
+        -->
+        <eviction-policy>NONE</eviction-policy>
+        <!--
+            Maximum size of the map. When max size is reached,
+            map is evicted based on the policy defined.
+            Any integer between 0 and Integer.MAX_VALUE. 0 means
+            Integer.MAX_VALUE. Default is 0.
+        -->
+        <max-size>0</max-size>
+        <!--
+            When max. size is reached, specified percentage of
+            the map will be evicted. Any integer between 0 and 100.
+            If 25 is set for example, 25% of the entries will
+            get evicted.
+        -->
+        <eviction-percentage>25</eviction-percentage>
+
+        <!--
+            While recovering from split-brain (network partitioning),
+            map entries in the small cluster will merge into the bigger cluster
+            based on the policy set here. When an entry merge into the
+            cluster, there might an existing entry with the same key already.
+            Values of these entries might be different for that same key.
+            Which value should be set for the key? Conflict is resolved by
+            the policy set here. Default policy is hz.ADD_NEW_ENTRY
+
+            There are built-in merge policies such as
+            hz.NO_MERGE      ; no entry will merge.
+            hz.ADD_NEW_ENTRY ; entry will be added if the merging entry's key
+                               doesn't exist in the cluster.
+            hz.HIGHER_HITS   ; entry with the higher hits wins.
+            hz.LATEST_UPDATE ; entry with the latest update wins.
+        -->
+        <merge-policy>hz.ADD_NEW_ENTRY</merge-policy>
+    </map>
+    <!-- Add your own map merge policy implementations here:     
+    	<merge-policies>
+           	<map-merge-policy name="MY_MERGE_POLICY">
+            	<class-name>com.acme.MyOwnMergePolicy</class-name>
+        	</map-merge-policy>
+    	</merge-policies>
+    -->
+
+    <map name="AlfrescoFilesysCache">
+        <!--
+            Number of backups. If 1 is set as the backup-count for example,
+            then all entries of the map will be copied to another JVM for
+            fail-safety. Valid numbers are 0 (no backup), 1, 2, 3.
+        -->
+        <backup-count>1</backup-count>
+        <!--
+            Valid values are:
+            NONE (no eviction),
+            LRU (Least Recently Used),
+            LFU (Least Frequently Used).
+            NONE is the default.
+        -->
+        <eviction-policy>NONE</eviction-policy>
+        <!--
+            Maximum size of the map. When max size is reached,
+            map is evicted based on the policy defined.
+            Any integer between 0 and Integer.MAX_VALUE. 0 means
+            Integer.MAX_VALUE. Default is 0.
+        -->
+        <max-size>0</max-size>
+        <!--
+            When max. size is reached, specified percentage of
+            the map will be evicted. Any integer between 0 and 100.
+            If 25 is set for example, 25% of the entries will
+            get evicted.
+        -->
+        <eviction-percentage>25</eviction-percentage>
+
+        <!--
+            While recovering from split-brain (network partitioning),
+            map entries in the small cluster will merge into the bigger cluster
+            based on the policy set here. When an entry merge into the
+            cluster, there might an existing entry with the same key already.
+            Values of these entries might be different for that same key.
+            Which value should be set for the key? Conflict is resolved by
+            the policy set here. Default policy is hz.ADD_NEW_ENTRY
+
+            There are built-in merge policies such as
+            hz.NO_MERGE      ; no entry will merge.
+            hz.ADD_NEW_ENTRY ; entry will be added if the merging entry's key
+                               doesn't exist in the cluster.
+            hz.HIGHER_HITS   ; entry with the higher hits wins.
+            hz.LATEST_UPDATE ; entry with the latest update wins.
+        -->
+        <merge-policy>hz.ADD_NEW_ENTRY</merge-policy>
+<!--		
+		<near-cache>
+			<time-to-live-seconds>5</time-to-live-seconds>
+			<max-idle-seconds>60</max-idle-seconds>
+			<eviction-policy>LRU</eviction-policy>
+			<max-size>1000</max-size>
+			<invalidate-on-change>true</invalidate-on-change>
+		</near-cache>
+-->
+    </map>
+	
+</hazelcast>

+ 97 - 0
libs/service/jlansrv.conf

@@ -0,0 +1,97 @@
+#********************************************************************
+# Wrapper Properties
+#********************************************************************
+# Java Application
+wrapper.java.command=java
+
+# Java Main class
+wrapper.java.mainclass=org.alfresco.jlan.app.JLANServerService
+
+# Java Classpath (include wrapper.jar)  Add class path elements as
+#  needed starting from 1
+wrapper.java.classpath.1=../wrapper.jar
+wrapper.java.classpath.2=../../jars/alfresco-jlan.jar
+wrapper.java.classpath.3=../../libs/cryptix-jce-provider.jar
+
+# Java Library Path (location of Wrapper.DLL or libwrapper.so)
+wrapper.java.library.path.1=./
+wrapper.java.library.path.2=../../jni
+
+# Java Additional Parameters
+#wrapper.java.additional.1=-DPATH="%PATH%"
+
+# Initial Java Heap Size (in MB)
+wrapper.java.initmemory=64
+
+# Maximum Java Heap Size (in MB)
+wrapper.java.maxmemory=256
+
+# Application parameters.  Add parameters as needed starting from 1
+#wrapper.app.parameter.1=
+
+# JLAN Server service startup timeout
+wrapper.startup.timeout=30
+
+#********************************************************************
+# Wrapper Logging Properties
+#********************************************************************
+# Format of output for the console.  (See docs for formats)
+wrapper.console.format=LPM
+
+# Log Level for console output.  (See docs for log levels)
+wrapper.console.loglevel=INFO
+
+# Log file to use for wrapper output logging.
+wrapper.logfile=jlanserver.log
+
+# Format of output for the log file.  (See docs for formats)
+wrapper.logfile.format=LPTM
+
+# Log Level for log file output.  (See docs for log levels)
+wrapper.logfile.loglevel=INFO
+
+# Maximum size that the log file will be allowed to grow to before
+#  the log is rolled. Size is specified in bytes.  The default value
+#  of 0, disables log rolling.  May abbreviate with the 'k' (kb) or
+#  'm' (mb) suffix.  For example: 10m = 10 megabytes.
+wrapper.logfile.maxsize=0
+
+# Maximum number of rolled log files which will be allowed before old
+#  files are deleted.  The default value of 0 implies no limit.
+wrapper.logfile.maxfiles=0
+
+# Log Level for sys/event log output.  (See docs for log levels)
+wrapper.syslog.loglevel=NONE
+
+#********************************************************************
+# Wrapper NT Service Properties
+#********************************************************************
+# WARNING - Do not modify any of these properties when an application
+#  using this configuration file has been installed as a service.
+#  Please uninstall the service before modifying this section.  The
+#  service can then be reinstalled.
+
+# Name of the service
+wrapper.ntservice.name=JLANServer
+
+# Display name of the service
+wrapper.ntservice.displayname=Alfresco JLAN Server
+
+# Description of the service
+wrapper.ntservice.description=SMB/CIFS, NFS and FTP virtual filesystem server
+
+# Service dependencies.  Add dependencies as needed starting from 1
+wrapper.ntservice.dependency.1=
+
+# Mode in which the service is installed.  AUTO_START or DEMAND_START
+wrapper.ntservice.starttype=DEMAND_START
+
+# Allow the service to interact with the desktop.
+wrapper.ntservice.interactive=false
+
+# Account to run the service under
+#
+# Do not use .\LocalSystem as the server needs access to the network
+
+wrapper.ntservice.account=
+wrapper.ntservice.password=

+ 54 - 0
libs/service/license.txt

@@ -0,0 +1,54 @@
+Copyright (c) 1999, 2006 Tanuki Software, Inc.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of the Java Service Wrapper and associated
+documentation files (the "Software"), to deal in the Software
+without  restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sub-license,
+and/or sell copies of the Software, and to permit persons to
+whom the Software is furnished to do so, subject to the
+following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+
+Portions of the Software have been derived from source code
+developed by Silver Egg Technology under the following license:
+
+BEGIN Silver Egg Techology License -----------------------------------
+    
+    Copyright (c) 2001 Silver Egg Technology
+    
+    Permission is hereby granted, free of charge, to any person
+    obtaining a copy of this software and associated documentation
+    files (the "Software"), to deal in the Software without 
+    restriction, including without limitation the rights to use, 
+    copy, modify, merge, publish, distribute, sub-license, and/or 
+    sell copies of the Software, and to permit persons to whom the
+    Software is furnished to do so, subject to the following 
+    conditions:
+    
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+    
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
+    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+    NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
+    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
+    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
+    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+    OTHER DEALINGS IN THE SOFTWARE.
+    
+END Silver Egg Techology License -------------------------------------
+

BIN
libs/service/linux/jlanserver


+ 212 - 0
libs/service/linux/jlanserver.sh

@@ -0,0 +1,212 @@
+#! /bin/bash
+
+#
+# JLAN Server Startup Script
+#
+
+#-----------------------------------------------------------------------------
+# These settings can be modified to fit the needs of your application
+
+# Application
+APP_NAME="JLANServer"
+APP_LONG_NAME="JLANServer Virtual Filesystem Server"
+
+# Wrapper
+WRAPPER_CMD="./wrapper"
+WRAPPER_CONF="./jlansrv.conf"
+
+# Priority (see the start() method if you want to use this)
+PRIORITY=
+
+# Do not modify anything beyond this point
+#-----------------------------------------------------------------------------
+
+# Get the fully qualified path to the script
+case $0 in
+    /*)
+        SCRIPT="$0"
+        ;;
+    *)
+        PWD=`pwd`
+        SCRIPT="$PWD/$0"
+        ;;
+esac
+
+# Change spaces to ":" so the tokens can be parsed.
+SCRIPT=`echo $SCRIPT | sed -e 's; ;:;g'`
+# Get the real path to this script, resolving any symbolic links
+TOKENS=`echo $SCRIPT | sed -e 's;/; ;g'`
+REALPATH=
+for C in $TOKENS; do
+    REALPATH="$REALPATH/$C"
+    while [ -h "$REALPATH" ] ; do
+        LS="`ls -ld "$REALPATH"`"
+        LINK="`expr "$LS" : '.*-> \(.*\)$'`"
+        if expr "$LINK" : '/.*' > /dev/null; then
+            REALPATH="$LINK"
+        else
+            REALPATH="`dirname "$REALPATH"`""/$LINK"
+        fi
+    done
+done
+# Change ":" chars back to spaces.
+REALPATH=`echo $REALPATH | sed -e 's;:; ;g'`
+
+# Change the current directory to the location of the script
+cd "`dirname "$REALPATH"`"
+
+# Find pidof.
+PIDOF="/bin/pidof"
+if [ ! -x $PIDOF ]
+then
+    PIDOF="/sbin/pidof"
+    if [ ! -x $PIDOF ]
+    then
+        echo "Cannot find 'pidof' in /bin or /sbin."
+        echo "This script requires 'pidof' to run."
+        exit 1
+    fi
+fi
+
+console() {
+    echo "Running $APP_LONG_NAME..."
+    pid=`$PIDOF $APP_NAME`
+    if [ -z $pid ]
+    then
+        # If you wanted to specify the priority with which
+        # your app runs, you could use nice here:
+        # exec -a $APP_NAME nice -$PRIORITY $WRAPPER_CMD $WRAPPER_CONF
+        # See "man nice" for more details.
+        exec -a $APP_NAME $WRAPPER_CMD $WRAPPER_CONF
+    else
+        echo "$APP_LONG_NAME is already running."
+        exit 1
+    fi
+}
+
+start() {
+    echo "Starting $APP_LONG_NAME..."
+    pid=`$PIDOF $APP_NAME`
+    if [ -z $pid ]
+    then
+        # If you wanted to specify the priority with which
+        # your app runs, you could use nice here:
+        # exec -a $APP_NAME nice -$PRIORITY $WRAPPER_CMD $WRAPPER_CONF wrapper.daemonize=TRUE wrapper.console.loglevel=NONE
+        # See "man nice" for more details.
+        exec -a $APP_NAME $WRAPPER_CMD $WRAPPER_CONF wrapper.daemonize=TRUE wrapper.console.loglevel=NONE
+    else
+        echo "$APP_LONG_NAME is already running."
+        exit 1
+    fi
+}
+
+stopit() {
+    echo "Stopping $APP_LONG_NAME..."
+    pid=`$PIDOF $APP_NAME`
+    if [ -z $pid ]
+    then
+        echo "$APP_LONG_NAME was not running."
+    else
+        # Running so try to stop it.
+        kill $pid
+        if [ $? -ne 0 ]
+        then
+            # An explanation for the failure should have been given
+            echo "Unable to stop $APP_LONG_NAME."
+            exit 1
+        fi
+
+        # We can not predict how long it will take for the wrapper to
+        #  actually stop as it depends on settings in wrapper.conf.
+        #  Loop until it does.
+        CNT=0
+        TOTCNT=0
+        while [ ! -z $pid ]
+        do
+            # Loop for up to 5 minutes
+            if [ "$TOTCNT" -lt "300" ]
+            then
+                if [ "$CNT" -lt "5" ]
+                then
+                    CNT=`expr $CNT + 1`
+                else
+                    echo "Waiting for $APP_LONG_NAME to exit..."
+                    CNT=0
+                fi
+                TOTCNT=`expr $TOTCNT + 1`
+
+                sleep 1
+
+                pid=`$PIDOF $APP_NAME`
+            else
+                pid=
+            fi
+        done
+
+        pid=`$PIDOF $APP_NAME`
+        if [ ! -z $pid ]
+        then
+            echo "Timed out waiting for $APP_LONG_NAME to exit."
+            echo "  Attempting a forced exit..."
+            kill -9 $pid
+        fi
+
+        pid=`$PIDOF $APP_NAME`
+        if [ ! -z $pid ]
+        then
+            echo "Failed to stop $APP_LONG_NAME."
+            exit 1
+        else
+            echo "Stopped $APP_LONG_NAME."
+        fi
+    fi
+}
+
+dump() {
+    echo "Dumping $APP_LONG_NAME..."
+    pid=`$PIDOF $APP_NAME`
+    if [ -z $pid ]
+    then
+        echo "$APP_LONG_NAME was not running."
+    else
+        kill -3 $pid
+
+        if [ $? -ne 0 ]
+        then
+            echo "Failed to dump $APP_LONG_NAME."
+        else
+            echo "Dumped $APP_LONG_NAME."
+        fi
+    fi
+}
+
+case "$1" in
+
+    'console')
+        console
+        ;;
+
+    'start')
+        start
+        ;;
+
+    'stop')
+        stopit
+        ;;
+
+    'restart')
+        stopit
+        start
+        ;;
+
+    'dump')
+        dump
+        ;;
+
+    *)
+        echo "Usage: $0 { console | start | stop | restart | dump }"
+        exit 1
+        ;;
+esac
+
+exit 0

BIN
libs/service/linux/libwrapper.so


BIN
libs/service/macosx/jlanserver


BIN
libs/service/macosx/libwrapper.jnilib


+ 5 - 0
libs/service/readme.txt

@@ -0,0 +1,5 @@
+Java Service Wrapper
+
+Complete documentation can be found by viewing doc/english/index.html
+or by going to http://wrapper.tanukisoftware.org
+

BIN
libs/service/solaris/jlanserver


BIN
libs/service/solaris/libwrapper.so


BIN
libs/service/windows/jlanserver.exe


BIN
libs/service/windows/wrapper.dll


BIN
libs/service/wrapper.jar


+ 97 - 0
src/javax/security/auth/AuthPermission.java

@@ -0,0 +1,97 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth;
+
+import java.security.BasicPermission;
+
+import org.apache.harmony.auth.internal.nls.Messages;
+
+/**
+ * Governs the use of methods in this package and also its subpackages. A
+ * <i>target name</i> of the permission specifies which methods are allowed
+ * without specifying the concrete action lists. Possible target names and
+ * associated authentication permissions are:
+ *
+ * <pre>
+ *    doAs                      invoke Subject.doAs methods.
+ *    doAsPrivileged            invoke the Subject.doAsPrivileged methods.
+ *    getSubject                invoke Subject.getSubject().
+ *    getSubjectFromDomainCombiner    invoke SubjectDomainCombiner.getSubject().
+ *    setReadOnly               invoke Subject.setReadonly().
+ *    modifyPrincipals          modify the set of principals
+ *                              associated with a Subject.
+ *    modifyPublicCredentials   modify the set of public credentials
+ *                              associated with a Subject.
+ *    modifyPrivateCredentials  modify the set of private credentials
+ *                              associated with a Subject.
+ *    refreshCredential         invoke the refresh method on a credential of a
+ *                              refreshable credential class.
+ *    destroyCredential         invoke the destroy method on a credential of a
+ *                              destroyable credential class.
+ *    createLoginContext.<i>name</i>   instantiate a LoginContext with the
+ *                              specified name. The wildcard name ('*')
+ *                              allows to a LoginContext of any name.
+ *    getLoginConfiguration     invoke the getConfiguration method of
+ *                              javax.security.auth.login.Configuration.
+ *    refreshLoginConfiguration Invoke the refresh method of
+ *                              javax.security.auth.login.Configuration.
+ * </pre>
+ */
+public final class AuthPermission extends BasicPermission {
+
+    private static final long serialVersionUID = 5806031445061587174L;
+
+    private static final String CREATE_LOGIN_CONTEXT = "createLoginContext"; //$NON-NLS-1$
+
+    private static final String CREATE_LOGIN_CONTEXT_ANY = "createLoginContext.*"; //$NON-NLS-1$
+
+    // inits permission name.
+    private static String init(String name) {
+
+        if (name == null) {
+            throw new NullPointerException(Messages.getString("auth.13")); //$NON-NLS-1$
+        }
+
+        if (CREATE_LOGIN_CONTEXT.equals(name)) {
+            return CREATE_LOGIN_CONTEXT_ANY;
+        }
+        return name;
+    }
+
+    /**
+     * Creates an authentication permission with the specified target name.
+     *
+     * @param name
+     *            the target name of this authentication permission.
+     */
+    public AuthPermission(String name) {
+        super(init(name));
+    }
+
+    /**
+     * Creates an authentication permission with the specified target name.
+     *
+     * @param name
+     *            the target name of this authentication permission.
+     * @param actions
+     *            this parameter is ignored and should be {@code null}.
+     */
+    public AuthPermission(String name, String actions) {
+        super(init(name), actions);
+    }
+}

+ 44 - 0
src/javax/security/auth/DestroyFailedException.java

@@ -0,0 +1,44 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth;
+
+/**
+ * Signals that the {@link Destroyable#destroy()} method failed.
+ */
+public class DestroyFailedException extends Exception {
+
+    private static final long serialVersionUID = -7790152857282749162L;
+
+    /**
+     * Creates an exception of type {@code DestroyFailedException}.
+     */
+    public DestroyFailedException() {
+        super();
+    }
+
+    /**
+     * Creates an exception of type {@code DestroyFailedException}.
+     *
+     * @param message
+     *            A detail message that describes the reason for this exception.
+     */
+    public DestroyFailedException(String message) {
+        super(message);
+    }
+
+}

+ 43 - 0
src/javax/security/auth/Destroyable.java

@@ -0,0 +1,43 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth;
+
+/**
+ * Allows for special treatment of sensitive information, when it comes to
+ * destroying or clearing of the data.
+ */
+public interface Destroyable {
+
+    /**
+     * Erases the sensitive information. Once an object is destroyed any calls
+     * to its methods will throw an {@code IllegalStateException}. If it does
+     * not succeed a DestroyFailedException is thrown.
+     *
+     * @throws DestroyFailedException
+     *             if the information cannot be erased.
+     */
+    void destroy() throws DestroyFailedException;
+
+    /**
+     * Returns {@code true} once an object has been safely destroyed.
+     *
+     * @return whether the object has been safely destroyed.
+     */
+    boolean isDestroyed();
+
+}

+ 130 - 0
src/javax/security/auth/Policy.java

@@ -0,0 +1,130 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth;
+
+import java.security.AccessController;
+import java.security.CodeSource;
+import java.security.PermissionCollection;
+import java.security.PrivilegedAction;
+
+import org.apache.harmony.security.fortress.PolicyUtils;
+import org.apache.harmony.auth.DefaultSubjectPolicy;
+import org.apache.harmony.auth.internal.nls.Messages;
+
+/**
+ * @deprecated Use
+ *             {@link java.security.Policy#getPermissions(java.security.ProtectionDomain)}
+ *             and
+ *             {@link java.security.ProtectionDomain#ProtectionDomain(java.security.CodeSource, java.security.PermissionCollection, ClassLoader, java.security.Principal[])}
+ *             to establish a policy's permissions for a principal.
+ */
+@Deprecated
+public abstract class Policy {
+    // Key to security properties, defining default policy provider.
+    private static final String POLICY_PROVIDER = "auth.policy.provider"; //$NON-NLS-1$
+
+    // The AuthPermission required to set custom Policy.
+    private static final AuthPermission SET_POLICY = new AuthPermission("setPolicy"); //$NON-NLS-1$
+
+    // The AuthPermission required to get current Policy.
+    private static final AuthPermission GET_POLICY = new AuthPermission("getPolicy"); //$NON-NLS-1$
+
+    // the current policy object
+    private static Policy activePolicy;
+
+    public abstract PermissionCollection getPermissions(Subject subject, CodeSource cs);
+
+    public abstract void refresh();
+
+    protected Policy() {
+        super();
+    }
+
+    public static Policy getPolicy() {
+        SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(GET_POLICY);
+        }
+        return getAccessiblePolicy();
+
+    }
+
+    /**
+     * Shortcut accessor for friendly classes, to skip security checks. If
+     * active policy was set to <code>null</code>, tries to load a default
+     * provider, so this method never returns <code>null</code>. <br>
+     * This method is synchronized with setPolicy()
+     */
+    static Policy getAccessiblePolicy() {
+        Policy current = activePolicy;
+        if (current == null) {
+            synchronized (Policy.class) {
+                // double check in case value has been reassigned
+                // while we've been awaiting monitor
+                if (activePolicy == null) {
+                    activePolicy = getDefaultProvider();
+                }
+                return activePolicy;
+            }
+        }
+        return current;
+    }
+
+    /**
+     * Reads name of default policy provider from security.properties, loads the
+     * class and instantiates the provider. In case of any exception, wraps it
+     * with SecurityException and throws further.
+     */
+    private static final Policy getDefaultProvider() {
+        final String defaultClass = AccessController
+                .doPrivileged(new PolicyUtils.SecurityPropertyAccessor(POLICY_PROVIDER));
+
+        if (defaultClass == null) {
+            return new DefaultSubjectPolicy();
+        }
+
+        Object policy = AccessController.doPrivileged(new PrivilegedAction<Object>() {
+            public Object run() {
+                try {
+                    return Class
+                            .forName(defaultClass, true, ClassLoader.getSystemClassLoader())
+                            .newInstance();
+                } catch (Exception e) {
+                    SecurityException se = new SecurityException(Messages.getString("auth.08")); //$NON-NLS-1$
+                    se.initCause(e);
+                    throw se;
+                }
+            }
+        });
+
+        if (!(policy instanceof Policy)) {
+            throw new SecurityException(Messages.getString("auth.08")); //$NON-NLS-1$
+        }
+        return (Policy) policy;
+    }
+
+    public static void setPolicy(Policy policy) {
+        SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(SET_POLICY);
+        }
+        synchronized (Policy.class) {
+            activePolicy = policy;
+        }
+    }
+}

+ 395 - 0
src/javax/security/auth/PrivateCredentialPermission.java

@@ -0,0 +1,395 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Principal;
+import java.util.Set;
+
+import org.apache.harmony.auth.internal.nls.Messages;
+
+/**
+ * Protects private credential objects belonging to a {@code Subject}. It has
+ * only one action which is "read". The target name of this permission has a
+ * special syntax:
+ *
+ * <pre>
+ * targetName = CredentialClass {PrincipalClass &quot;PrincipalName&quot;}*
+ * </pre>
+ *
+ * First it states a credential class and is followed then by a list of one or
+ * more principals identifying the subject.
+ * <p>
+ * The principals on their part are specified as the name of the {@code
+ * Principal} class followed by the principal name in quotes. For example, the
+ * following file may define permission to read the private credentials of a
+ * principal named "Bob": "com.sun.PrivateCredential com.sun.Principal \"Bob\""
+ * <p>
+ * The syntax also allows the use of the wildcard "*" in place of {@code
+ * CredentialClass} or {@code PrincipalClass} and/or {@code PrincipalName}.
+ *
+ * @see Principal
+ */
+public final class PrivateCredentialPermission extends Permission {
+
+    private static final long serialVersionUID = 5284372143517237068L;
+
+    // allowed action
+    private static final String READ = "read"; //$NON-NLS-1$
+
+    private String credentialClass;
+
+    // current offset        
+    private transient int offset;
+
+    // owners set
+    private transient CredOwner[] set;
+    
+    /**
+     * Creates a new permission for private credentials specified by the target
+     * name {@code name} and an {@code action}. The action is always
+     * {@code "read"}.
+     *
+     * @param name
+     *            the target name of the permission.
+     * @param action
+     *            the action {@code "read"}.
+     */
+    public PrivateCredentialPermission(String name, String action) {
+        super(name);
+        if (READ.equalsIgnoreCase(action)) {
+            initTargetName(name);
+        } else {
+            throw new IllegalArgumentException(Messages.getString("auth.11")); //$NON-NLS-1$
+        }
+    }
+
+    /**
+     * Creates a {@code PrivateCredentialPermission} from the {@code Credential}
+     * class and set of principals.
+     * 
+     * @param credentialClass
+     *            the credential class name.
+     * @param principals
+     *            the set of principals.
+     */
+    PrivateCredentialPermission(String credentialClass, Set<Principal> principals) {
+        super(credentialClass);
+        this.credentialClass = credentialClass;
+
+        set = new CredOwner[principals.size()];
+        for (Principal p : principals) {
+            CredOwner element = new CredOwner(p.getClass().getName(), p.getName());
+            // check for duplicate elements
+            boolean found = false;
+            for (int ii = 0; ii < offset; ii++) {
+                if (set[ii].equals(element)) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                set[offset++] = element;
+            }
+        }
+    }
+
+    /**
+     * Initialize a PrivateCredentialPermission object and checks that a target
+     * name has a correct format: CredentialClass 1*(PrincipalClass
+     * "PrincipalName")
+     */
+    private void initTargetName(String name) {
+
+        if (name == null) {
+            throw new NullPointerException(Messages.getString("auth.0E")); //$NON-NLS-1$
+        }
+
+        // check empty string
+        name = name.trim();
+        if (name.length() == 0) {
+            throw new IllegalArgumentException(Messages.getString("auth.0F")); //$NON-NLS-1$
+        }
+
+        // get CredentialClass
+        int beg = name.indexOf(' ');
+        if (beg == -1) {
+            throw new IllegalArgumentException(Messages.getString("auth.10")); //$NON-NLS-1$
+        }
+        credentialClass = name.substring(0, beg);
+
+        // get a number of pairs: PrincipalClass "PrincipalName"
+        beg++;
+        int count = 0;
+        int nameLength = name.length();
+        for (int i, j = 0; beg < nameLength; beg = j + 2, count++) {
+            i = name.indexOf(' ', beg);
+            j = name.indexOf('"', i + 2);
+
+            if (i == -1 || j == -1 || name.charAt(i + 1) != '"') {
+                throw new IllegalArgumentException(Messages.getString("auth.10")); //$NON-NLS-1$
+            }
+        }
+
+        // name MUST have one pair at least
+        if (count < 1) {
+            throw new IllegalArgumentException(Messages.getString("auth.10")); //$NON-NLS-1$
+        }
+
+        beg = name.indexOf(' ');
+        beg++;
+
+        // populate principal set with instances of CredOwner class
+        String principalClass;
+        String principalName;
+
+        set = new CredOwner[count];
+        for (int index = 0, i, j; index < count; beg = j + 2, index++) {
+            i = name.indexOf(' ', beg);
+            j = name.indexOf('"', i + 2);
+
+            principalClass = name.substring(beg, i);
+            principalName = name.substring(i + 2, j);
+
+            CredOwner element = new CredOwner(principalClass, principalName);
+            // check for duplicate elements
+            boolean found = false;
+            for (int ii = 0; ii < offset; ii++) {
+                if (set[ii].equals(element)) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                set[offset++] = element;
+            }
+        }
+    }
+
+    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
+        ois.defaultReadObject();
+        initTargetName(getName());
+    }
+
+    /**
+     * Returns the principal's classes and names associated with this {@code
+     * PrivateCredentialPermission} as a two dimensional array. The first
+     * dimension of the array corresponds to the number of principals. The
+     * second dimension defines either the name of the {@code PrincipalClass}
+     * [x][0] or the value of {@code PrincipalName} [x][1].
+     * <p>
+     * This corresponds to the the target name's syntax:
+     *
+     * <pre>
+     * targetName = CredentialClass {PrincipalClass &quot;PrincipalName&quot;}*
+     * </pre>
+     *
+     * @return the principal classes and names associated with this {@code
+     *         PrivateCredentialPermission}.
+     */
+    public String[][] getPrincipals() {
+
+        String[][] s = new String[offset][2];
+
+        for (int i = 0; i < s.length; i++) {
+            s[i][0] = set[i].principalClass;
+            s[i][1] = set[i].principalName;
+        }
+        return s;
+    }
+
+    @Override
+    public String getActions() {
+        return READ;
+    }
+
+    /**
+     * Returns the class name of the credential associated with this permission.
+     *
+     * @return the class name of the credential associated with this permission.
+     */
+    public String getCredentialClass() {
+        return credentialClass;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 0;
+        for (int i = 0; i < offset; i++) {
+            hash = hash + set[i].hashCode();
+        }
+        return getCredentialClass().hashCode() + hash;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+
+        if (obj == null || this.getClass() != obj.getClass()) {
+            return false;
+        }
+
+        PrivateCredentialPermission that = (PrivateCredentialPermission) obj;
+
+        return credentialClass.equals(that.credentialClass) && (offset == that.offset)
+                && sameMembers(set, that.set, offset);
+    }
+
+    @Override
+    public boolean implies(Permission permission) {
+
+        if (permission == null || this.getClass() != permission.getClass()) {
+            return false;
+        }
+
+        PrivateCredentialPermission that = (PrivateCredentialPermission) permission;
+
+        if (!("*".equals(credentialClass) || credentialClass //$NON-NLS-1$
+                .equals(that.getCredentialClass()))) {
+            return false;
+        }
+
+        if (that.offset == 0) {
+            return true;
+        }
+
+        CredOwner[] thisCo = set;
+        CredOwner[] thatCo = that.set;
+        int thisPrincipalsSize = offset;
+        int thatPrincipalsSize = that.offset;
+        for (int i = 0, j; i < thisPrincipalsSize; i++) {
+            for (j = 0; j < thatPrincipalsSize; j++) {
+                if (thisCo[i].implies(thatCo[j])) {
+                    break;
+                }
+            }
+            if (j == thatCo.length) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public PermissionCollection newPermissionCollection() {
+        return null;
+    }
+
+    /**
+     * Returns true if the two arrays have the same length, and every member of
+     * one array is contained in another array
+     */
+    private boolean sameMembers(Object[] ar1, Object[] ar2, int length) {
+        if (ar1 == null && ar2 == null) {
+            return true;
+        }
+        if (ar1 == null || ar2 == null) {
+            return false;
+        }
+        boolean found;
+        for (int i = 0; i < length; i++) {
+            found = false;
+            for (int j = 0; j < length; j++) {
+                if (ar1[i].equals(ar2[j])) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static final class CredOwner implements Serializable {
+
+        private static final long serialVersionUID = -5607449830436408266L;
+
+        String principalClass;
+
+        String principalName;
+
+        // whether class name contains wildcards
+        private transient boolean isClassWildcard;
+
+        // whether pname contains wildcards
+        private transient boolean isPNameWildcard;
+
+        // Creates a new CredOwner with the specified Principal Class and Principal Name 
+        CredOwner(String principalClass, String principalName) {
+            super();
+            if ("*".equals(principalClass)) { //$NON-NLS-1$
+                isClassWildcard = true;
+            }
+
+            if ("*".equals(principalName)) { //$NON-NLS-1$
+                isPNameWildcard = true;
+            }
+
+            if (isClassWildcard && !isPNameWildcard) {
+                throw new IllegalArgumentException(Messages.getString("auth.12")); //$NON-NLS-1$
+            }
+
+            this.principalClass = principalClass;
+            this.principalName = principalName;
+        }
+
+        // Checks if this CredOwner implies the specified Object. 
+        boolean implies(Object obj) {
+            if (obj == this) {
+                return true;
+            }
+
+            CredOwner co = (CredOwner) obj;
+
+            if (isClassWildcard || principalClass.equals(co.principalClass)) {
+                if (isPNameWildcard || principalName.equals(co.principalName)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        // Checks two CredOwner objects for equality. 
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == this) {
+                return true;
+            }
+            if (obj instanceof CredOwner) {
+                CredOwner that = (CredOwner) obj;
+                return principalClass.equals(that.principalClass)
+                    && principalName.equals(that.principalName);
+            }
+            return false;
+        }
+
+        // Returns the hash code value for this object.
+        @Override
+        public int hashCode() {
+            return principalClass.hashCode() + principalName.hashCode();
+        }
+    }
+}

+ 31 - 0
src/javax/security/auth/RefreshFailedException.java

@@ -0,0 +1,31 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth;
+
+public class RefreshFailedException extends Exception {
+
+    private static final long serialVersionUID = 5058444488565265840L;
+
+    public RefreshFailedException() {
+        super();
+    }
+
+    public RefreshFailedException(String message) {
+        super(message);
+    }
+}

+ 26 - 0
src/javax/security/auth/Refreshable.java

@@ -0,0 +1,26 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth;
+
+public interface Refreshable {
+
+    void refresh() throws RefreshFailedException;
+
+    boolean isCurrent();
+
+}

+ 782 - 0
src/javax/security/auth/Subject.java

@@ -0,0 +1,782 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.DomainCombiner;
+import java.security.Permission;
+import java.security.Principal;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.security.ProtectionDomain;
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Set;
+
+import org.apache.harmony.auth.internal.nls.Messages;
+
+/**
+ * The central class of the {@code javax.security.auth} package representing an
+ * authenticated user or entity (both referred to as "subject"). IT defines also
+ * the static methods that allow code to be run, and do modifications according
+ * to the subject's permissions.
+ * <p>
+ * A subject has the following features:
+ * <ul>
+ * <li>A set of {@code Principal} objects specifying the identities bound to a
+ * {@code Subject} that distinguish it.</li>
+ * <li>Credentials (public and private) such as certificates, keys, or
+ * authentication proofs such as tickets</li>
+ * </ul>
+ */
+public final class Subject implements Serializable {
+
+    private static final long serialVersionUID = -8308522755600156056L;
+    
+    private static final AuthPermission _AS = new AuthPermission("doAs"); //$NON-NLS-1$
+
+    private static final AuthPermission _AS_PRIVILEGED = new AuthPermission(
+            "doAsPrivileged"); //$NON-NLS-1$
+
+    private static final AuthPermission _SUBJECT = new AuthPermission(
+            "getSubject"); //$NON-NLS-1$
+
+    private static final AuthPermission _PRINCIPALS = new AuthPermission(
+            "modifyPrincipals"); //$NON-NLS-1$
+
+    private static final AuthPermission _PRIVATE_CREDENTIALS = new AuthPermission(
+            "modifyPrivateCredentials"); //$NON-NLS-1$
+
+    private static final AuthPermission _PUBLIC_CREDENTIALS = new AuthPermission(
+            "modifyPublicCredentials"); //$NON-NLS-1$
+
+    private static final AuthPermission _READ_ONLY = new AuthPermission(
+            "setReadOnly"); //$NON-NLS-1$
+
+    private final Set<Principal> principals;
+
+    private boolean readOnly;
+    
+    // set of private credentials
+    private transient SecureSet<Object> privateCredentials;
+
+    // set of public credentials
+    private transient SecureSet<Object> publicCredentials;
+    
+    /**
+     * The default constructor initializing the sets of public and private
+     * credentials and principals with the empty set.
+     */
+    public Subject() {
+        super();
+        principals = new SecureSet<Principal>(_PRINCIPALS);
+        publicCredentials = new SecureSet<Object>(_PUBLIC_CREDENTIALS);
+        privateCredentials = new SecureSet<Object>(_PRIVATE_CREDENTIALS);
+
+        readOnly = false;
+    }
+
+    /**
+     * The constructor for the subject, setting its public and private
+     * credentials and principals according to the arguments.
+     *
+     * @param readOnly
+     *            {@code true} if this {@code Subject} is read-only, thus
+     *            preventing any modifications to be done.
+     * @param subjPrincipals
+     *            the set of Principals that are attributed to this {@code
+     *            Subject}.
+     * @param pubCredentials
+     *            the set of public credentials that distinguish this {@code
+     *            Subject}.
+     * @param privCredentials
+     *            the set of private credentials that distinguish this {@code
+     *            Subject}.
+     */
+    public Subject(boolean readOnly, Set<? extends Principal> subjPrincipals,
+            Set<?> pubCredentials, Set<?> privCredentials) {
+
+        if (subjPrincipals == null || pubCredentials == null || privCredentials == null) {
+            throw new NullPointerException();
+        }
+
+        principals = new SecureSet<Principal>(_PRINCIPALS, subjPrincipals);
+        publicCredentials = new SecureSet<Object>(_PUBLIC_CREDENTIALS, pubCredentials);
+        privateCredentials = new SecureSet<Object>(_PRIVATE_CREDENTIALS, privCredentials);
+
+        this.readOnly = readOnly;
+    }
+
+    /**
+     * Runs the code defined by {@code action} using the permissions granted to
+     * the {@code Subject} itself and to the code as well.
+     *
+     * @param subject
+     *            the distinguished {@code Subject}.
+     * @param action
+     *            the code to be run.
+     * @return the {@code Object} returned when running the {@code action}.
+     */
+    @SuppressWarnings("unchecked")
+    public static Object doAs(Subject subject, PrivilegedAction action) {
+
+        checkPermission(_AS);
+
+        return doAs_PrivilegedAction(subject, action, AccessController.getContext());
+    }
+
+    /**
+     * Run the code defined by {@code action} using the permissions granted to
+     * the {@code Subject} and to the code itself, additionally providing a more
+     * specific context.
+     *
+     * @param subject
+     *            the distinguished {@code Subject}.
+     * @param action
+     *            the code to be run.
+     * @param context
+     *            the specific context in which the {@code action} is invoked.
+     *            if {@code null} a new {@link AccessControlContext} is
+     *            instantiated.
+     * @return the {@code Object} returned when running the {@code action}.
+     */
+    @SuppressWarnings("unchecked")
+    public static Object doAsPrivileged(Subject subject, PrivilegedAction action,
+            AccessControlContext context) {
+
+        checkPermission(_AS_PRIVILEGED);
+
+        if (context == null) {
+            return doAs_PrivilegedAction(subject, action, new AccessControlContext(
+                    new ProtectionDomain[0]));
+        }
+        return doAs_PrivilegedAction(subject, action, context);
+    }
+
+    // instantiates a new context and passes it to AccessController
+    @SuppressWarnings("unchecked")
+    private static Object doAs_PrivilegedAction(Subject subject, PrivilegedAction action,
+            final AccessControlContext context) {
+
+        AccessControlContext newContext;
+
+        final SubjectDomainCombiner combiner;
+        if (subject == null) {
+            // performance optimization
+            // if subject is null there is nothing to combine
+            combiner = null;
+        } else {
+            combiner = new SubjectDomainCombiner(subject);
+        }
+
+        PrivilegedAction dccAction = new PrivilegedAction() {
+            public Object run() {
+
+                return new AccessControlContext(context, combiner);
+            }
+        };
+
+        newContext = (AccessControlContext) AccessController.doPrivileged(dccAction);
+
+        return AccessController.doPrivileged(action, newContext);
+    }
+
+    /**
+     * Runs the code defined by {@code action} using the permissions granted to
+     * the subject and to the code itself.
+     *
+     * @param subject
+     *            the distinguished {@code Subject}.
+     * @param action
+     *            the code to be run.
+     * @return the {@code Object} returned when running the {@code action}.
+     * @throws PrivilegedActionException
+     *             if running the {@code action} throws an exception.
+     */
+    @SuppressWarnings("unchecked")
+    public static Object doAs(Subject subject, PrivilegedExceptionAction action)
+            throws PrivilegedActionException {
+
+        checkPermission(_AS);
+
+        return doAs_PrivilegedExceptionAction(subject, action, AccessController.getContext());
+    }
+
+    /**
+     * Runs the code defined by {@code action} using the permissions granted to
+     * the subject and to the code itself, additionally providing a more
+     * specific context.
+     *
+     * @param subject
+     *            the distinguished {@code Subject}.
+     * @param action
+     *            the code to be run.
+     * @param context
+     *            the specific context in which the {@code action} is invoked.
+     *            if {@code null} a new {@link AccessControlContext} is
+     *            instantiated.
+     * @return the {@code Object} returned when running the {@code action}.
+     * @throws PrivilegedActionException
+     *             if running the {@code action} throws an exception.
+     */
+    @SuppressWarnings("unchecked")
+    public static Object doAsPrivileged(Subject subject,
+            PrivilegedExceptionAction action, AccessControlContext context)
+            throws PrivilegedActionException {
+
+        checkPermission(_AS_PRIVILEGED);
+
+        if (context == null) {
+            return doAs_PrivilegedExceptionAction(subject, action,
+                    new AccessControlContext(new ProtectionDomain[0]));
+        }
+        return doAs_PrivilegedExceptionAction(subject, action, context);
+    }
+
+    // instantiates a new context and passes it to AccessController
+    @SuppressWarnings("unchecked")
+    private static Object doAs_PrivilegedExceptionAction(Subject subject,
+            PrivilegedExceptionAction action, final AccessControlContext context)
+            throws PrivilegedActionException {
+
+        AccessControlContext newContext;
+
+        final SubjectDomainCombiner combiner;
+        if (subject == null) {
+            // performance optimization
+            // if subject is null there is nothing to combine
+            combiner = null;
+        } else {
+            combiner = new SubjectDomainCombiner(subject);
+        }
+
+        PrivilegedAction<AccessControlContext> dccAction = new PrivilegedAction<AccessControlContext>() {
+            public AccessControlContext run() {
+                return new AccessControlContext(context, combiner);
+            }
+        };
+
+        newContext = AccessController.doPrivileged(dccAction);
+
+        return AccessController.doPrivileged(action, newContext);
+    }
+
+    /**
+     * Checks two Subjects for equality. More specifically if the principals,
+     * public and private credentials are equal, equality for two {@code
+     * Subjects} is implied.
+     *
+     * @param obj
+     *            the {@code Object} checked for equality with this {@code
+     *            Subject}.
+     * @return {@code true} if the specified {@code Subject} is equal to this
+     *         one.
+     */
+    @Override
+    public boolean equals(Object obj) {
+
+        if (this == obj) {
+            return true;
+        }
+
+        if (obj == null || this.getClass() != obj.getClass()) {
+            return false;
+        }
+
+        Subject that = (Subject) obj;
+
+        if (principals.equals(that.principals)
+                && publicCredentials.equals(that.publicCredentials)
+                && privateCredentials.equals(that.privateCredentials)) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns this {@code Subject}'s {@link Principal}.
+     *
+     * @return this {@code Subject}'s {@link Principal}.
+     */
+    public Set<Principal> getPrincipals() {
+        return principals;
+    }
+
+
+    /**
+     * Returns this {@code Subject}'s {@link Principal} which is a subclass of
+     * the {@code Class} provided.
+     *
+     * @param c
+     *            the {@code Class} as a criteria which the {@code Principal}
+     *            returned must satisfy.
+     * @return this {@code Subject}'s {@link Principal}. Modifications to the
+     *         returned set of {@code Principal}s do not affect this {@code
+     *         Subject}'s set.
+     */
+    public <T extends Principal> Set<T> getPrincipals(Class<T> c) {
+        return ((SecureSet<Principal>) principals).get(c);
+    }
+
+    /**
+     * Returns the private credentials associated with this {@code Subject}.
+     *
+     * @return the private credentials associated with this {@code Subject}.
+     */
+    public Set<Object> getPrivateCredentials() {
+        return privateCredentials;
+    }
+
+    /**
+     * Returns this {@code Subject}'s private credentials which are a subclass
+     * of the {@code Class} provided.
+     *
+     * @param c
+     *            the {@code Class} as a criteria which the private credentials
+     *            returned must satisfy.
+     * @return this {@code Subject}'s private credentials. Modifications to the
+     *         returned set of credentials do not affect this {@code Subject}'s
+     *         credentials.
+     */
+    public <T> Set<T> getPrivateCredentials(Class<T> c) {
+        return privateCredentials.get(c);
+    }
+
+    /**
+     * Returns the public credentials associated with this {@code Subject}.
+     *
+     * @return the public credentials associated with this {@code Subject}.
+     */
+    public Set<Object> getPublicCredentials() {
+        return publicCredentials;
+    }
+
+
+    /**
+     * Returns this {@code Subject}'s public credentials which are a subclass of
+     * the {@code Class} provided.
+     *
+     * @param c
+     *            the {@code Class} as a criteria which the public credentials
+     *            returned must satisfy.
+     * @return this {@code Subject}'s public credentials. Modifications to the
+     *         returned set of credentials do not affect this {@code Subject}'s
+     *         credentials.
+     */
+    public <T> Set<T> getPublicCredentials(Class<T> c) {
+        return publicCredentials.get(c);
+    }
+
+    /**
+     * Returns a hash code of this {@code Subject}.
+     *
+     * @return a hash code of this {@code Subject}.
+     */
+    @Override
+    public int hashCode() {
+        return principals.hashCode() + privateCredentials.hashCode()
+                + publicCredentials.hashCode();
+    }
+
+    /**
+     * Prevents from modifications being done to the credentials and {@link
+     * Principal} sets. After setting it to read-only this {@code Subject} can
+     * not be made writable again. The destroy method on the credentials still
+     * works though.
+     */
+    public void setReadOnly() {
+        checkPermission(_READ_ONLY);
+
+        readOnly = true;
+    }
+
+    /**
+     * Returns whether this {@code Subject} is read-only or not.
+     *
+     * @return whether this {@code Subject} is read-only or not.
+     */
+    public boolean isReadOnly() {
+        return readOnly;
+    }
+
+    /**
+     * Returns a {@code String} representation of this {@code Subject}.
+     *
+     * @return a {@code String} representation of this {@code Subject}.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder buf = new StringBuilder("Subject:\n"); //$NON-NLS-1$
+
+        Iterator<?> it = principals.iterator();
+        while (it.hasNext()) {
+            buf.append("\tPrincipal: "); //$NON-NLS-1$
+            buf.append(it.next());
+            buf.append('\n');
+        }
+
+        it = publicCredentials.iterator();
+        while (it.hasNext()) {
+            buf.append("\tPublic Credential: "); //$NON-NLS-1$
+            buf.append(it.next());
+            buf.append('\n');
+        }
+
+        int offset = buf.length() - 1;
+        it = privateCredentials.iterator();
+        try {
+            while (it.hasNext()) {
+                buf.append("\tPrivate Credential: "); //$NON-NLS-1$
+                buf.append(it.next());
+                buf.append('\n');
+            }
+        } catch (SecurityException e) {
+            buf.delete(offset, buf.length());
+            buf.append("\tPrivate Credentials: no accessible information\n"); //$NON-NLS-1$
+        }
+        return buf.toString();
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException,
+            ClassNotFoundException {
+
+        in.defaultReadObject();
+
+        publicCredentials = new SecureSet<Object>(_PUBLIC_CREDENTIALS);
+        privateCredentials = new SecureSet<Object>(_PRIVATE_CREDENTIALS);
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        out.defaultWriteObject();
+    }
+
+    /**
+     * Returns the {@code Subject} that was last associated with the {@code
+     * context} provided as argument.
+     *
+     * @param context
+     *            the {@code context} that was associated with the
+     *            {@code Subject}.
+     * @return the {@code Subject} that was last associated with the {@code
+     *         context} provided as argument.
+     */
+    public static Subject getSubject(final AccessControlContext context) {
+        checkPermission(_SUBJECT);
+        if (context == null) {
+            throw new NullPointerException(Messages.getString("auth.09")); //$NON-NLS-1$
+        }
+        PrivilegedAction<DomainCombiner> action = new PrivilegedAction<DomainCombiner>() {
+            public DomainCombiner run() {
+                return context.getDomainCombiner();
+            }
+        };
+        DomainCombiner combiner = AccessController.doPrivileged(action);
+
+        if ((combiner == null) || !(combiner instanceof SubjectDomainCombiner)) {
+            return null;
+        }
+        return ((SubjectDomainCombiner) combiner).getSubject();
+    }
+
+    // checks passed permission
+    private static void checkPermission(Permission p) {
+        SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(p);
+        }
+    }
+
+    // FIXME is used only in two places. remove?
+    private void checkState() {
+        if (readOnly) {
+            throw new IllegalStateException(Messages.getString("auth.0A")); //$NON-NLS-1$
+        }
+    }
+
+    private final class SecureSet<SST> extends AbstractSet<SST> implements Serializable {
+
+        /**
+         * Compatibility issue: see comments for setType variable
+         */
+        private static final long serialVersionUID = 7911754171111800359L;
+
+        private LinkedList<SST> elements;
+
+        /*
+         * Is used to define a set type for serialization.
+         * 
+         * A type can be principal, priv. or pub. credential set. The spec.
+         * doesn't clearly says that priv. and pub. credential sets can be
+         * serialized and what classes they are. It is only possible to figure
+         * out from writeObject method comments that priv. credential set is
+         * serializable and it is an instance of SecureSet class. So pub.
+         * credential was implemented by analogy
+         * 
+         * Compatibility issue: the class follows its specified serial form.
+         * Also according to the serialization spec. adding new field is a
+         * compatible change. So is ok for principal set (because the default
+         * value for integer is zero). But priv. or pub. credential set it is
+         * not compatible because most probably other implementations resolve
+         * this issue in other way
+         */
+        private int setType;
+
+        // Defines principal set for serialization.
+        private static final int SET_Principal = 0;
+
+        // Defines private credential set for serialization.
+        private static final int SET_PrivCred = 1;
+
+        // Defines public credential set for serialization.
+        private static final int SET_PubCred = 2;
+
+        // permission required to modify set
+        private transient AuthPermission permission;
+
+        protected SecureSet(AuthPermission perm) {
+            permission = perm;
+            elements = new LinkedList<SST>();
+        }
+
+        // creates set from specified collection with specified permission
+        // all collection elements are verified before adding
+        protected SecureSet(AuthPermission perm, Collection<? extends SST> s) {
+            this(perm);
+
+            // Subject's constructor receives a Set, we can trusts if a set is from bootclasspath,
+            // and not to check whether it contains duplicates or not
+            boolean trust = s.getClass().getClassLoader() == null; 
+            
+            Iterator<? extends SST> it = s.iterator();
+            while (it.hasNext()) {
+                SST o = it.next();
+                verifyElement(o);
+                if (trust || !elements.contains(o)) {
+                    elements.add(o);
+                }
+            }
+        }
+
+        // verifies new set element
+        private void verifyElement(Object o) {
+
+            if (o == null) {
+                throw new NullPointerException();
+            }
+            if (permission == _PRINCIPALS && !(Principal.class.isAssignableFrom(o.getClass()))) {
+                throw new IllegalArgumentException(Messages.getString("auth.0B")); //$NON-NLS-1$
+            }
+        }
+
+        /*
+         * verifies specified element, checks set state, and security permission
+         * to modify set before adding new element
+         */
+        @Override
+        public boolean add(SST o) {
+
+            verifyElement(o);
+
+            checkState();
+            checkPermission(permission);
+
+            if (!elements.contains(o)) {
+                elements.add(o);
+                return true;
+            }
+            return false;
+        }
+
+        // returns an instance of SecureIterator
+        @Override
+        public Iterator<SST> iterator() {
+
+            if (permission == _PRIVATE_CREDENTIALS) {
+                /*
+                 * private credential set requires iterator with additional
+                 * security check (PrivateCredentialPermission)
+                 */
+                return new SecureIterator(elements.iterator()) {
+                    /*
+                     * checks permission to access next private credential moves
+                     * to the next element even SecurityException was thrown
+                     */
+                    @Override
+                    public SST next() {
+                        SST obj = iterator.next();
+                        checkPermission(new PrivateCredentialPermission(obj
+                                .getClass().getName(), principals));
+                        return obj;
+                    }
+                };
+            }
+            return new SecureIterator(elements.iterator());
+        }
+
+        @Override
+        public boolean retainAll(Collection<?> c) {
+
+            if (c == null) {
+                throw new NullPointerException();
+            }
+            return super.retainAll(c);
+        }
+
+        @Override
+        public int size() {
+            return elements.size();
+        }
+
+        /**
+         * return set with elements that are instances or subclasses of the
+         * specified class
+         */
+        protected final <E> Set<E> get(final Class<E> c) {
+
+            if (c == null) {
+                throw new NullPointerException();
+            }
+
+            AbstractSet<E> s = new AbstractSet<E>() {
+                private LinkedList<E> elements = new LinkedList<E>();
+
+                @Override
+                public boolean add(E o) {
+
+                    if (!c.isAssignableFrom(o.getClass())) {
+                        throw new IllegalArgumentException(
+                                Messages.getString("auth.0C", c.getName())); //$NON-NLS-1$
+                    }
+
+                    if (elements.contains(o)) {
+                        return false;
+                    }
+                    elements.add(o);
+                    return true;
+                }
+
+                @Override
+                public Iterator<E> iterator() {
+                    return elements.iterator();
+                }
+
+                @Override
+                public boolean retainAll(Collection<?> c) {
+
+                    if (c == null) {
+                        throw new NullPointerException();
+                    }
+                    return super.retainAll(c);
+                }
+
+                @Override
+                public int size() {
+                    return elements.size();
+                }
+            };
+
+            // FIXME must have permissions for requested priv. credentials
+            for (Iterator<SST> it = iterator(); it.hasNext();) {
+                SST o = it.next();
+                if (c.isAssignableFrom(o.getClass())) {
+                    s.add(c.cast(o));
+                }
+            }
+            return s;
+        }
+
+        private void readObject(ObjectInputStream in) throws IOException,
+                ClassNotFoundException {
+            in.defaultReadObject();
+
+            switch (setType) {
+            case SET_Principal:
+                permission = _PRINCIPALS;
+                break;
+            case SET_PrivCred:
+                permission = _PRIVATE_CREDENTIALS;
+                break;
+            case SET_PubCred:
+                permission = _PUBLIC_CREDENTIALS;
+                break;
+            default:
+                throw new IllegalArgumentException();
+            }
+
+            Iterator<SST> it = elements.iterator();
+            while (it.hasNext()) {
+                verifyElement(it.next());
+            }
+        }
+
+        private void writeObject(ObjectOutputStream out) throws IOException {
+
+            if (permission == _PRIVATE_CREDENTIALS) {
+                // does security check for each private credential
+                for (Iterator<SST> it = iterator(); it.hasNext();) {
+                    it.next();
+                }
+                setType = SET_PrivCred;
+            } else if (permission == _PRINCIPALS) {
+                setType = SET_Principal;
+            } else {
+                setType = SET_PubCred;
+            }
+
+            out.defaultWriteObject();
+        }
+
+        /**
+         * Represents iterator for subject's secure set
+         */
+        private class SecureIterator implements Iterator<SST> {
+            protected Iterator<SST> iterator;
+
+            protected SecureIterator(Iterator<SST> iterator) {
+                this.iterator = iterator;
+            }
+
+            public boolean hasNext() {
+                return iterator.hasNext();
+            }
+
+            public SST next() {
+                return iterator.next();
+            }
+
+            /**
+             * checks set state, and security permission to modify set before
+             * removing current element
+             */
+            public void remove() {
+                checkState();
+                checkPermission(permission);
+                iterator.remove();
+            }
+        }
+    }
+}

+ 121 - 0
src/javax/security/auth/SubjectDomainCombiner.java

@@ -0,0 +1,121 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth;
+
+import java.security.DomainCombiner;
+import java.security.Principal;
+import java.security.ProtectionDomain;
+import java.util.Set;
+
+/**
+ * Merges permissions based on code source and code signers with permissions
+ * granted to the specified {@link Subject}.
+ */
+public class SubjectDomainCombiner implements DomainCombiner {
+
+    // subject to be associated
+    private Subject subject;
+
+    // permission required to get a subject object
+    private static final AuthPermission _GET = new AuthPermission(
+            "getSubjectFromDomainCombiner"); //$NON-NLS-1$
+
+    /**
+     * Creates a domain combiner for the entity provided in {@code subject}.
+     *
+     * @param subject
+     *            the entity to which this domain combiner is associated.
+     */
+    public SubjectDomainCombiner(Subject subject) {
+        super();
+        if (subject == null) {
+            throw new NullPointerException();
+        }
+        this.subject = subject;
+    }
+
+    /**
+     * Returns the entity to which this domain combiner is associated.
+     *
+     * @return the entity to which this domain combiner is associated.
+     */
+    public Subject getSubject() {
+        SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(_GET);
+        }
+
+        return subject;
+    }
+
+    /**
+     * Merges the {@code ProtectionDomain} with the {@code Principal}s
+     * associated with the subject of this {@code SubjectDomainCombiner}.
+     *
+     * @param currentDomains
+     *            the {@code ProtectionDomain}s associated with the context of
+     *            the current thread. The domains must be sorted according to
+     *            the execution order, the most recent residing at the
+     *            beginning.
+     * @param assignedDomains
+     *            the {@code ProtectionDomain}s from the parent thread based on
+     *            code source and signers.
+     * @return a single {@code ProtectionDomain} array computed from the two
+     *         provided arrays, or {@code null}.
+     * @see ProtectionDomain
+     */
+    public ProtectionDomain[] combine(ProtectionDomain[] currentDomains,
+            ProtectionDomain[] assignedDomains) {
+        // get array length for combining protection domains
+        int len = 0;
+        if (currentDomains != null) {
+            len += currentDomains.length;
+        }
+        if (assignedDomains != null) {
+            len += assignedDomains.length;
+        }
+        if (len == 0) {
+            return null;
+        }
+
+        ProtectionDomain[] pd = new ProtectionDomain[len];
+
+        // for each current domain substitute set of principal with subject's
+        int cur = 0;
+        if (currentDomains != null) {
+
+            Set<Principal> s = subject.getPrincipals();
+            Principal[] p = s.toArray(new Principal[s.size()]);
+
+            for (cur = 0; cur < currentDomains.length; cur++) {
+                ProtectionDomain newPD;
+                newPD = new ProtectionDomain(currentDomains[cur].getCodeSource(),
+                        currentDomains[cur].getPermissions(), currentDomains[cur]
+                                .getClassLoader(), p);
+                pd[cur] = newPD;
+            }
+        }
+
+        // copy assigned domains
+        if (assignedDomains != null) {
+            System.arraycopy(assignedDomains, 0, pd, cur, assignedDomains.length);
+        }
+
+        return pd;
+    }
+}

+ 65 - 0
src/javax/security/auth/callback/.svn/all-wcprops

@@ -0,0 +1,65 @@
+K 25
+svn:wc:ra_dav:version-url
+V 121
+/repos/asf/!svn/ver/768124/harmony/enhanced/classlib/trunk/modules/auth/src/main/java/common/javax/security/auth/callback
+END
+PasswordCallback.java
+K 25
+svn:wc:ra_dav:version-url
+V 143
+/repos/asf/!svn/ver/768124/harmony/enhanced/classlib/trunk/modules/auth/src/main/java/common/javax/security/auth/callback/PasswordCallback.java
+END
+LanguageCallback.java
+K 25
+svn:wc:ra_dav:version-url
+V 143
+/repos/asf/!svn/ver/476395/harmony/enhanced/classlib/trunk/modules/auth/src/main/java/common/javax/security/auth/callback/LanguageCallback.java
+END
+TextInputCallback.java
+K 25
+svn:wc:ra_dav:version-url
+V 144
+/repos/asf/!svn/ver/476395/harmony/enhanced/classlib/trunk/modules/auth/src/main/java/common/javax/security/auth/callback/TextInputCallback.java
+END
+TextOutputCallback.java
+K 25
+svn:wc:ra_dav:version-url
+V 145
+/repos/asf/!svn/ver/476395/harmony/enhanced/classlib/trunk/modules/auth/src/main/java/common/javax/security/auth/callback/TextOutputCallback.java
+END
+UnsupportedCallbackException.java
+K 25
+svn:wc:ra_dav:version-url
+V 155
+/repos/asf/!svn/ver/768124/harmony/enhanced/classlib/trunk/modules/auth/src/main/java/common/javax/security/auth/callback/UnsupportedCallbackException.java
+END
+ConfirmationCallback.java
+K 25
+svn:wc:ra_dav:version-url
+V 147
+/repos/asf/!svn/ver/476395/harmony/enhanced/classlib/trunk/modules/auth/src/main/java/common/javax/security/auth/callback/ConfirmationCallback.java
+END
+ChoiceCallback.java
+K 25
+svn:wc:ra_dav:version-url
+V 141
+/repos/asf/!svn/ver/476395/harmony/enhanced/classlib/trunk/modules/auth/src/main/java/common/javax/security/auth/callback/ChoiceCallback.java
+END
+CallbackHandler.java
+K 25
+svn:wc:ra_dav:version-url
+V 142
+/repos/asf/!svn/ver/768124/harmony/enhanced/classlib/trunk/modules/auth/src/main/java/common/javax/security/auth/callback/CallbackHandler.java
+END
+Callback.java
+K 25
+svn:wc:ra_dav:version-url
+V 135
+/repos/asf/!svn/ver/768124/harmony/enhanced/classlib/trunk/modules/auth/src/main/java/common/javax/security/auth/callback/Callback.java
+END
+NameCallback.java
+K 25
+svn:wc:ra_dav:version-url
+V 139
+/repos/asf/!svn/ver/476395/harmony/enhanced/classlib/trunk/modules/auth/src/main/java/common/javax/security/auth/callback/NameCallback.java
+END

+ 368 - 0
src/javax/security/auth/callback/.svn/entries

@@ -0,0 +1,368 @@
+10
+
+dir
+887718
+http://svn.apache.org/repos/asf/harmony/enhanced/classlib/trunk/modules/auth/src/main/java/common/javax/security/auth/callback
+http://svn.apache.org/repos/asf
+
+
+
+2009-04-24T02:02:45.504642Z
+768124
+ndbeyer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+13f79535-47bb-0310-9956-ffa450edef68
+
+PasswordCallback.java
+file
+
+
+
+
+2009-12-06T16:26:51.000000Z
+46dcf77dbee99dd6563a5da77b40713e
+2009-04-24T02:02:45.504642Z
+768124
+ndbeyer
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+4061
+
+LanguageCallback.java
+file
+
+
+
+
+2009-12-06T16:26:51.000000Z
+f874e364f186e8338114f9b0a3db6b05
+2006-09-30T04:30:25.142282Z
+451539
+ndbeyer
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1275
+
+TextInputCallback.java
+file
+
+
+
+
+2009-12-06T16:26:51.000000Z
+bcb26569caa1743af6f2957f8496df31
+2006-09-30T04:30:25.142282Z
+451539
+ndbeyer
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+2203
+
+TextOutputCallback.java
+file
+
+
+
+
+2009-12-06T16:26:51.000000Z
+6ca37aa861c2c0e81811eadec9334e69
+2006-09-30T04:30:25.142282Z
+451539
+ndbeyer
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1870
+
+UnsupportedCallbackException.java
+file
+
+
+
+
+2009-12-06T16:26:51.000000Z
+7ee144fb1bb63a41ca523d30dca76c8d
+2009-04-24T02:02:45.504642Z
+768124
+ndbeyer
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+2080
+
+ConfirmationCallback.java
+file
+
+
+
+
+2009-12-06T16:26:51.000000Z
+5984a84a8e278f328c9272796ea4e484
+2006-09-30T04:30:25.142282Z
+451539
+ndbeyer
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+8702
+
+ChoiceCallback.java
+file
+
+
+
+
+2009-12-06T16:26:51.000000Z
+e701f291431fbbbc9f41604fefb13b1a
+2006-09-30T04:30:25.142282Z
+451539
+ndbeyer
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+3530
+
+CallbackHandler.java
+file
+
+
+
+
+2009-12-06T16:26:51.000000Z
+f2a18a37efeae78e2394008c8ba572e9
+2009-04-24T02:02:45.504642Z
+768124
+ndbeyer
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+2511
+
+Callback.java
+file
+
+
+
+
+2009-12-06T16:26:51.000000Z
+c1055b78c126f9b232c8e1ff7a07e4f8
+2009-04-24T02:02:45.504642Z
+768124
+ndbeyer
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+982
+
+NameCallback.java
+file
+
+
+
+
+2009-12-06T16:26:51.000000Z
+9dc8a626c070318c06988f03f89dc0e2
+2006-09-30T04:30:25.142282Z
+451539
+ndbeyer
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+2187
+

+ 5 - 0
src/javax/security/auth/callback/.svn/prop-base/Callback.java.svn-base

@@ -0,0 +1,5 @@
+K 13
+svn:eol-style
+V 6
+native
+END

+ 5 - 0
src/javax/security/auth/callback/.svn/prop-base/CallbackHandler.java.svn-base

@@ -0,0 +1,5 @@
+K 13
+svn:eol-style
+V 6
+native
+END

+ 5 - 0
src/javax/security/auth/callback/.svn/prop-base/ChoiceCallback.java.svn-base

@@ -0,0 +1,5 @@
+K 13
+svn:eol-style
+V 6
+native
+END

+ 5 - 0
src/javax/security/auth/callback/.svn/prop-base/ConfirmationCallback.java.svn-base

@@ -0,0 +1,5 @@
+K 13
+svn:eol-style
+V 6
+native
+END

+ 5 - 0
src/javax/security/auth/callback/.svn/prop-base/LanguageCallback.java.svn-base

@@ -0,0 +1,5 @@
+K 13
+svn:eol-style
+V 6
+native
+END

+ 5 - 0
src/javax/security/auth/callback/.svn/prop-base/NameCallback.java.svn-base

@@ -0,0 +1,5 @@
+K 13
+svn:eol-style
+V 6
+native
+END

+ 5 - 0
src/javax/security/auth/callback/.svn/prop-base/PasswordCallback.java.svn-base

@@ -0,0 +1,5 @@
+K 13
+svn:eol-style
+V 6
+native
+END

+ 5 - 0
src/javax/security/auth/callback/.svn/prop-base/TextInputCallback.java.svn-base

@@ -0,0 +1,5 @@
+K 13
+svn:eol-style
+V 6
+native
+END

+ 5 - 0
src/javax/security/auth/callback/.svn/prop-base/TextOutputCallback.java.svn-base

@@ -0,0 +1,5 @@
+K 13
+svn:eol-style
+V 6
+native
+END

+ 5 - 0
src/javax/security/auth/callback/.svn/prop-base/UnsupportedCallbackException.java.svn-base

@@ -0,0 +1,5 @@
+K 13
+svn:eol-style
+V 6
+native
+END

+ 25 - 0
src/javax/security/auth/callback/.svn/text-base/Callback.java.svn-base

@@ -0,0 +1,25 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.callback;
+
+/**
+ * Defines an empty base interface for all {@code Callback}s used during
+ * authentication.
+ */
+public interface Callback {
+}

+ 54 - 0
src/javax/security/auth/callback/.svn/text-base/CallbackHandler.java.svn-base

@@ -0,0 +1,54 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.callback;
+
+import java.io.IOException;
+
+/**
+ * Needs to be implemented by classes that want to handle authentication
+ * {@link Callback}s. A single method {@link #handle(Callback[])} must be
+ * provided that checks the type of the incoming {@code Callback}s and reacts
+ * accordingly. {@code CallbackHandler}s can be installed per application. It is
+ * also possible to configure a system-default {@code CallbackHandler} by
+ * setting the {@code auth.login.defaultCallbackHandler} property in the
+ * standard {@code security.properties} file.
+ */
+public interface CallbackHandler {
+
+    /**
+     * Handles the actual {@link Callback}. A {@code CallbackHandler} needs to
+     * implement this method. In the method, it is free to select which {@code
+     * Callback}s it actually wants to handle and in which way. For example, a
+     * console-based {@code CallbackHandler} might choose to sequentially ask
+     * the user for login and password, if it implements these {@code Callback}
+     * s, whereas a GUI-based one might open a single dialog window for both
+     * values. If a {@code CallbackHandler} is not able to handle a specific
+     * {@code Callback}, it needs to throw an
+     * {@link UnsupportedCallbackException}.
+     *
+     * @param callbacks
+     *            the array of {@code Callback}s that need handling
+     * @throws IOException
+     *             if an I/O related error occurs
+     * @throws UnsupportedCallbackException
+     *             if the {@code CallbackHandler} is not able to handle a
+     *             specific {@code Callback}
+     */
+    void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException;
+
+}

+ 109 - 0
src/javax/security/auth/callback/.svn/text-base/ChoiceCallback.java.svn-base

@@ -0,0 +1,109 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.callback;
+
+import java.io.Serializable;
+
+import org.apache.harmony.auth.internal.nls.Messages;
+
+public class ChoiceCallback implements Callback, Serializable {
+
+    private static final long serialVersionUID = -3975664071579892167L;
+
+    private int defaultChoice;
+
+    private String prompt;
+
+    private boolean multipleSelectionsAllowed;
+
+    private String[] choices;
+
+    private int[] selections;
+
+    private void setChoices(String[] choices) {
+        if (choices == null || choices.length == 0) {
+            throw new IllegalArgumentException(Messages.getString("auth.1C")); //$NON-NLS-1$
+        }
+        for (int i = 0; i < choices.length; i++) {
+            if (choices[i] == null || choices[i].length() == 0) {
+                throw new IllegalArgumentException(Messages.getString("auth.1C")); //$NON-NLS-1$
+            }
+        }
+        //FIXME: System.arraycopy(choices, 0 , new String[choices.length], 0, choices.length);
+        this.choices = choices;
+
+    }
+
+    private void setPrompt(String prompt) {
+        if (prompt == null || prompt.length() == 0) {
+            throw new IllegalArgumentException(Messages.getString("auth.14")); //$NON-NLS-1$
+        }
+        this.prompt = prompt;
+    }
+
+    private void setDefaultChoice(int defaultChoice) {
+        if (0 > defaultChoice || defaultChoice >= choices.length) {
+            throw new IllegalArgumentException(Messages.getString("auth.1D")); //$NON-NLS-1$
+        }
+        this.defaultChoice = defaultChoice;
+    }
+
+    public ChoiceCallback(String prompt, String[] choices, int defaultChoice,
+            boolean multipleSelectionsAllowed) {
+        super();
+        setPrompt(prompt);
+        setChoices(choices);
+        setDefaultChoice(defaultChoice);
+        this.multipleSelectionsAllowed = multipleSelectionsAllowed;
+    }
+
+    public boolean allowMultipleSelections() {
+        return multipleSelectionsAllowed;
+    }
+
+    public String[] getChoices() {
+        return choices;
+    }
+
+    public int getDefaultChoice() {
+        return defaultChoice;
+    }
+
+    public String getPrompt() {
+        return prompt;
+    }
+
+    public int[] getSelectedIndexes() {
+        return selections;
+    }
+
+    public void setSelectedIndex(int selection) {
+        this.selections = new int[1];
+        this.selections[0] = selection;
+    }
+
+    public void setSelectedIndexes(int[] selections) {
+        if (!multipleSelectionsAllowed) {
+            throw new UnsupportedOperationException();
+        }
+        this.selections = selections;
+        //FIXME: 
+        // this.selections = new int[selections.length]
+        //System.arraycopy(selections, 0, this.selections, 0, this.selections.length);
+    }
+}

+ 234 - 0
src/javax/security/auth/callback/.svn/text-base/ConfirmationCallback.java.svn-base

@@ -0,0 +1,234 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.callback;
+
+import java.io.Serializable;
+
+import org.apache.harmony.auth.internal.nls.Messages;
+
+public class ConfirmationCallback implements Callback, Serializable {
+
+    private static final long serialVersionUID = -9095656433782481624L;
+
+    public static final int YES = 0; // default options
+
+    public static final int NO = 1;
+
+    public static final int CANCEL = 2;
+
+    public static final int OK = 3;
+
+    public static final int YES_NO_OPTION = 0; // options type
+
+    public static final int YES_NO_CANCEL_OPTION = 1;
+
+    public static final int OK_CANCEL_OPTION = 2;
+
+    public static final int UNSPECIFIED_OPTION = -1;
+
+    public static final int INFORMATION = 0; // messages type
+
+    public static final int WARNING = 1;
+
+    public static final int ERROR = 2;
+
+    private String prompt;
+
+    private int messageType;
+
+    private int optionType = UNSPECIFIED_OPTION;
+
+    private int defaultOption;
+
+    private String[] options;
+
+    private int selection;
+
+    public ConfirmationCallback(int messageType, int optionType, int defaultOption) {
+        super();
+        if (messageType > ERROR || messageType < INFORMATION) {
+            throw new IllegalArgumentException(Messages.getString("auth.16")); //$NON-NLS-1$
+        }
+
+        switch (optionType) {
+            case YES_NO_OPTION:
+                if (defaultOption != YES && defaultOption != NO) {
+                    throw new IllegalArgumentException(Messages.getString("auth.17")); //$NON-NLS-1$
+                }
+                break;
+            case YES_NO_CANCEL_OPTION:
+                if (defaultOption != YES && defaultOption != NO && defaultOption != CANCEL) {
+                    throw new IllegalArgumentException(Messages.getString("auth.17")); //$NON-NLS-1$
+                }
+                break;
+            case OK_CANCEL_OPTION:
+                if (defaultOption != OK && defaultOption != CANCEL) {
+                    throw new IllegalArgumentException(Messages.getString("auth.17")); //$NON-NLS-1$
+                }
+                break;
+            default:
+                throw new IllegalArgumentException(Messages.getString("auth.18")); //$NON-NLS-1$
+        }
+        this.messageType = messageType;
+        this.optionType = optionType;
+        this.defaultOption = defaultOption;
+    }
+
+    public ConfirmationCallback(int messageType, String[] options, int defaultOption) {
+        super();
+        if (messageType > ERROR || messageType < INFORMATION) {
+            throw new IllegalArgumentException(Messages.getString("auth.16")); //$NON-NLS-1$
+        }
+
+        if (options == null || options.length == 0) {
+            throw new IllegalArgumentException(Messages.getString("auth.1A")); //$NON-NLS-1$
+        }
+        for (int i = 0; i < options.length; i++) {
+            if (options[i] == null || options[i].length() == 0) {
+                throw new IllegalArgumentException(Messages.getString("auth.1A")); //$NON-NLS-1$
+            }
+        }
+        if (0 > defaultOption || defaultOption >= options.length) {
+            throw new IllegalArgumentException(Messages.getString("auth.17")); //$NON-NLS-1$
+        }
+        // FIXME:System.arraycopy(options, 0 , new String[this.options.length],
+        // 0, this.options.length);
+        this.options = options;
+        this.defaultOption = defaultOption;
+        this.messageType = messageType;
+    }
+
+    public ConfirmationCallback(String prompt, int messageType, int optionType,
+            int defaultOption) {
+        super();
+        if (prompt == null || prompt.length() == 0) {
+            throw new IllegalArgumentException(Messages.getString("auth.14")); //$NON-NLS-1$
+        }
+
+        if (messageType > ERROR || messageType < INFORMATION) {
+            throw new IllegalArgumentException(Messages.getString("auth.16")); //$NON-NLS-1$
+        }
+
+        switch (optionType) {
+            case YES_NO_OPTION:
+                if (defaultOption != YES && defaultOption != NO) {
+                    throw new IllegalArgumentException(Messages.getString("auth.17")); //$NON-NLS-1$
+                }
+                break;
+            case YES_NO_CANCEL_OPTION:
+                if (defaultOption != YES && defaultOption != NO && defaultOption != CANCEL) {
+                    throw new IllegalArgumentException(Messages.getString("auth.17")); //$NON-NLS-1$
+                }
+                break;
+            case OK_CANCEL_OPTION:
+                if (defaultOption != OK && defaultOption != CANCEL) {
+                    throw new IllegalArgumentException(Messages.getString("auth.17")); //$NON-NLS-1$
+                }
+                break;
+            default:
+                throw new IllegalArgumentException(Messages.getString("auth.18")); //$NON-NLS-1$
+        }
+        this.prompt = prompt;
+        this.messageType = messageType;
+        this.optionType = optionType;
+        this.defaultOption = defaultOption;
+    }
+
+    public ConfirmationCallback(String prompt, int messageType, String[] options,
+            int defaultOption) {
+        super();
+        if (prompt == null || prompt.length() == 0) {
+            throw new IllegalArgumentException(Messages.getString("auth.14")); //$NON-NLS-1$
+        }
+
+        if (messageType > ERROR || messageType < INFORMATION) {
+            throw new IllegalArgumentException(Messages.getString("auth.16")); //$NON-NLS-1$
+        }
+
+        if (options == null || options.length == 0) {
+            throw new IllegalArgumentException(Messages.getString("auth.1A")); //$NON-NLS-1$
+        }
+        for (int i = 0; i < options.length; i++) {
+            if (options[i] == null || options[i].length() == 0) {
+                throw new IllegalArgumentException(Messages.getString("auth.1A")); //$NON-NLS-1$
+            }
+        }
+        if (0 > defaultOption || defaultOption >= options.length) {
+            throw new IllegalArgumentException(Messages.getString("auth.17")); //$NON-NLS-1$
+        }
+        // FIXME:System.arraycopy(options, 0 , new String[this.options.length],
+        // 0, this.options.length);
+        this.options = options;
+        this.defaultOption = defaultOption;
+        this.messageType = messageType;
+        this.prompt = prompt;
+    }
+
+    public String getPrompt() {
+        return prompt;
+    }
+
+    public int getMessageType() {
+        return messageType;
+    }
+
+    public int getDefaultOption() {
+        return defaultOption;
+    }
+
+    public String[] getOptions() {
+        return options;
+    }
+
+    public int getOptionType() {
+        return optionType;
+    }
+
+    public int getSelectedIndex() {
+        return selection;
+    }
+
+    public void setSelectedIndex(int selection) {
+        if (options != null) {
+            if (0 <= selection && selection <= options.length) {
+                this.selection = selection;
+            } else {
+                throw new ArrayIndexOutOfBoundsException(Messages.getString("auth.1B")); //$NON-NLS-1$
+            }
+        } else {
+            switch (optionType) {
+                case YES_NO_OPTION:
+                    if (selection != YES && selection != NO) {
+                        throw new IllegalArgumentException(Messages.getString("auth.19")); //$NON-NLS-1$
+                    }
+                    break;
+                case YES_NO_CANCEL_OPTION:
+                    if (selection != YES && selection != NO && selection != CANCEL) {
+                        throw new IllegalArgumentException(Messages.getString("auth.19")); //$NON-NLS-1$
+                    }
+                    break;
+                case OK_CANCEL_OPTION:
+                    if (selection != OK && selection != CANCEL) {
+                        throw new IllegalArgumentException(Messages.getString("auth.19")); //$NON-NLS-1$
+                    }
+                    break;
+            }
+            this.selection = selection;
+        }
+    }
+}

+ 41 - 0
src/javax/security/auth/callback/.svn/text-base/LanguageCallback.java.svn-base

@@ -0,0 +1,41 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.callback;
+
+import java.io.Serializable;
+import java.util.Locale;
+
+public class LanguageCallback implements Callback, Serializable {
+
+    private static final long serialVersionUID = 2019050433478903213L;
+
+    private Locale locale;
+
+    public LanguageCallback() {
+        super();
+    }
+
+    public Locale getLocale() {
+        return locale;
+    }
+
+    public void setLocale(Locale locale) {
+        this.locale = locale;
+    }
+
+}

+ 74 - 0
src/javax/security/auth/callback/.svn/text-base/NameCallback.java.svn-base

@@ -0,0 +1,74 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.callback;
+
+import java.io.Serializable;
+
+import org.apache.harmony.auth.internal.nls.Messages;
+
+public class NameCallback implements Callback, Serializable {
+
+    private static final long serialVersionUID = 3770938795909392253L;
+
+    private String prompt;
+
+    private String defaultName;
+
+    private String inputName;
+
+    private void setPrompt(String prompt) {
+        if (prompt == null || prompt.length() == 0) {
+            throw new IllegalArgumentException(Messages.getString("auth.14")); //$NON-NLS-1$
+        }
+        this.prompt = prompt;
+    }
+
+    private void setDefaultName(String defaultName) {
+        if (defaultName == null || defaultName.length() == 0) {
+            throw new IllegalArgumentException(Messages.getString("auth.1E")); //$NON-NLS-1$
+        }
+        this.defaultName = defaultName;
+    }
+
+    public NameCallback(String prompt) {
+        super();
+        setPrompt(prompt);
+    }
+
+    public NameCallback(String prompt, String defaultName) {
+        super();
+        setPrompt(prompt);
+        setDefaultName(defaultName);
+    }
+
+    public String getPrompt() {
+        return prompt;
+    }
+
+    public String getDefaultName() {
+        return defaultName;
+    }
+
+    public void setName(String name) {
+        this.inputName = name;
+    }
+
+    public String getName() {
+        return inputName;
+    }
+}

+ 124 - 0
src/javax/security/auth/callback/.svn/text-base/PasswordCallback.java.svn-base

@@ -0,0 +1,124 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.callback;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+import org.apache.harmony.auth.internal.nls.Messages;
+
+/**
+ * Is used in conjunction with a {@link CallbackHandler} to retrieve a password
+ * when needed.
+ */
+public class PasswordCallback implements Callback, Serializable {
+
+    private static final long serialVersionUID = 2267422647454909926L;
+
+    private String prompt;
+
+    boolean echoOn;
+
+    private char[] inputPassword;
+
+    private void setPrompt(String prompt) throws IllegalArgumentException {
+        if (prompt == null || prompt.length() == 0) {
+            throw new IllegalArgumentException(Messages.getString("auth.14")); //$NON-NLS-1$
+        }
+        this.prompt = prompt;
+    }
+
+    /**
+     * Creates a new {@code PasswordCallback} instance.
+     *
+     * @param prompt
+     *            the message that should be displayed to the user
+     * @param echoOn
+     *            determines whether the user input should be echoed
+     */
+    public PasswordCallback(String prompt, boolean echoOn) {
+        super();
+        setPrompt(prompt);
+        this.echoOn = echoOn;
+    }
+
+    /**
+     * Returns the prompt that was specified when creating this {@code
+     * PasswordCallback}
+     *
+     * @return the prompt
+     */
+    public String getPrompt() {
+        return prompt;
+    }
+
+    /**
+     * Queries whether this {@code PasswordCallback} expects user input to be
+     * echoed, which is specified during the creation of the object.
+     *
+     * @return {@code true} if (and only if) user input should be echoed
+     */
+    public boolean isEchoOn() {
+        return echoOn;
+    }
+
+    /**
+     * Sets the password. The {@link CallbackHandler} that performs the actual
+     * provisioning or input of the password needs to call this method to hand
+     * back the password to the security service that requested it.
+     *
+     * @param password
+     *            the password. A copy of this is stored, so subsequent changes
+     *            to the input array do not affect the {@code PasswordCallback}.
+     */
+    public void setPassword(char[] password) {
+        if (password == null) {
+            this.inputPassword = password;
+        } else {
+            inputPassword = new char[password.length];
+            System.arraycopy(password, 0, inputPassword, 0, inputPassword.length);
+        }
+    }
+
+    /**
+     * Returns the password. The security service that needs the password
+     * usually calls this method once the {@link CallbackHandler} has finished
+     * its work.
+     *
+     * @return the password. A copy of the internal password is created and
+     *         returned, so subsequent changes to the internal password do not
+     *         affect the result.
+     */
+    public char[] getPassword() {
+        if (inputPassword != null) {
+            char[] tmp = new char[inputPassword.length];
+            System.arraycopy(inputPassword, 0, tmp, 0, tmp.length);
+            return tmp;
+        }
+        return null;
+    }
+
+    /**
+     * Clears the password stored in this {@code PasswordCallback}.
+     */
+    public void clearPassword() {
+        if (inputPassword != null) {
+            Arrays.fill(inputPassword, '\u0000');
+        }
+    }
+}

+ 74 - 0
src/javax/security/auth/callback/.svn/text-base/TextInputCallback.java.svn-base

@@ -0,0 +1,74 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.callback;
+
+import java.io.Serializable;
+
+import org.apache.harmony.auth.internal.nls.Messages;
+
+public class TextInputCallback implements Callback, Serializable {
+
+    private static final long serialVersionUID = -8064222478852811804L;
+
+    private String defaultText;
+
+    private String prompt;
+
+    private String inputText;
+
+    private void setPrompt(String prompt) {
+        if (prompt == null || prompt.length() == 0) {
+            throw new IllegalArgumentException(Messages.getString("auth.14")); //$NON-NLS-1$
+        }
+        this.prompt = prompt;
+    }
+
+    private void setDefaultText(String defaultText) {
+        if (defaultText == null || defaultText.length() == 0) {
+            throw new IllegalArgumentException(Messages.getString("auth.15")); //$NON-NLS-1$
+        }
+        this.defaultText = defaultText;
+    }
+
+    public TextInputCallback(String prompt) {
+        super();
+        setPrompt(prompt);
+    }
+
+    public TextInputCallback(String prompt, String defaultText) {
+        super();
+        setPrompt(prompt);
+        setDefaultText(defaultText);
+    }
+
+    public String getDefaultText() {
+        return defaultText;
+    }
+
+    public String getPrompt() {
+        return prompt;
+    }
+
+    public String getText() {
+        return inputText;
+    }
+
+    public void setText(String text) {
+        this.inputText = text;
+    }
+}

+ 56 - 0
src/javax/security/auth/callback/.svn/text-base/TextOutputCallback.java.svn-base

@@ -0,0 +1,56 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.callback;
+
+import java.io.Serializable;
+
+import org.apache.harmony.auth.internal.nls.Messages;
+
+public class TextOutputCallback implements Callback, Serializable {
+
+    private static final long serialVersionUID = 1689502495511663102L;
+
+    public static final int INFORMATION = 0;
+
+    public static final int WARNING = 1;
+
+    public static final int ERROR = 2;
+
+    private String message;
+
+    private int messageType;
+
+    public TextOutputCallback(int messageType, String message) {
+        if (messageType > ERROR || messageType < INFORMATION) {
+            throw new IllegalArgumentException(Messages.getString("auth.16")); //$NON-NLS-1$
+        }
+        if (message == null || message.length() == 0) {
+            throw new IllegalArgumentException(Messages.getString("auth.1F")); //$NON-NLS-1$
+        }
+        this.messageType = messageType;
+        this.message = message;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public int getMessageType() {
+        return messageType;
+    }
+}

+ 64 - 0
src/javax/security/auth/callback/.svn/text-base/UnsupportedCallbackException.java.svn-base

@@ -0,0 +1,64 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.callback;
+
+/**
+ * Thrown when a {@link CallbackHandler} does not support a particular {@link
+ * Callback}.
+ */
+public class UnsupportedCallbackException extends Exception {
+
+    private static final long serialVersionUID = -6873556327655666839L;
+
+    private Callback callback;
+
+    /**
+     * Creates a new exception instance and initializes it with just the
+     * unsupported {@code Callback}, but no error message.
+     *
+     * @param callback
+     *            the {@code Callback}
+     */
+    public UnsupportedCallbackException(Callback callback) {
+        super();
+        this.callback = callback;
+    }
+
+    /**
+     * Creates a new exception instance and initializes it with both the
+     * unsupported {@code Callback} and an error message.
+     *
+     * @param callback
+     *            the {@code Callback}
+     * @param message
+     *            the error message
+     */
+    public UnsupportedCallbackException(Callback callback, String message) {
+        super(message);
+        this.callback = callback;
+    }
+
+    /**
+     * Returns the unsupported {@code Callback} that triggered this exception.
+     *
+     * @return the {@code Callback}
+     */
+    public Callback getCallback() {
+        return callback;
+    }
+}

+ 25 - 0
src/javax/security/auth/callback/Callback.java

@@ -0,0 +1,25 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.callback;
+
+/**
+ * Defines an empty base interface for all {@code Callback}s used during
+ * authentication.
+ */
+public interface Callback {
+}

+ 54 - 0
src/javax/security/auth/callback/CallbackHandler.java

@@ -0,0 +1,54 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.callback;
+
+import java.io.IOException;
+
+/**
+ * Needs to be implemented by classes that want to handle authentication
+ * {@link Callback}s. A single method {@link #handle(Callback[])} must be
+ * provided that checks the type of the incoming {@code Callback}s and reacts
+ * accordingly. {@code CallbackHandler}s can be installed per application. It is
+ * also possible to configure a system-default {@code CallbackHandler} by
+ * setting the {@code auth.login.defaultCallbackHandler} property in the
+ * standard {@code security.properties} file.
+ */
+public interface CallbackHandler {
+
+    /**
+     * Handles the actual {@link Callback}. A {@code CallbackHandler} needs to
+     * implement this method. In the method, it is free to select which {@code
+     * Callback}s it actually wants to handle and in which way. For example, a
+     * console-based {@code CallbackHandler} might choose to sequentially ask
+     * the user for login and password, if it implements these {@code Callback}
+     * s, whereas a GUI-based one might open a single dialog window for both
+     * values. If a {@code CallbackHandler} is not able to handle a specific
+     * {@code Callback}, it needs to throw an
+     * {@link UnsupportedCallbackException}.
+     *
+     * @param callbacks
+     *            the array of {@code Callback}s that need handling
+     * @throws IOException
+     *             if an I/O related error occurs
+     * @throws UnsupportedCallbackException
+     *             if the {@code CallbackHandler} is not able to handle a
+     *             specific {@code Callback}
+     */
+    void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException;
+
+}

+ 109 - 0
src/javax/security/auth/callback/ChoiceCallback.java

@@ -0,0 +1,109 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.callback;
+
+import java.io.Serializable;
+
+import org.apache.harmony.auth.internal.nls.Messages;
+
+public class ChoiceCallback implements Callback, Serializable {
+
+    private static final long serialVersionUID = -3975664071579892167L;
+
+    private int defaultChoice;
+
+    private String prompt;
+
+    private boolean multipleSelectionsAllowed;
+
+    private String[] choices;
+
+    private int[] selections;
+
+    private void setChoices(String[] choices) {
+        if (choices == null || choices.length == 0) {
+            throw new IllegalArgumentException(Messages.getString("auth.1C")); //$NON-NLS-1$
+        }
+        for (int i = 0; i < choices.length; i++) {
+            if (choices[i] == null || choices[i].length() == 0) {
+                throw new IllegalArgumentException(Messages.getString("auth.1C")); //$NON-NLS-1$
+            }
+        }
+        //FIXME: System.arraycopy(choices, 0 , new String[choices.length], 0, choices.length);
+        this.choices = choices;
+
+    }
+
+    private void setPrompt(String prompt) {
+        if (prompt == null || prompt.length() == 0) {
+            throw new IllegalArgumentException(Messages.getString("auth.14")); //$NON-NLS-1$
+        }
+        this.prompt = prompt;
+    }
+
+    private void setDefaultChoice(int defaultChoice) {
+        if (0 > defaultChoice || defaultChoice >= choices.length) {
+            throw new IllegalArgumentException(Messages.getString("auth.1D")); //$NON-NLS-1$
+        }
+        this.defaultChoice = defaultChoice;
+    }
+
+    public ChoiceCallback(String prompt, String[] choices, int defaultChoice,
+            boolean multipleSelectionsAllowed) {
+        super();
+        setPrompt(prompt);
+        setChoices(choices);
+        setDefaultChoice(defaultChoice);
+        this.multipleSelectionsAllowed = multipleSelectionsAllowed;
+    }
+
+    public boolean allowMultipleSelections() {
+        return multipleSelectionsAllowed;
+    }
+
+    public String[] getChoices() {
+        return choices;
+    }
+
+    public int getDefaultChoice() {
+        return defaultChoice;
+    }
+
+    public String getPrompt() {
+        return prompt;
+    }
+
+    public int[] getSelectedIndexes() {
+        return selections;
+    }
+
+    public void setSelectedIndex(int selection) {
+        this.selections = new int[1];
+        this.selections[0] = selection;
+    }
+
+    public void setSelectedIndexes(int[] selections) {
+        if (!multipleSelectionsAllowed) {
+            throw new UnsupportedOperationException();
+        }
+        this.selections = selections;
+        //FIXME: 
+        // this.selections = new int[selections.length]
+        //System.arraycopy(selections, 0, this.selections, 0, this.selections.length);
+    }
+}

+ 234 - 0
src/javax/security/auth/callback/ConfirmationCallback.java

@@ -0,0 +1,234 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.callback;
+
+import java.io.Serializable;
+
+import org.apache.harmony.auth.internal.nls.Messages;
+
+public class ConfirmationCallback implements Callback, Serializable {
+
+    private static final long serialVersionUID = -9095656433782481624L;
+
+    public static final int YES = 0; // default options
+
+    public static final int NO = 1;
+
+    public static final int CANCEL = 2;
+
+    public static final int OK = 3;
+
+    public static final int YES_NO_OPTION = 0; // options type
+
+    public static final int YES_NO_CANCEL_OPTION = 1;
+
+    public static final int OK_CANCEL_OPTION = 2;
+
+    public static final int UNSPECIFIED_OPTION = -1;
+
+    public static final int INFORMATION = 0; // messages type
+
+    public static final int WARNING = 1;
+
+    public static final int ERROR = 2;
+
+    private String prompt;
+
+    private int messageType;
+
+    private int optionType = UNSPECIFIED_OPTION;
+
+    private int defaultOption;
+
+    private String[] options;
+
+    private int selection;
+
+    public ConfirmationCallback(int messageType, int optionType, int defaultOption) {
+        super();
+        if (messageType > ERROR || messageType < INFORMATION) {
+            throw new IllegalArgumentException(Messages.getString("auth.16")); //$NON-NLS-1$
+        }
+
+        switch (optionType) {
+            case YES_NO_OPTION:
+                if (defaultOption != YES && defaultOption != NO) {
+                    throw new IllegalArgumentException(Messages.getString("auth.17")); //$NON-NLS-1$
+                }
+                break;
+            case YES_NO_CANCEL_OPTION:
+                if (defaultOption != YES && defaultOption != NO && defaultOption != CANCEL) {
+                    throw new IllegalArgumentException(Messages.getString("auth.17")); //$NON-NLS-1$
+                }
+                break;
+            case OK_CANCEL_OPTION:
+                if (defaultOption != OK && defaultOption != CANCEL) {
+                    throw new IllegalArgumentException(Messages.getString("auth.17")); //$NON-NLS-1$
+                }
+                break;
+            default:
+                throw new IllegalArgumentException(Messages.getString("auth.18")); //$NON-NLS-1$
+        }
+        this.messageType = messageType;
+        this.optionType = optionType;
+        this.defaultOption = defaultOption;
+    }
+
+    public ConfirmationCallback(int messageType, String[] options, int defaultOption) {
+        super();
+        if (messageType > ERROR || messageType < INFORMATION) {
+            throw new IllegalArgumentException(Messages.getString("auth.16")); //$NON-NLS-1$
+        }
+
+        if (options == null || options.length == 0) {
+            throw new IllegalArgumentException(Messages.getString("auth.1A")); //$NON-NLS-1$
+        }
+        for (int i = 0; i < options.length; i++) {
+            if (options[i] == null || options[i].length() == 0) {
+                throw new IllegalArgumentException(Messages.getString("auth.1A")); //$NON-NLS-1$
+            }
+        }
+        if (0 > defaultOption || defaultOption >= options.length) {
+            throw new IllegalArgumentException(Messages.getString("auth.17")); //$NON-NLS-1$
+        }
+        // FIXME:System.arraycopy(options, 0 , new String[this.options.length],
+        // 0, this.options.length);
+        this.options = options;
+        this.defaultOption = defaultOption;
+        this.messageType = messageType;
+    }
+
+    public ConfirmationCallback(String prompt, int messageType, int optionType,
+            int defaultOption) {
+        super();
+        if (prompt == null || prompt.length() == 0) {
+            throw new IllegalArgumentException(Messages.getString("auth.14")); //$NON-NLS-1$
+        }
+
+        if (messageType > ERROR || messageType < INFORMATION) {
+            throw new IllegalArgumentException(Messages.getString("auth.16")); //$NON-NLS-1$
+        }
+
+        switch (optionType) {
+            case YES_NO_OPTION:
+                if (defaultOption != YES && defaultOption != NO) {
+                    throw new IllegalArgumentException(Messages.getString("auth.17")); //$NON-NLS-1$
+                }
+                break;
+            case YES_NO_CANCEL_OPTION:
+                if (defaultOption != YES && defaultOption != NO && defaultOption != CANCEL) {
+                    throw new IllegalArgumentException(Messages.getString("auth.17")); //$NON-NLS-1$
+                }
+                break;
+            case OK_CANCEL_OPTION:
+                if (defaultOption != OK && defaultOption != CANCEL) {
+                    throw new IllegalArgumentException(Messages.getString("auth.17")); //$NON-NLS-1$
+                }
+                break;
+            default:
+                throw new IllegalArgumentException(Messages.getString("auth.18")); //$NON-NLS-1$
+        }
+        this.prompt = prompt;
+        this.messageType = messageType;
+        this.optionType = optionType;
+        this.defaultOption = defaultOption;
+    }
+
+    public ConfirmationCallback(String prompt, int messageType, String[] options,
+            int defaultOption) {
+        super();
+        if (prompt == null || prompt.length() == 0) {
+            throw new IllegalArgumentException(Messages.getString("auth.14")); //$NON-NLS-1$
+        }
+
+        if (messageType > ERROR || messageType < INFORMATION) {
+            throw new IllegalArgumentException(Messages.getString("auth.16")); //$NON-NLS-1$
+        }
+
+        if (options == null || options.length == 0) {
+            throw new IllegalArgumentException(Messages.getString("auth.1A")); //$NON-NLS-1$
+        }
+        for (int i = 0; i < options.length; i++) {
+            if (options[i] == null || options[i].length() == 0) {
+                throw new IllegalArgumentException(Messages.getString("auth.1A")); //$NON-NLS-1$
+            }
+        }
+        if (0 > defaultOption || defaultOption >= options.length) {
+            throw new IllegalArgumentException(Messages.getString("auth.17")); //$NON-NLS-1$
+        }
+        // FIXME:System.arraycopy(options, 0 , new String[this.options.length],
+        // 0, this.options.length);
+        this.options = options;
+        this.defaultOption = defaultOption;
+        this.messageType = messageType;
+        this.prompt = prompt;
+    }
+
+    public String getPrompt() {
+        return prompt;
+    }
+
+    public int getMessageType() {
+        return messageType;
+    }
+
+    public int getDefaultOption() {
+        return defaultOption;
+    }
+
+    public String[] getOptions() {
+        return options;
+    }
+
+    public int getOptionType() {
+        return optionType;
+    }
+
+    public int getSelectedIndex() {
+        return selection;
+    }
+
+    public void setSelectedIndex(int selection) {
+        if (options != null) {
+            if (0 <= selection && selection <= options.length) {
+                this.selection = selection;
+            } else {
+                throw new ArrayIndexOutOfBoundsException(Messages.getString("auth.1B")); //$NON-NLS-1$
+            }
+        } else {
+            switch (optionType) {
+                case YES_NO_OPTION:
+                    if (selection != YES && selection != NO) {
+                        throw new IllegalArgumentException(Messages.getString("auth.19")); //$NON-NLS-1$
+                    }
+                    break;
+                case YES_NO_CANCEL_OPTION:
+                    if (selection != YES && selection != NO && selection != CANCEL) {
+                        throw new IllegalArgumentException(Messages.getString("auth.19")); //$NON-NLS-1$
+                    }
+                    break;
+                case OK_CANCEL_OPTION:
+                    if (selection != OK && selection != CANCEL) {
+                        throw new IllegalArgumentException(Messages.getString("auth.19")); //$NON-NLS-1$
+                    }
+                    break;
+            }
+            this.selection = selection;
+        }
+    }
+}

+ 41 - 0
src/javax/security/auth/callback/LanguageCallback.java

@@ -0,0 +1,41 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.callback;
+
+import java.io.Serializable;
+import java.util.Locale;
+
+public class LanguageCallback implements Callback, Serializable {
+
+    private static final long serialVersionUID = 2019050433478903213L;
+
+    private Locale locale;
+
+    public LanguageCallback() {
+        super();
+    }
+
+    public Locale getLocale() {
+        return locale;
+    }
+
+    public void setLocale(Locale locale) {
+        this.locale = locale;
+    }
+
+}

+ 74 - 0
src/javax/security/auth/callback/NameCallback.java

@@ -0,0 +1,74 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.callback;
+
+import java.io.Serializable;
+
+import org.apache.harmony.auth.internal.nls.Messages;
+
+public class NameCallback implements Callback, Serializable {
+
+    private static final long serialVersionUID = 3770938795909392253L;
+
+    private String prompt;
+
+    private String defaultName;
+
+    private String inputName;
+
+    private void setPrompt(String prompt) {
+        if (prompt == null || prompt.length() == 0) {
+            throw new IllegalArgumentException(Messages.getString("auth.14")); //$NON-NLS-1$
+        }
+        this.prompt = prompt;
+    }
+
+    private void setDefaultName(String defaultName) {
+        if (defaultName == null || defaultName.length() == 0) {
+            throw new IllegalArgumentException(Messages.getString("auth.1E")); //$NON-NLS-1$
+        }
+        this.defaultName = defaultName;
+    }
+
+    public NameCallback(String prompt) {
+        super();
+        setPrompt(prompt);
+    }
+
+    public NameCallback(String prompt, String defaultName) {
+        super();
+        setPrompt(prompt);
+        setDefaultName(defaultName);
+    }
+
+    public String getPrompt() {
+        return prompt;
+    }
+
+    public String getDefaultName() {
+        return defaultName;
+    }
+
+    public void setName(String name) {
+        this.inputName = name;
+    }
+
+    public String getName() {
+        return inputName;
+    }
+}

+ 124 - 0
src/javax/security/auth/callback/PasswordCallback.java

@@ -0,0 +1,124 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.callback;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+import org.apache.harmony.auth.internal.nls.Messages;
+
+/**
+ * Is used in conjunction with a {@link CallbackHandler} to retrieve a password
+ * when needed.
+ */
+public class PasswordCallback implements Callback, Serializable {
+
+    private static final long serialVersionUID = 2267422647454909926L;
+
+    private String prompt;
+
+    boolean echoOn;
+
+    private char[] inputPassword;
+
+    private void setPrompt(String prompt) throws IllegalArgumentException {
+        if (prompt == null || prompt.length() == 0) {
+            throw new IllegalArgumentException(Messages.getString("auth.14")); //$NON-NLS-1$
+        }
+        this.prompt = prompt;
+    }
+
+    /**
+     * Creates a new {@code PasswordCallback} instance.
+     *
+     * @param prompt
+     *            the message that should be displayed to the user
+     * @param echoOn
+     *            determines whether the user input should be echoed
+     */
+    public PasswordCallback(String prompt, boolean echoOn) {
+        super();
+        setPrompt(prompt);
+        this.echoOn = echoOn;
+    }
+
+    /**
+     * Returns the prompt that was specified when creating this {@code
+     * PasswordCallback}
+     *
+     * @return the prompt
+     */
+    public String getPrompt() {
+        return prompt;
+    }
+
+    /**
+     * Queries whether this {@code PasswordCallback} expects user input to be
+     * echoed, which is specified during the creation of the object.
+     *
+     * @return {@code true} if (and only if) user input should be echoed
+     */
+    public boolean isEchoOn() {
+        return echoOn;
+    }
+
+    /**
+     * Sets the password. The {@link CallbackHandler} that performs the actual
+     * provisioning or input of the password needs to call this method to hand
+     * back the password to the security service that requested it.
+     *
+     * @param password
+     *            the password. A copy of this is stored, so subsequent changes
+     *            to the input array do not affect the {@code PasswordCallback}.
+     */
+    public void setPassword(char[] password) {
+        if (password == null) {
+            this.inputPassword = password;
+        } else {
+            inputPassword = new char[password.length];
+            System.arraycopy(password, 0, inputPassword, 0, inputPassword.length);
+        }
+    }
+
+    /**
+     * Returns the password. The security service that needs the password
+     * usually calls this method once the {@link CallbackHandler} has finished
+     * its work.
+     *
+     * @return the password. A copy of the internal password is created and
+     *         returned, so subsequent changes to the internal password do not
+     *         affect the result.
+     */
+    public char[] getPassword() {
+        if (inputPassword != null) {
+            char[] tmp = new char[inputPassword.length];
+            System.arraycopy(inputPassword, 0, tmp, 0, tmp.length);
+            return tmp;
+        }
+        return null;
+    }
+
+    /**
+     * Clears the password stored in this {@code PasswordCallback}.
+     */
+    public void clearPassword() {
+        if (inputPassword != null) {
+            Arrays.fill(inputPassword, '\u0000');
+        }
+    }
+}

+ 74 - 0
src/javax/security/auth/callback/TextInputCallback.java

@@ -0,0 +1,74 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.callback;
+
+import java.io.Serializable;
+
+import org.apache.harmony.auth.internal.nls.Messages;
+
+public class TextInputCallback implements Callback, Serializable {
+
+    private static final long serialVersionUID = -8064222478852811804L;
+
+    private String defaultText;
+
+    private String prompt;
+
+    private String inputText;
+
+    private void setPrompt(String prompt) {
+        if (prompt == null || prompt.length() == 0) {
+            throw new IllegalArgumentException(Messages.getString("auth.14")); //$NON-NLS-1$
+        }
+        this.prompt = prompt;
+    }
+
+    private void setDefaultText(String defaultText) {
+        if (defaultText == null || defaultText.length() == 0) {
+            throw new IllegalArgumentException(Messages.getString("auth.15")); //$NON-NLS-1$
+        }
+        this.defaultText = defaultText;
+    }
+
+    public TextInputCallback(String prompt) {
+        super();
+        setPrompt(prompt);
+    }
+
+    public TextInputCallback(String prompt, String defaultText) {
+        super();
+        setPrompt(prompt);
+        setDefaultText(defaultText);
+    }
+
+    public String getDefaultText() {
+        return defaultText;
+    }
+
+    public String getPrompt() {
+        return prompt;
+    }
+
+    public String getText() {
+        return inputText;
+    }
+
+    public void setText(String text) {
+        this.inputText = text;
+    }
+}

+ 56 - 0
src/javax/security/auth/callback/TextOutputCallback.java

@@ -0,0 +1,56 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.callback;
+
+import java.io.Serializable;
+
+import org.apache.harmony.auth.internal.nls.Messages;
+
+public class TextOutputCallback implements Callback, Serializable {
+
+    private static final long serialVersionUID = 1689502495511663102L;
+
+    public static final int INFORMATION = 0;
+
+    public static final int WARNING = 1;
+
+    public static final int ERROR = 2;
+
+    private String message;
+
+    private int messageType;
+
+    public TextOutputCallback(int messageType, String message) {
+        if (messageType > ERROR || messageType < INFORMATION) {
+            throw new IllegalArgumentException(Messages.getString("auth.16")); //$NON-NLS-1$
+        }
+        if (message == null || message.length() == 0) {
+            throw new IllegalArgumentException(Messages.getString("auth.1F")); //$NON-NLS-1$
+        }
+        this.messageType = messageType;
+        this.message = message;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public int getMessageType() {
+        return messageType;
+    }
+}

+ 64 - 0
src/javax/security/auth/callback/UnsupportedCallbackException.java

@@ -0,0 +1,64 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.callback;
+
+/**
+ * Thrown when a {@link CallbackHandler} does not support a particular {@link
+ * Callback}.
+ */
+public class UnsupportedCallbackException extends Exception {
+
+    private static final long serialVersionUID = -6873556327655666839L;
+
+    private Callback callback;
+
+    /**
+     * Creates a new exception instance and initializes it with just the
+     * unsupported {@code Callback}, but no error message.
+     *
+     * @param callback
+     *            the {@code Callback}
+     */
+    public UnsupportedCallbackException(Callback callback) {
+        super();
+        this.callback = callback;
+    }
+
+    /**
+     * Creates a new exception instance and initializes it with both the
+     * unsupported {@code Callback} and an error message.
+     *
+     * @param callback
+     *            the {@code Callback}
+     * @param message
+     *            the error message
+     */
+    public UnsupportedCallbackException(Callback callback, String message) {
+        super(message);
+        this.callback = callback;
+    }
+
+    /**
+     * Returns the unsupported {@code Callback} that triggered this exception.
+     *
+     * @return the {@code Callback}
+     */
+    public Callback getCallback() {
+        return callback;
+    }
+}

+ 100 - 0
src/javax/security/auth/kerberos/DelegationPermission.java

@@ -0,0 +1,100 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.kerberos;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.security.BasicPermission;
+import java.security.Permission;
+import java.security.PermissionCollection;
+
+import org.apache.harmony.auth.internal.nls.Messages;
+
+public final class DelegationPermission extends BasicPermission implements Serializable {
+
+    private static final long serialVersionUID = 883133252142523922L;
+
+    // initialization of a target name
+    private static String init(String name) {
+
+        String trName = name.trim();
+
+        int length = trName.length();
+        // length MUST be at least 7 characters
+        if (length < 7) {
+            throw new IllegalArgumentException(Messages.getString("auth.20")); //$NON-NLS-1$
+
+        }
+
+        int index = name.indexOf('"', 2);
+
+        if (trName.charAt(0) != '"' || index == -1 || (index + 6) > trName.length()
+                || trName.charAt(index + 1) != ' ' || trName.charAt(index + 2) != '"'
+                || trName.charAt(trName.length() - 1) != '"') {
+            throw new IllegalArgumentException(Messages.getString("auth.20")); //$NON-NLS-1$
+        }
+        return trName;
+    }
+
+    public DelegationPermission(String principals) {
+        super(init(principals));
+    }
+
+    public DelegationPermission(String principals, String action) {
+        super(init(principals), action);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+
+        if (obj == null || obj.getClass() != this.getClass()) {
+            return false;
+        }
+
+        return this.getName().equals(((DelegationPermission) obj).getName());
+    }
+
+    @Override
+    public boolean implies(Permission permission) {
+        return equals(permission);
+    }
+
+    @Override
+    public int hashCode() {
+        return getName().hashCode();
+    }
+
+    @Override
+    public PermissionCollection newPermissionCollection() {
+        return new KrbDelegationPermissionCollection();
+    }
+
+    private void writeObject(ObjectOutputStream s) throws IOException, ClassNotFoundException {
+        s.defaultWriteObject();
+    }
+
+    private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
+        s.defaultReadObject();
+        init(getName());
+    }
+}

+ 120 - 0
src/javax/security/auth/kerberos/KerberosKey.java

@@ -0,0 +1,120 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.kerberos;
+
+import javax.crypto.SecretKey;
+import javax.security.auth.DestroyFailedException;
+import javax.security.auth.Destroyable;
+
+import org.apache.harmony.auth.internal.nls.Messages;
+
+/**
+ * See <a href="http://www.ietf.org/rfc/rfc3961.txt">RFC3961</a>
+ */
+public class KerberosKey implements SecretKey, Destroyable {
+
+    private static final long serialVersionUID = -4625402278148246993L;
+
+    //principal    
+    private KerberosPrincipal principal;
+
+    //key version number
+    private int versionNum;
+
+    //raw bytes for the secret key
+    private KeyImpl key;
+
+    // indicates the ticket state
+    private transient boolean destroyed;
+
+    public KerberosKey(KerberosPrincipal principal, byte[] keyBytes, int keyType,
+            int versionNumber) {
+
+        if (keyBytes == null) {
+            throw new NullPointerException(Messages.getString("auth.47")); //$NON-NLS-1$
+        }
+
+        this.principal = principal;
+        this.versionNum = versionNumber;
+
+        this.key = new KeyImpl(keyBytes, keyType);
+
+    }
+
+    public KerberosKey(KerberosPrincipal principal, char[] password, String algorithm) {
+
+        this.principal = principal;
+
+        this.key = new KeyImpl(principal, password, algorithm);
+    }
+
+    public final KerberosPrincipal getPrincipal() {
+        checkState();
+        return principal;
+    }
+
+    public final String getAlgorithm() {
+        return key.getAlgorithm();
+    }
+
+    public final String getFormat() {
+        return key.getFormat();
+    }
+
+    public final int getKeyType() {
+        return key.getKeyType();
+    }
+
+    public final byte[] getEncoded() {
+        return key.getEncoded();
+    }
+
+    public final int getVersionNumber() {
+        checkState();
+        return versionNum;
+    }
+
+    public void destroy() throws DestroyFailedException {
+        if (!destroyed) {
+            this.principal = null;
+            key.destroy();
+            this.destroyed = true;
+        }
+    }
+
+    public boolean isDestroyed() {
+        return destroyed;
+    }
+
+    @Override
+    public String toString() {
+        checkState();
+        StringBuilder sb = new StringBuilder();
+        sb.append("KerberosPrincipal ").append(principal.getName()).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
+        sb.append("KeyVersion ").append(versionNum).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
+        sb.append(key.toString());
+        return sb.toString();
+    }
+
+    // if a key is destroyed then IllegalStateException must be thrown 
+    private void checkState() {
+        if (destroyed) {
+            throw new IllegalStateException(Messages.getString("auth.48")); //$NON-NLS-1$
+        }
+    }
+}

+ 167 - 0
src/javax/security/auth/kerberos/KerberosPrincipal.java

@@ -0,0 +1,167 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.kerberos;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.security.Principal;
+
+import org.apache.harmony.auth.internal.kerberos.v5.KerberosException;
+import org.apache.harmony.auth.internal.kerberos.v5.KrbClient;
+import org.apache.harmony.auth.internal.kerberos.v5.PrincipalName;
+import org.apache.harmony.auth.internal.nls.Messages;
+import org.apache.harmony.security.asn1.ASN1StringType;
+
+public final class KerberosPrincipal implements Principal, Serializable {
+
+    private static final long serialVersionUID = -7374788026156829911L;
+
+    public static final int KRB_NT_UNKNOWN = 0;
+
+    public static final int KRB_NT_PRINCIPAL = 1;
+
+    public static final int KRB_NT_SRV_INST = 2;
+
+    public static final int KRB_NT_SRV_HST = 3;
+
+    public static final int KRB_NT_SRV_XHST = 4;
+
+    public static final int KRB_NT_UID = 5;
+
+    // the full name of principal
+    private transient PrincipalName name;
+
+    // the realm
+    private transient String realm;
+
+    // "principal" @ "realm"
+    private transient String strName;
+
+    private void init(int type, String name) {
+
+        // FIXME: correctly implement parsing name according to RFC 1964
+        // http://www.ietf.org/rfc/rfc1964.txt
+        if (name == null || name.trim().length() == 0) {
+            throw new IllegalArgumentException(Messages.getString("auth.23")); //$NON-NLS-1$
+        }
+
+        int pos = name.indexOf('@');
+        if (pos != -1) {
+            realm = name.substring(pos + 1, name.length());
+
+            // verify realm name according to RFC 1964(2.1.1 (2))
+            // check invalid chars '/', ':' and null
+            if (realm.indexOf('/') != -1 || realm.indexOf(':') != -1
+                    || realm.indexOf(0) != -1) {
+                throw new IllegalArgumentException(Messages
+                        .getString("auth.24")); //$NON-NLS-1$
+            }
+
+            name = name.substring(0, pos);
+        } else {
+            // look for default realm name
+            try {
+                realm = KrbClient.getRealm();
+            } catch (KerberosException e) {
+                throw new IllegalArgumentException(e);
+            }
+        }
+        this.name = new PrincipalName(type, name);
+    }
+
+    public KerberosPrincipal(String name) {
+        init(KRB_NT_PRINCIPAL, name);
+    }
+
+    public KerberosPrincipal(String name, int type) {
+        init(type, name);
+        if (type < 0 || type > KRB_NT_UID) {
+            throw new IllegalArgumentException(Messages.getString("auth.25")); //$NON-NLS-1$
+        }
+    }
+
+    public String getName() {
+        if (strName == null) {
+            if (realm == null) {
+                strName = name.getCanonicalName();
+            } else {
+                strName = name.getCanonicalName() + '@' + realm;
+            }
+        }
+        return strName;
+    }
+
+    public String getRealm() {
+        return realm;
+    }
+
+    public int getNameType() {
+        return name.getType();
+    }
+
+    @Override
+    public int hashCode() {
+        return getName().hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof KerberosPrincipal)) {
+            return false;
+        }
+
+        KerberosPrincipal that = (KerberosPrincipal) obj;
+
+        if (realm == null) {
+            return that.realm == null;
+        } else if (!realm.equals(that.realm)) {
+            return false;
+        }
+        return name.equals(that.name);
+    }
+
+    @Override
+    public String toString() {
+        return getName();
+    }
+
+    private void readObject(ObjectInputStream s) throws IOException,
+            ClassNotFoundException {
+
+        s.defaultReadObject();
+
+        name = PrincipalName.instanceOf((byte[]) s.readObject());
+        realm = (String) ASN1StringType.GENERALSTRING.decode((byte[]) s
+                .readObject());
+
+        //FIXME: verify serialized values
+    }
+
+    private void writeObject(ObjectOutputStream s) throws IOException {
+
+        s.defaultWriteObject();
+
+        s.writeObject(name.getEncoded());
+        s.writeObject(ASN1StringType.GENERALSTRING.encode(realm));
+    }
+}

+ 374 - 0
src/javax/security/auth/kerberos/KerberosTicket.java

@@ -0,0 +1,374 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.kerberos;
+
+import java.io.Serializable;
+import java.net.InetAddress;
+import java.util.Arrays;
+import java.util.Date;
+
+import javax.crypto.SecretKey;
+import javax.security.auth.DestroyFailedException;
+import javax.security.auth.Destroyable;
+import javax.security.auth.RefreshFailedException;
+import javax.security.auth.Refreshable;
+
+import org.apache.harmony.auth.internal.kerberos.v5.KerberosException;
+import org.apache.harmony.auth.internal.kerberos.v5.KrbClient;
+import org.apache.harmony.auth.internal.nls.Messages;
+import org.apache.harmony.security.utils.Array;
+
+public class KerberosTicket implements Destroyable, Refreshable, Serializable {
+
+    private static final long serialVersionUID = 7395334370157380539L;
+
+    // The description of these flags defines in the Kerberos Protocol Specification (RFC 1510).
+
+    // FORWARDABLE flag 
+    private static final int FORWARDABLE = 1;
+
+    // FORWARDED flag
+    private static final int FORWARDED = 2;
+
+    // PROXIABLE flag
+    private static final int PROXIABLE = 3;
+
+    // PROXY flag
+    private static final int PROXY = 4;
+
+    // POSTDATED flag
+    private static final int POSTDATED = 6;
+
+    // RENEWABLE flag
+    private static final int RENEWABLE = 8;
+
+    // INITIAL flag
+    private static final int INITIAL = 9;
+
+    // number of flags used by Kerberos protocol
+    private static final int FLAGS_NUM = 32;
+
+    // line feed 
+    private static final String LF = "\n"; //$NON-NLS-1$
+
+    //ASN.1 encoding of the ticket
+    private byte[] asn1Encoding;
+
+    //raw bytes for the session key
+    private KeyImpl sessionKey;
+
+    //ticket flags
+    private boolean[] flags;
+
+    //time of initial authentication for the client
+    private Date authTime;
+
+    //time after which the ticket will be valid
+    private Date startTime;
+
+    // time after which the ticket will be invalid
+    private Date endTime;
+
+    // expiration time for the ticket
+    private Date renewTill;
+
+    // client that owns this ticket
+    private KerberosPrincipal client;
+
+    //service that owns this ticket
+    private KerberosPrincipal server;
+
+    //addresses from where the ticket may be used by the client
+    private InetAddress[] clientAddresses;
+
+    // indicates the ticket state
+    private transient boolean destroyed;
+
+    public KerberosTicket(byte[] asn1Encoding, KerberosPrincipal client,
+            KerberosPrincipal server, byte[] keyBytes, int keyType, boolean[] flags,
+            Date authTime, Date startTime, Date endTime, Date renewTill,
+            InetAddress[] clientAddresses) {
+
+        if (asn1Encoding == null) {
+            throw new IllegalArgumentException(Messages.getString("auth.3B")); //$NON-NLS-1$
+        }
+        if (client == null) {
+            throw new IllegalArgumentException(Messages.getString("auth.3C")); //$NON-NLS-1$
+        }
+
+        if (server == null) {
+            throw new IllegalArgumentException(Messages.getString("auth.3D")); //$NON-NLS-1$
+        }
+
+        if (keyBytes == null) {
+            throw new IllegalArgumentException(Messages.getString("auth.3E")); //$NON-NLS-1$
+        }
+
+        if (authTime == null) {
+            throw new IllegalArgumentException(Messages.getString("auth.3F")); //$NON-NLS-1$
+        }
+
+        if (endTime == null) {
+            throw new IllegalArgumentException(Messages.getString("auth.40")); //$NON-NLS-1$
+        }
+
+        this.asn1Encoding = new byte[asn1Encoding.length];
+        System.arraycopy(asn1Encoding, 0, this.asn1Encoding, 0, this.asn1Encoding.length);
+
+        this.client = client;
+        this.server = server;
+        this.sessionKey = new KeyImpl(keyBytes, keyType);
+
+        if (flags == null) {
+            this.flags = new boolean[FLAGS_NUM];
+        } else if (flags.length > FLAGS_NUM) {
+            this.flags = new boolean[flags.length];
+            System.arraycopy(flags, 0, this.flags, 0, this.flags.length);
+        } else {
+            this.flags = new boolean[FLAGS_NUM];
+            System.arraycopy(flags, 0, this.flags, 0, flags.length);
+        }
+
+        if (this.flags[RENEWABLE] && renewTill == null) {
+            throw new IllegalArgumentException(Messages.getString("auth.41")); //$NON-NLS-1$
+        }
+
+        this.renewTill = renewTill;
+
+        if (startTime != null) {
+            this.startTime = startTime;
+        } else {
+            this.startTime = authTime;
+        }
+
+        if (this.startTime.getTime() > endTime.getTime()) {
+            // TODO: make correct description of the exception  
+            throw new IllegalArgumentException(Messages.getString("auth.42")); //$NON-NLS-1$
+        }
+
+        this.authTime = authTime;
+        this.endTime = endTime;
+
+        if (clientAddresses != null) {
+            this.clientAddresses = new InetAddress[clientAddresses.length];
+            System.arraycopy(clientAddresses, 0, this.clientAddresses, 0,
+                    this.clientAddresses.length);
+        }
+
+    }
+
+    public final KerberosPrincipal getClient() {
+        return client;
+    }
+
+    public final KerberosPrincipal getServer() {
+        return server;
+    }
+
+    public final SecretKey getSessionKey() {
+        checkState();
+        return sessionKey;
+    }
+
+    public final int getSessionKeyType() {
+        checkState();
+        return sessionKey.getKeyType();
+    }
+
+    public final byte[] getEncoded() {
+        checkState();
+        byte[] tmp = new byte[this.asn1Encoding.length];
+        System.arraycopy(this.asn1Encoding, 0, tmp, 0, tmp.length);
+        return tmp;
+    }
+
+    public final boolean isForwardable() {
+        checkState();
+        return flags[FORWARDABLE];
+    }
+
+    public final boolean isForwarded() {
+        checkState();
+        //TODO: was based on authentication involving a forwarded TGT ?
+        return flags[FORWARDED];
+    }
+
+    public final boolean isProxiable() {
+        checkState();
+        return flags[PROXIABLE];
+    }
+
+    public final boolean isProxy() {
+        checkState();
+        return flags[PROXY];
+    }
+
+    public final boolean isPostdated() {
+        checkState();
+        return flags[POSTDATED];
+    }
+
+    public final boolean isRenewable() {
+        checkState();
+        return flags[RENEWABLE];
+    }
+
+    public final boolean isInitial() {
+        checkState();
+        return flags[INITIAL];
+    }
+
+    public final boolean[] getFlags() {
+        if (destroyed) {
+            return null;
+        }
+        boolean[] tmp = new boolean[flags.length];
+        System.arraycopy(flags, 0, tmp, 0, tmp.length);
+        return tmp;
+
+    }
+
+    public final Date getAuthTime() {
+        if (destroyed) {
+            return null;
+        }
+        return new Date(authTime.getTime());
+    }
+
+    public final Date getStartTime() {
+        checkState();
+        return new Date(startTime.getTime());
+    }
+
+    public final Date getEndTime() {
+        if (destroyed) {
+            return null;
+        }
+        return new Date(endTime.getTime());
+    }
+
+    public final Date getRenewTill() {
+        if (destroyed) {
+            return null;
+        }
+        return renewTill;
+    }
+
+    public final InetAddress[] getClientAddresses() {
+        if (this.clientAddresses != null) {
+            InetAddress[] tmp = new InetAddress[this.clientAddresses.length];
+            System.arraycopy(clientAddresses, 0, tmp, 0, tmp.length);
+            return tmp;
+        }
+        return null;
+    }
+
+    public void destroy() throws DestroyFailedException {
+        if (destroyed) {
+            return;
+        }
+        Arrays.fill(this.asn1Encoding, (byte) 0);
+        this.client = null;
+        this.server = null;
+        this.sessionKey.destroy();
+        this.flags = null;
+        this.authTime = null;
+        this.startTime = null;
+        this.endTime = null;
+        this.renewTill = null;
+        this.clientAddresses = null;
+        destroyed = true;
+    }
+
+    public boolean isDestroyed() {
+        return destroyed;
+    }
+
+    public void refresh() throws RefreshFailedException {
+
+        checkState();
+
+        if (!flags[RENEWABLE]) {
+            throw new RefreshFailedException(Messages.getString("auth.44")); //$NON-NLS-1$
+        }
+
+        if (System.currentTimeMillis() > this.renewTill.getTime()) {
+            throw new RefreshFailedException(Messages.getString("auth.45")); //$NON-NLS-1$
+        }
+
+        try {
+            KrbClient.doTGS();
+        } catch (KerberosException e) {
+            throw new RefreshFailedException(e.getMessage());
+        }
+    }
+
+    public boolean isCurrent() {
+        checkState();
+        if (this.getStartTime().getTime() <= System.currentTimeMillis()
+                && System.currentTimeMillis() <= this.getEndTime().getTime()) {
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        checkState();
+        StringBuilder sb = new StringBuilder();
+        sb.append("Ticket = ").append(Array.toString(asn1Encoding, "(hex) ") + LF); //$NON-NLS-1$ //$NON-NLS-2$
+        sb.append("Client Principal = ").append(client.getName() + LF); //$NON-NLS-1$
+        sb.append("Server Principal = ").append(server.getName() + LF); //$NON-NLS-1$
+        //TODO: append session key
+        sb.append("Session Key = ").append(sessionKey.toString() + LF); //$NON-NLS-1$
+        sb.append("Forwardable Ticket = ").append(flags[FORWARDABLE] + LF); //$NON-NLS-1$
+        sb.append("Forwarded Ticket = ").append(flags[FORWARDED] + LF); //$NON-NLS-1$
+        sb.append("Proxiable Ticket = ").append(flags[PROXIABLE] + LF); //$NON-NLS-1$
+        sb.append("Proxy Ticket = ").append(flags[PROXY] + LF); //$NON-NLS-1$
+        sb.append("Postdated Ticket = ").append(flags[POSTDATED] + LF); //$NON-NLS-1$
+        sb.append("Renewable Ticket = ").append(flags[RENEWABLE] + LF); //$NON-NLS-1$
+        sb.append("Initial Ticket = ").append(flags[INITIAL] + LF); //$NON-NLS-1$
+        sb.append("Auth Time = ").append(this.authTime.toString() + LF); //$NON-NLS-1$
+        sb.append("Start Time = ").append(this.startTime.toString() + LF); //$NON-NLS-1$
+        sb.append("End Time = ").append(this.endTime.toString() + LF); //$NON-NLS-1$
+        sb.append("Renew Till = ").append(this.renewTill.toString() + LF); //$NON-NLS-1$
+        sb.append("Client Addresses "); //$NON-NLS-1$
+        if (clientAddresses != null) {
+            for (int i = 0; i < clientAddresses.length; i++) {
+                if (clientAddresses[i] == null) {
+                    throw new NullPointerException(Messages.getString("auth.46")); //$NON-NLS-1$
+                }
+                sb
+                        .append("clientAddresses[" + i + "] = ").append(clientAddresses[i].toString() + LF + "\t\t"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+            }
+        } else {
+            sb.append("null"); //$NON-NLS-1$
+        }
+
+        return sb.toString();
+    }
+
+    /** 
+     * if a key is destroyed then IllegalStateException must be thrown 
+     */
+    private void checkState() {
+        if (destroyed) {
+            throw new IllegalStateException(Messages.getString("auth.43")); //$NON-NLS-1$
+        }
+    }
+}

+ 320 - 0
src/javax/security/auth/kerberos/KeyImpl.java

@@ -0,0 +1,320 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.kerberos;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.Arrays;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import javax.security.auth.DestroyFailedException;
+import javax.security.auth.Destroyable;
+
+import org.apache.harmony.auth.internal.kerberos.v5.EncryptionKey;
+import org.apache.harmony.auth.internal.nls.Messages;
+import org.apache.harmony.security.utils.Array;
+
+/**
+ * This class encapsulates a Kerberos encryption key.
+ * 
+ */
+class KeyImpl implements SecretKey, Destroyable, Serializable {
+
+    private static final long serialVersionUID = -7889313790214321193L;
+    
+    private transient byte[] keyBytes;
+
+    private transient int keyType;
+    
+    //  indicates the ticket state
+    private transient boolean destroyed;
+
+    // Pre-calculated parity values 
+    // TODO the alternative for boolean table - any acceptable algorithm?
+    private final static boolean[] PARITY = new boolean[] { false, true, true,
+            false, true, false, false, true, true, false, false, true, false,
+            true, true, false, true, false, false, true, false, true, true,
+            false, false, true, true, false, true, false, false, true, true,
+            false, false, true, false, true, true, false, false, true, true,
+            false, true, false, false, true, false, true, true, false, true,
+            false, false, true, true, false, false, true, false, true, true,
+            false, true, false, false, true, false, true, true, false, false,
+            true, true, false, true, false, false, true, false, true, true,
+            false, true, false, false, true, true, false, false, true, false,
+            true, true, false, false, true, true, false, true, false, false,
+            true, true, false, false, true, false, true, true, false, true,
+            false, false, true, false, true, true, false, false, true, true,
+            false, true, false, false, true, true, false, false, true, false,
+            true, true, false, false, true, true, false, true, false, false,
+            true, false, true, true, false, true, false, false, true, true,
+            false, false, true, false, true, true, false, false, true, true,
+            false, true, false, false, true, true, false, false, true, false,
+            true, true, false, true, false, false, true, false, true, true,
+            false, false, true, true, false, true, false, false, true, false,
+            true, true, false, true, false, false, true, true, false, false,
+            true, false, true, true, false, true, false, false, true, false,
+            true, true, false, false, true, true, false, true, false, false,
+            true, true, false, false, true, false, true, true, false, false,
+            true, true, false, true, false, false, true, false, true, true,
+            false, true, false, false, true, true, false, false, true, false,
+            true, true, false };
+
+    // Pre-calculated reversed values 
+    // TODO any acceptable alternative algorithm instead of table?
+    private static final byte[] REVERSE = new byte[] { 0, 64, 32, 96, 16, 80,
+            48, 112, 8, 72, 40, 104, 24, 88, 56, 120, 4, 68, 36, 100, 20, 84,
+            52, 116, 12, 76, 44, 108, 28, 92, 60, 124, 2, 66, 34, 98, 18, 82,
+            50, 114, 10, 74, 42, 106, 26, 90, 58, 122, 6, 70, 38, 102, 22, 86,
+            54, 118, 14, 78, 46, 110, 30, 94, 62, 126, 1, 65, 33, 97, 17, 81,
+            49, 113, 9, 73, 41, 105, 25, 89, 57, 121, 5, 69, 37, 101, 21, 85,
+            53, 117, 13, 77, 45, 109, 29, 93, 61, 125, 3, 67, 35, 99, 19, 83,
+            51, 115, 11, 75, 43, 107, 27, 91, 59, 123, 7, 71, 39, 103, 23, 87,
+            55, 119, 15, 79, 47, 111, 31, 95, 63, 127 };
+
+    /**
+     * creates a secret key from a given raw bytes
+     * 
+     * @param keyBytes
+     * @param keyType
+     */
+    public KeyImpl(byte[] keyBytes, int keyType) {
+        this.keyBytes = new byte[keyBytes.length];
+        System.arraycopy(keyBytes , 0, this.keyBytes, 0, this.keyBytes.length); 
+        this.keyType = keyType;
+    }
+    /**
+     * creates a secret key from a given password
+     * 
+     * @param principal
+     * @param password
+     * @param algorithm
+     */
+    public KeyImpl(KerberosPrincipal principal, char[] password, String algorithm) {
+
+        //
+        // See http://www.ietf.org/rfc/rfc3961.txt for algorithm description
+        //
+        
+        if (principal == null || password == null) {
+            throw new NullPointerException();
+        }
+
+        if (algorithm != null && "DES".compareTo(algorithm) != 0) { //$NON-NLS-1$
+            throw new IllegalArgumentException(Messages.getString("auth.49")); //$NON-NLS-1$
+        }
+
+        keyType = 3; // DES algorithm
+        keyBytes = new byte[8];
+        
+        String realm = principal.getRealm();
+        String pname = principal.getName();
+
+        StringBuilder buf = new StringBuilder();
+        buf.append(password);
+        buf.append(realm);
+        buf.append(pname.substring(0, pname.length() - realm.length() - 1));
+
+        byte[] tmp = org.apache.harmony.luni.util.Util.getUTF8Bytes(buf
+                .toString());
+
+        // pad with 0x00 to 8 byte boundary
+        byte[] raw = new byte[tmp.length
+                + ((tmp.length % 8) == 0 ? 0 : (8 - tmp.length % 8))];
+        System.arraycopy(tmp, 0, raw, 0, tmp.length);
+
+        long k1, k2 = 0;
+        boolean isOdd = false;
+        // for each 8-byte block in raw byte array
+        for (int i = 0; i < raw.length; i = i + 8, isOdd = !isOdd) {
+
+            k1 = 0;
+            if (isOdd) {
+                //reverse
+                for (int j = 7; j > -1; j--) {
+                    k1 = (k1 << 7) + REVERSE[raw[i + j] & 0x7F];
+                }
+            } else {
+                for (int j = 0; j < 8; j++) {
+                    k1 = (k1 << 7) + (raw[i + j] & 0x7F);
+                }
+            }
+            k2 = k2 ^ k1;
+        }
+        
+        // 56-bit long to byte array (8 bytes)
+        for (int i = 7; i > -1; i--) {
+            keyBytes[i] = (byte) k2;
+            keyBytes[i] = (byte) (keyBytes[i] << 1);
+            k2 = k2 >> 7;
+        }
+        keyCorrection(keyBytes);
+
+        // calculate DES-CBC check sum
+        try {
+            Cipher cipher = Cipher.getInstance("DES/CBC/NoPadding"); //$NON-NLS-1$
+
+            // use tmp key as IV
+            IvParameterSpec IV = new IvParameterSpec(keyBytes);
+
+            // do DES encryption 
+            SecretKey secretKey = new SecretKeySpec(keyBytes, "DES"); //$NON-NLS-1$
+            cipher.init(Cipher.ENCRYPT_MODE, secretKey, IV);
+            byte[] enc = cipher.doFinal(raw);
+
+            // final last block is check sum
+            System.arraycopy(enc, enc.length - 8, keyBytes, 0, 8);
+            
+            keyCorrection(keyBytes);
+
+        } catch (Exception e) {
+            throw new RuntimeException(
+                    Messages.getString("auth.4A"), e); //$NON-NLS-1$
+        }
+    }
+
+    private void keyCorrection(byte[] key) {
+        
+        // fix parity
+        for (int i = 0; i < 8; i++) {
+            if (!PARITY[key[i] & 0xFF]) {
+                if ((key[i] & 0x01) == 0) {
+                    key[i]++;
+                } else {
+                    key[i]--;
+                }
+            }
+        }
+        
+        // TODO if is week do XOR
+        //if(DESKeySpec.isWeak(keyBytes,0)){
+        //}
+    }
+
+    /**
+     * Method is described in 
+     * <code>getAlgorithm</code> in interface <code>Key</code>
+     */
+    public final String getAlgorithm() {
+        checkState();
+        if (keyType == 0) {
+            return "NULL"; //$NON-NLS-1$
+        }
+        return "DES"; //$NON-NLS-1$
+    }
+    
+    /**
+     * Method is described in
+     * <code>getFormat</code> in interface <code>Key</code>
+     */
+    public final String getFormat() {
+        checkState();
+        return "RAW"; //$NON-NLS-1$
+    }
+   
+    /**
+     * Method is described in
+     * <code>getEncoded</code> in interface <code>Key</code>
+     */
+    public final byte[] getEncoded() {
+        checkState();
+        byte[] tmp = new byte[keyBytes.length];
+        System.arraycopy(keyBytes, 0, tmp, 0, tmp.length);
+        return tmp;
+    }
+
+    /**
+     * Returns the key type for this key
+     */
+    public final int getKeyType() {
+        checkState();
+        return keyType;
+    }
+
+    /**
+     * Destroys this key
+     */
+    public void destroy() throws DestroyFailedException {
+        if (!destroyed) {
+            Arrays.fill(keyBytes, (byte) 0); 
+            destroyed = true;
+        }
+        
+    }
+    /**
+     * Determines if this key has been destroyed 
+     */
+   public boolean isDestroyed() {
+        return destroyed;
+    }
+
+   /**
+    * A string representation of this key
+    */
+   @Override
+public String toString() {
+       String s_key = null;
+       StringBuilder sb = new StringBuilder();
+       
+       if (keyBytes.length == 0) {
+           s_key = "Empty Key"; //$NON-NLS-1$
+       } else {
+           s_key = Array.toString(keyBytes," "); //$NON-NLS-1$
+       }
+       sb.append("EncryptionKey: ").append("KeyType = ").append(keyType); //$NON-NLS-1$ //$NON-NLS-2$
+       sb.append("KeyBytes (Hex dump) = ").append(s_key); //$NON-NLS-1$
+       return sb.toString();
+   }
+   
+   /**
+    * if a key is destroyed then IllegalStateException should be thrown
+    */  
+   private void checkState() {
+       if (destroyed) {
+           throw new IllegalStateException (Messages.getString("auth.48")); //$NON-NLS-1$
+       }
+   }
+
+   private void readObject(ObjectInputStream s) throws IOException,
+            ClassNotFoundException {
+
+        s.defaultReadObject();
+
+        EncryptionKey ekey = (EncryptionKey) EncryptionKey.ASN1
+                .decode((byte[]) s.readObject());
+
+        keyType = ekey.getType();
+        keyBytes = ekey.getValue();
+    }
+
+    private void writeObject(ObjectOutputStream s) throws IOException {
+
+        if (destroyed) {
+            throw new IOException(Messages.getString("auth.48")); //$NON-NLS-1$
+        }
+        s.defaultWriteObject();
+
+        byte[] enc = EncryptionKey.ASN1.encode(new EncryptionKey(keyType,
+                keyBytes));
+        s.writeObject(enc);
+    }
+}

+ 140 - 0
src/javax/security/auth/kerberos/KrbDelegationPermissionCollection.java

@@ -0,0 +1,140 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.kerberos;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamField;
+import java.io.Serializable;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.util.Enumeration;
+import java.util.NoSuchElementException;
+import java.util.Vector;
+
+import org.apache.harmony.auth.internal.nls.Messages;
+
+/**
+ * Specific PermissionCollection for storing DelegationPermissions
+ * 
+ */
+class KrbDelegationPermissionCollection extends PermissionCollection implements Serializable {
+
+    private static final long serialVersionUID = -3383936936589966948L;
+    
+    private static final ObjectStreamField[] serialPersistentFields = { new ObjectStreamField(
+            "permissions", Vector.class) }; //$NON-NLS-1$
+
+    private transient DelegationPermission[] items = new DelegationPermission[10];
+
+    private transient int offset;
+
+    //initialization of a collection
+    KrbDelegationPermissionCollection() {
+        super();
+    }
+
+    /**
+     * Adds a ServicePermission to the collection.
+     */
+    @Override
+    public void add(Permission permission) {
+
+        if (isReadOnly()) {
+            throw new SecurityException(Messages.getString("auth.21")); //$NON-NLS-1$
+        }
+
+        if (permission == null || !(permission instanceof DelegationPermission)) {
+            throw new IllegalArgumentException(Messages.getString("auth.22", permission)); //$NON-NLS-1$
+        }
+        synchronized (this) {
+            if (offset == items.length) {
+                DelegationPermission[] dp = new DelegationPermission[items.length * 2];
+                System.arraycopy(items, 0, dp, 0, offset);
+                items = dp;
+            }
+            items[offset++] = (DelegationPermission) permission;
+        }
+    }
+
+    /**
+     * Returns enumeration of the collection.
+     */
+    @Override
+    public Enumeration<Permission> elements() {
+        return new Enumeration<Permission>() {
+            private int index;
+
+            public boolean hasMoreElements() {
+                return index < offset;
+            }
+
+            public DelegationPermission nextElement() {
+                if (index == offset) {
+                    throw new NoSuchElementException();
+                }
+                return items[index++];
+            }
+        };
+    }
+
+    /**
+     * Returns true if this collection implies the specified permission. 
+     */
+    @Override
+    public boolean implies(Permission permission) {
+        if (permission == null || !(permission instanceof DelegationPermission)) {
+            return false;
+        }
+
+        synchronized (this) {
+            for (int i = 0; i < offset; i++) {
+                if (items[i].implies(permission)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    // white a collection to stream
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        Vector<DelegationPermission> permissions;
+        permissions = new Vector<DelegationPermission>(offset);
+        for (int i = 0; i < offset; permissions.add(items[i++])) {
+        }
+        ObjectOutputStream.PutField fields = out.putFields();
+        fields.put("permissions", permissions); //$NON-NLS-1$
+        out.writeFields();
+    }
+
+    // read a collection from stream
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        ObjectInputStream.GetField fields = in.readFields();
+        Vector<?> permissions = (Vector<?>) fields.get("permissions", null); //$NON-NLS-1$
+        items = new DelegationPermission[permissions.size() * 2];
+        for (offset = 0; offset < items.length / 2;) {
+            Object obj = permissions.get(offset);
+            if (obj == null || !(obj instanceof DelegationPermission)) {
+                throw new IllegalArgumentException(Messages.getString("auth.22", obj)); //$NON-NLS-1$
+            }
+            items[offset++] = (DelegationPermission) obj;
+        }
+    }
+}

+ 144 - 0
src/javax/security/auth/kerberos/KrbServicePermissionCollection.java

@@ -0,0 +1,144 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.kerberos;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamField;
+import java.io.Serializable;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.util.Enumeration;
+import java.util.NoSuchElementException;
+import java.util.Vector;
+
+import org.apache.harmony.auth.internal.nls.Messages;
+
+/**
+ * Specific PermissionCollection for storing ServicePermissions
+ * 
+ */
+
+final class KrbServicePermissionCollection extends PermissionCollection
+        implements Serializable {
+
+    private static final long serialVersionUID = -4118834211490102011L;
+
+    private static final ObjectStreamField[] serialPersistentFields = { new ObjectStreamField(
+            "permissions", Vector.class) }; //$NON-NLS-1$
+
+    private transient ServicePermission[] items = new ServicePermission[10];
+
+    private transient int offset;
+
+    // initialization of a collection
+    KrbServicePermissionCollection() {
+    }
+
+    /**
+     * Adds a ServicePermission to the collection.
+     */
+    @Override
+    public void add(Permission permission) {
+
+        if (isReadOnly()) {
+            throw new SecurityException(Messages.getString("auth.21")); //$NON-NLS-1$
+        }
+
+        if (permission == null || !(permission instanceof ServicePermission)) {
+            throw new IllegalArgumentException(Messages.getString("auth.22",permission)); //$NON-NLS-1$
+        }
+        synchronized (this) {
+            if (offset == items.length) {
+                ServicePermission[] sp = new ServicePermission[items.length * 2];
+                System.arraycopy(items, 0, sp, 0, offset);
+                items = sp;
+            }
+            items[offset++] = (ServicePermission) permission;
+        }
+    }
+
+    /**
+     * Returns enumeration of the collection.
+     */
+    @Override
+    public Enumeration<Permission> elements() {
+        return new Enumeration<Permission>() {
+            private int index = 0;
+
+            public boolean hasMoreElements() {
+                return index < offset;
+            }
+
+            public Permission nextElement() {
+                if (index == offset) {
+                    throw new NoSuchElementException();
+                }
+                return items[index++];
+            }
+        };
+    }
+
+    /**
+     * Returns true if this collection implies the specified permission. 
+     */
+    @Override
+    public boolean implies(Permission permission) {
+
+        if (permission == null || !(permission instanceof ServicePermission)) {
+            return false;
+        }
+
+        synchronized (this) {
+            for (int i = 0; i < offset; i++) {
+                if (items[i].implies(permission)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    // white collection to stream
+    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
+        Vector<ServicePermission> permissions;
+        permissions = new Vector<ServicePermission>(offset);
+        for (int i = 0; i < offset; permissions.add(items[i++])) {
+        }
+        ObjectOutputStream.PutField fields = out.putFields();
+        fields.put("permissions", permissions); //$NON-NLS-1$
+        out.writeFields();
+    }
+
+    // read collection from stream
+    private void readObject(java.io.ObjectInputStream in) throws IOException,
+            ClassNotFoundException {
+        ObjectInputStream.GetField fields = in.readFields();
+        Vector<?> permissions = (Vector<?>) fields.get("permissions", null); //$NON-NLS-1$
+        items = new ServicePermission[permissions.size() * 2];
+        for (offset = 0; offset < items.length / 2;) {
+            Object obj = permissions.get(offset);
+            if (obj == null || !(obj instanceof ServicePermission)) {
+                throw new IllegalArgumentException(Messages.getString("auth.22", obj)); //$NON-NLS-1$
+            }
+            items[offset++] = (ServicePermission) obj;
+        }
+    }
+}

+ 188 - 0
src/javax/security/auth/kerberos/ServicePermission.java

@@ -0,0 +1,188 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.kerberos;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.security.Permission;
+import java.security.PermissionCollection;
+
+import org.apache.harmony.auth.internal.nls.Messages;
+
+public final class ServicePermission extends Permission implements Serializable {
+
+    private static final long serialVersionUID = -1227585031618624935L;
+
+    private static final String INITIATE = "initiate"; //$NON-NLS-1$
+    private static final String ACCEPT = "accept"; //$NON-NLS-1$
+    private static final String INITIATE_ACCEPT = "initiate,accept"; //$NON-NLS-1$
+    private static final String[] ACTIONS_TABLE = {"", ACCEPT, INITIATE, INITIATE_ACCEPT}; //$NON-NLS-1$
+
+    private final static char ACCEPT_MASK = 1;
+    private final static char INITIATE_MASK = 2;
+
+    private static final int INITIATE_LEN = INITIATE.length();
+    private static final int ACCEPT_LEN = ACCEPT.length();
+    private static final int MIN_LEN = Math.min(INITIATE_LEN,ACCEPT_LEN); 
+
+    /** 
+     * ACCEPT_MASK, INITIATE_ACCEPT or (INITIATE_ACCEPT | ACCEPT_MASK)
+     */
+    private String actions;
+
+    // initialization of actions
+    private void initActions(String actions) {
+        if (actions == null || actions.length() < MIN_LEN) {
+            throw new IllegalArgumentException(Messages.getString("auth.2E")); //$NON-NLS-1$
+        }
+
+        char[] c_acts = actions.toCharArray();
+
+        int result = 0;
+        int ptr = 0;
+
+        int len6 = c_acts.length - ACCEPT_LEN;
+        int len8 = c_acts.length - INITIATE_LEN;
+
+        do {
+            //skipping whitespaces
+            while (ptr <= len6
+                    && (c_acts[ptr] == ' ' || c_acts[ptr] == '\t'
+                            || c_acts[ptr] == '\n' || c_acts[ptr] == 0x0B
+                            || c_acts[ptr] == '\f' || c_acts[ptr] == '\r')) {
+                ++ptr;
+            }
+
+            if (ptr > len6) {
+                // expect string "accept" or "initiate", not just white
+                // spaces
+                throw new IllegalArgumentException(Messages.getString("auth.2E")); //$NON-NLS-1$
+            }
+
+            //parsing string
+            if ((c_acts[ptr] == 'a' || c_acts[ptr] == 'A')
+                    && (c_acts[ptr + 1] == 'c' || c_acts[ptr + 1] == 'C')
+                    && (c_acts[ptr + 2] == 'c' || c_acts[ptr + 2] == 'C')
+                    && (c_acts[ptr + 3] == 'e' || c_acts[ptr + 3] == 'E')
+                    && (c_acts[ptr + 4] == 'p' || c_acts[ptr + 4] == 'P')
+                    && (c_acts[ptr + 5] == 't' || c_acts[ptr + 5] == 'T')) {
+                result |= ACCEPT_MASK;
+                ptr += ACCEPT_LEN;
+            } else if (ptr <= len8
+                    && (c_acts[ptr] == 'i' || c_acts[ptr] == 'I')
+                    && (c_acts[ptr + 1] == 'n' || c_acts[ptr + 1] == 'N')
+                    && (c_acts[ptr + 2] == 'i' || c_acts[ptr + 2] == 'I')
+                    && (c_acts[ptr + 3] == 't' || c_acts[ptr + 3] == 'T')
+                    && (c_acts[ptr + 4] == 'i' || c_acts[ptr + 4] == 'I')
+                    && (c_acts[ptr + 5] == 'a' || c_acts[ptr + 5] == 'A')
+                    && (c_acts[ptr + 6] == 't' || c_acts[ptr + 6] == 'T')
+                    && (c_acts[ptr + 7] == 'e' || c_acts[ptr + 7] == 'E')) {
+                result |= INITIATE_MASK;
+                ptr += INITIATE_LEN;
+            } else {
+                throw new IllegalArgumentException(Messages.getString("auth.2E")); //$NON-NLS-1$
+            }
+
+            //skipping trailing whitespaces
+            while (ptr < c_acts.length
+                    && (c_acts[ptr] == ' ' || c_acts[ptr] == '\t'
+                            || c_acts[ptr] == '\n' || c_acts[ptr] == 0x0B
+                            || c_acts[ptr] == '\f' || c_acts[ptr] == '\r')) {
+                ptr++;
+            }
+
+            if (ptr == c_acts.length) {
+                this.actions = ACTIONS_TABLE[result];
+                return;
+            }
+        } while (c_acts[ptr++] == ',');
+
+        // unknown trailing symbol
+        throw new IllegalArgumentException(Messages.getString("auth.2E")); //$NON-NLS-1$
+    }
+
+    public ServicePermission(String name, String actions) {
+        super(name);
+
+        initActions(actions);
+
+        if (name == null) {
+            throw new NullPointerException(Messages.getString("auth.2F")); //$NON-NLS-1$
+        }
+        if (name.trim().length() == 0) {
+            throw new IllegalArgumentException(Messages.getString("auth.30")); //$NON-NLS-1$
+        }
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (obj == null || ServicePermission.class != obj.getClass()) {
+            return false;
+        }
+        ServicePermission sp = (ServicePermission) obj;
+
+        return actions == sp.actions && getName().equals(sp.getName());
+    }
+
+    @Override
+    public int hashCode() {
+        return getName().hashCode() * actions.length();
+    }
+
+    @Override
+    public String getActions() {
+        return actions;
+    }
+
+    @Override
+    public boolean implies(Permission permission) {
+        if (this == permission) {
+            return true;
+        }
+
+        if (permission == null || ServicePermission.class != permission.getClass()) {
+            return false;
+        }
+
+        ServicePermission sp = (ServicePermission) permission;
+        String name = getName();
+
+        return (actions == INITIATE_ACCEPT || actions == sp.actions)
+				&& (name.length() == 1 && name.charAt(0) == '*' || name.equals(permission.getName()));
+    }
+
+    @Override
+    public PermissionCollection newPermissionCollection() {
+        return new KrbServicePermissionCollection();
+    }
+
+    private synchronized void writeObject(java.io.ObjectOutputStream s)
+            throws IOException {
+        s.defaultWriteObject();
+    }
+
+    private synchronized void readObject(java.io.ObjectInputStream s)
+            throws IOException, ClassNotFoundException {
+        s.defaultReadObject();
+        initActions(getActions());
+    }
+}

+ 31 - 0
src/javax/security/auth/login/AccountException.java

@@ -0,0 +1,31 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.login;
+
+public class AccountException extends LoginException {
+
+    private static final long serialVersionUID = -2112878680072211787L;
+
+    public AccountException() {
+        super();
+    }
+
+    public AccountException(String message) {
+        super(message);
+    }
+}

+ 31 - 0
src/javax/security/auth/login/AccountExpiredException.java

@@ -0,0 +1,31 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.login;
+
+public class AccountExpiredException extends AccountException {
+
+    private static final long serialVersionUID = -6064064890162661560L;
+
+    public AccountExpiredException() {
+        super();
+    }
+
+    public AccountExpiredException(String message) {
+        super(message);
+    }
+}

+ 32 - 0
src/javax/security/auth/login/AccountLockedException.java

@@ -0,0 +1,32 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.login;
+
+public class AccountLockedException extends AccountException {
+
+    private static final long serialVersionUID = 8280345554014066334L;
+
+    public AccountLockedException() {
+        super();
+    }
+
+    public AccountLockedException(String message) {
+        super(message);
+    }
+
+}

+ 32 - 0
src/javax/security/auth/login/AccountNotFoundException.java

@@ -0,0 +1,32 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.login;
+
+public class AccountNotFoundException extends AccountException {
+
+    private static final long serialVersionUID = 1498349563916294614L;
+
+    public AccountNotFoundException() {
+        super();
+    }
+
+    public AccountNotFoundException(String message) {
+        super(message);
+    }
+
+}

+ 95 - 0
src/javax/security/auth/login/AppConfigurationEntry.java

@@ -0,0 +1,95 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.login;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.harmony.auth.internal.nls.Messages;
+
+public class AppConfigurationEntry {
+
+    // the login module options
+    private final Map<String, ?> options;
+
+    // the control flag
+    private final AppConfigurationEntry.LoginModuleControlFlag controlFlag;
+
+    // the login module name 
+    private final String loginModuleName;
+
+    public AppConfigurationEntry(String loginModuleName,
+            AppConfigurationEntry.LoginModuleControlFlag controlFlag, Map<String, ?> options) {
+
+        if (loginModuleName == null || loginModuleName.length() == 0) {
+            throw new IllegalArgumentException(Messages.getString("auth.26")); //$NON-NLS-1$
+        }
+
+        if (controlFlag == null) {
+            throw new IllegalArgumentException(Messages.getString("auth.27")); //$NON-NLS-1$
+        }
+
+        if (options == null) {
+            throw new IllegalArgumentException(Messages.getString("auth.1A")); //$NON-NLS-1$
+        }
+
+        this.loginModuleName = loginModuleName;
+        this.controlFlag = controlFlag;
+        this.options = Collections.unmodifiableMap(options);
+    }
+
+    public String getLoginModuleName() {
+        return loginModuleName;
+    }
+
+    public LoginModuleControlFlag getControlFlag() {
+        return controlFlag;
+    }
+
+    public Map<java.lang.String, ?> getOptions() {
+        return options;
+    }
+
+    public static class LoginModuleControlFlag {
+
+        // the control flag
+        private final String flag;
+
+        public static final LoginModuleControlFlag REQUIRED = new LoginModuleControlFlag(
+                "LoginModuleControlFlag: required"); //$NON-NLS-1$
+
+        public static final LoginModuleControlFlag REQUISITE = new LoginModuleControlFlag(
+                "LoginModuleControlFlag: requisite"); //$NON-NLS-1$
+
+        public static final LoginModuleControlFlag OPTIONAL = new LoginModuleControlFlag(
+                "LoginModuleControlFlag: optional"); //$NON-NLS-1$
+
+        public static final LoginModuleControlFlag SUFFICIENT = new LoginModuleControlFlag(
+                "LoginModuleControlFlag: sufficient"); //$NON-NLS-1$
+
+        // Creates the LoginModuleControlFlag object with specified a flag
+        private LoginModuleControlFlag(String flag) {
+            this.flag = flag;
+        }
+
+        @Override
+        public String toString() {
+            return flag;
+        }
+    }
+}

+ 94 - 0
src/javax/security/auth/login/Configuration.java

@@ -0,0 +1,94 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.login;
+
+import java.security.AccessController;
+import javax.security.auth.AuthPermission;
+
+import org.apache.harmony.security.fortress.PolicyUtils;
+
+public abstract class Configuration {
+
+    // the current configuration 
+    private static Configuration configuration;
+
+    // creates a AuthPermission object with a specify property
+    private static final AuthPermission GET_LOGIN_CONFIGURATION = new AuthPermission(
+            "getLoginConfiguration"); //$NON-NLS-1$
+
+    // creates a AuthPermission object with a specify property
+    private static final AuthPermission SET_LOGIN_CONFIGURATION = new AuthPermission(
+            "setLoginConfiguration"); //$NON-NLS-1$
+
+    // Key to security properties, defining default configuration provider.
+    private static final String LOGIN_CONFIGURATION_PROVIDER = "login.configuration.provider"; //$NON-NLS-1$
+
+    protected Configuration() {
+        super();
+    }
+
+    public static Configuration getConfiguration() {
+        SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(GET_LOGIN_CONFIGURATION);
+        }
+        return getAccessibleConfiguration();
+    }
+
+    /**
+     * Reads name of default configuration provider from security.properties,
+     * loads the class and instantiates the provider.<br> In case of any
+     * exception, wraps it with SecurityException and throws further.
+     */
+    private static final Configuration getDefaultProvider() {
+        return AccessController.doPrivileged(new PolicyUtils.ProviderLoader<Configuration>(
+                LOGIN_CONFIGURATION_PROVIDER, Configuration.class));
+    }
+
+    /**
+     * Shortcut accessor for friendly classes, to skip security checks.
+     * If active configuration was set to <code>null</code>, tries to load a default 
+     * provider, so this method never returns <code>null</code>. <br>
+     * This method is synchronized with setConfiguration()
+     */
+    static Configuration getAccessibleConfiguration() {
+        Configuration current = configuration;
+        if (current == null) {
+            synchronized (Configuration.class) {
+                if (configuration == null) {
+                    configuration = getDefaultProvider();
+                }
+                return configuration;
+            }
+        }
+        return current;
+    }
+
+    public static void setConfiguration(Configuration configuration) {
+        SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(SET_LOGIN_CONFIGURATION);
+        }
+        Configuration.configuration = configuration;
+    }
+
+    public abstract AppConfigurationEntry[] getAppConfigurationEntry(String applicationName);
+
+    public abstract void refresh();
+
+}

+ 32 - 0
src/javax/security/auth/login/CredentialException.java

@@ -0,0 +1,32 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.login;
+
+public class CredentialException extends LoginException {
+
+    private static final long serialVersionUID = -4772893876810601859L;
+
+    public CredentialException() {
+        super();
+    }
+
+    public CredentialException(String message) {
+        super(message);
+    }
+
+}

+ 32 - 0
src/javax/security/auth/login/CredentialExpiredException.java

@@ -0,0 +1,32 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.login;
+
+public class CredentialExpiredException extends CredentialException {
+
+    private static final long serialVersionUID = -5344739593859737937L;
+
+    public CredentialExpiredException() {
+        super();
+    }
+
+    public CredentialExpiredException(String message) {
+        super(message);
+    }
+
+}

+ 32 - 0
src/javax/security/auth/login/CredentialNotFoundException.java

@@ -0,0 +1,32 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.login;
+
+public class CredentialNotFoundException extends CredentialException {
+
+    private static final long serialVersionUID = -7779934467214319475L;
+
+    public CredentialNotFoundException() {
+        super();
+    }
+
+    public CredentialNotFoundException(String message) {
+        super(message);
+    }
+
+}

+ 32 - 0
src/javax/security/auth/login/FailedLoginException.java

@@ -0,0 +1,32 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.login;
+
+public class FailedLoginException extends LoginException {
+
+    private static final long serialVersionUID = 802556922354616286L;
+
+    public FailedLoginException() {
+        super();
+    }
+
+    public FailedLoginException(String message) {
+        super(message);
+    }
+
+}

+ 548 - 0
src/javax/security/auth/login/LoginContext.java

@@ -0,0 +1,548 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.login;
+
+import java.io.IOException;
+import java.security.AccessController;
+import java.security.AccessControlContext;
+import java.security.PrivilegedExceptionAction;
+import java.security.PrivilegedActionException;
+
+import java.security.Security;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.spi.LoginModule;
+import javax.security.auth.AuthPermission;
+
+import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;
+
+import org.apache.harmony.auth.internal.nls.Messages;
+
+public class LoginContext {
+
+    private static final String DEFAULT_CALLBACK_HANDLER_PROPERTY = "auth.login.defaultCallbackHandler"; //$NON-NLS-1$
+
+    /*
+     * Integer constants which serve as a replacement for the corresponding
+     * LoginModuleControlFlag.* constants. These integers are used later as
+     * index in the arrays - see loginImpl() and logoutImpl() methods
+     */
+    private static final int OPTIONAL = 0;
+
+    private static final int REQUIRED = 1;
+
+    private static final int REQUISITE = 2;
+
+    private static final int SUFFICIENT = 3;
+
+    // Subject to be used for this LoginContext's operations
+    private Subject subject;
+
+    /*
+     * Shows whether the subject was specified by user (true) or was created by
+     * this LoginContext itself (false).
+     */
+    private boolean userProvidedSubject;
+
+    // Shows whether we use installed or user-provided Configuration
+    private boolean userProvidedConfig;
+
+    // An user's AccessControlContext, used when user specifies 
+    private AccessControlContext userContext;
+
+    /*
+     * Either a callback handler passed by the user or a wrapper for the user's
+     * specified handler - see init() below.
+     */
+    private CallbackHandler callbackHandler;
+
+    /*
+     * An array which keeps the instantiated and init()-ialized login modules
+     * and their states
+     */
+    private Module[] modules;
+
+    // Stores a shared state
+    private Map<String, ?> sharedState;
+
+    // A context class loader used to load [mainly] LoginModules
+    private ClassLoader contextClassLoader;
+
+    // Shows overall status - whether this LoginContext was successfully logged 
+    private boolean loggedIn;
+
+    public LoginContext(String name) throws LoginException {
+        super();
+        init(name, null, null, null);
+    }
+
+    public LoginContext(String name, CallbackHandler cbHandler) throws LoginException {
+        super();
+        if (cbHandler == null) {
+            throw new LoginException(Messages.getString("auth.34")); //$NON-NLS-1$
+        }
+        init(name, null, cbHandler, null);
+    }
+
+    public LoginContext(String name, Subject subject) throws LoginException {
+        super();
+        if (subject == null) {
+            throw new LoginException(Messages.getString("auth.03")); //$NON-NLS-1$
+        }
+        init(name, subject, null, null);
+    }
+
+    public LoginContext(String name, Subject subject, CallbackHandler cbHandler)
+            throws LoginException {
+        super();
+        if (subject == null) {
+            throw new LoginException(Messages.getString("auth.03")); //$NON-NLS-1$
+        }
+        if (cbHandler == null) {
+            throw new LoginException(Messages.getString("auth.34")); //$NON-NLS-1$
+        }
+        init(name, subject, cbHandler, null);
+    }
+
+    public LoginContext(String name, Subject subject, CallbackHandler cbHandler,
+            Configuration config) throws LoginException {
+        super();
+        init(name, subject, cbHandler, config);
+    }
+
+    // Does all the machinery needed for the initialization.
+    private void init(String name, Subject subject, final CallbackHandler cbHandler,
+            Configuration config) throws LoginException {
+        userProvidedSubject = (this.subject = subject) != null;
+
+        //
+        // Set config
+        //
+        if (name == null) {
+            throw new LoginException(Messages.getString("auth.00")); //$NON-NLS-1$
+        }
+
+        if (config == null) {
+            config = Configuration.getAccessibleConfiguration();
+        } else {
+            userProvidedConfig = true;
+        }
+
+        SecurityManager sm = System.getSecurityManager();
+
+        if (sm != null && !userProvidedConfig) {
+            sm.checkPermission(new AuthPermission("createLoginContext." + name));//$NON-NLS-1$
+        }
+
+        AppConfigurationEntry[] entries = config.getAppConfigurationEntry(name);
+        if (entries == null) {
+            if (sm != null && !userProvidedConfig) {
+                sm.checkPermission(new AuthPermission("createLoginContext.other")); //$NON-NLS-1$
+            }
+            entries = config.getAppConfigurationEntry("other"); //$NON-NLS-1$
+            if (entries == null) {
+                throw new LoginException(Messages.getString("auth.35", name)); //$NON-NLS-1$
+            }
+        }
+
+        modules = new Module[entries.length];
+        for (int i = 0; i < modules.length; i++) {
+            modules[i] = new Module(entries[i]);
+        }
+        //
+        // Set CallbackHandler and this.contextClassLoader
+        //
+
+        /*
+         * as some of the operations to be executed (i.e. get*ClassLoader,
+         * getProperty, class loading) are security-checked, then combine all of
+         * them into a single doPrivileged() call.
+         */
+        try {
+            AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
+                public Void run() throws Exception {
+                    // First, set the 'contextClassLoader'
+                    contextClassLoader = Thread.currentThread().getContextClassLoader();
+                    if (contextClassLoader == null) {
+                        contextClassLoader = ClassLoader.getSystemClassLoader();
+                    }
+                    // then, checks whether the cbHandler is set
+                    if (cbHandler == null) {
+                        // well, let's try to find it
+                        String klassName = Security
+                                .getProperty(DEFAULT_CALLBACK_HANDLER_PROPERTY);
+                        if (klassName == null || klassName.length() == 0) {
+                            return null;
+                        }
+                        Class<?> klass = Class.forName(klassName, true, contextClassLoader);
+                        callbackHandler = (CallbackHandler) klass.newInstance();
+                    } else {
+                        callbackHandler = cbHandler;
+                    }
+                    return null;
+                }
+            });
+        } catch (PrivilegedActionException ex) {
+            Throwable cause = ex.getCause();
+            throw (LoginException) new LoginException(Messages.getString("auth.36")).initCause(cause);//$NON-NLS-1$
+        }
+
+        if (userProvidedConfig) {
+            userContext = AccessController.getContext();
+        } else if (callbackHandler != null) {
+            userContext = AccessController.getContext();
+            callbackHandler = new ContextedCallbackHandler(callbackHandler);
+        }
+    }
+
+    public Subject getSubject() {
+        if (userProvidedSubject || loggedIn) {
+            return subject;
+        }
+        return null;
+    }
+
+    /**
+     * Warning: calling the method more than once may result in undefined
+     * behaviour if logout() method is not invoked before.
+     */
+    public void login() throws LoginException {
+        PrivilegedExceptionAction<Void> action = new PrivilegedExceptionAction<Void>() {
+            public Void run() throws LoginException {
+                loginImpl();
+                return null;
+            }
+        };
+        try {
+            if (userProvidedConfig) {
+                AccessController.doPrivileged(action, userContext);
+            } else {
+                AccessController.doPrivileged(action);
+            }
+        } catch (PrivilegedActionException ex) {
+            throw (LoginException) ex.getException();
+        }
+    }
+
+    /**
+     * The real implementation of login() method whose calls are wrapped into
+     * appropriate doPrivileged calls in login().
+     */
+    private void loginImpl() throws LoginException {
+        if (subject == null) {
+            subject = new Subject();
+        }
+
+        if (sharedState == null) {
+            sharedState = new HashMap<String, Object>();
+        }
+
+        // PHASE 1: Calling login()-s
+        Throwable firstProblem = null;
+
+        int[] logged = new int[4];
+        int[] total = new int[4];
+
+        for (Module module : modules) {
+            try {
+                // if a module fails during Class.forName(), then it breaks overall 
+                // attempt - see catch() below
+                module.create(subject, callbackHandler, sharedState);
+
+                if (module.module.login()) {
+                    ++total[module.getFlag()];
+                    ++logged[module.getFlag()];
+                    if (module.getFlag() == SUFFICIENT) {
+                        break;
+                    }
+                }
+            } catch (Throwable ex) {
+                if (firstProblem == null) {
+                    firstProblem = ex;
+                }
+                if (module.klass == null) {
+                    /*
+                     * an exception occurred during class lookup - overall
+                     * attempt must fail a little trick: increase the REQUIRED's
+                     * number - this will look like a failed REQUIRED module
+                     * later, so overall attempt will fail
+                     */
+                    ++total[REQUIRED];
+                    break;
+                }
+                ++total[module.getFlag()];
+                // something happened after the class was loaded
+                if (module.getFlag() == REQUISITE) {
+                    // ... and no need to walk down anymore
+                    break;
+                }
+            }
+        }
+        // end of PHASE1, 
+
+        // Let's decide whether we have either overall success or a total failure
+        boolean fail = true;
+
+        /*
+         * Note: 'failed[xxx]!=0' is not enough to check.
+         * 
+         * Use 'logged[xx] != total[xx]' instead. This is because some modules
+         * might not be counted as 'failed' if an exception occurred during
+         * preload()/Class.forName()-ing. But, such modules still get counted in
+         * the total[].
+         */
+
+        // if any REQ* module failed - then it's failure
+        if (logged[REQUIRED] != total[REQUIRED] || logged[REQUISITE] != total[REQUISITE]) {
+            // fail = true;
+        } else {
+            if (total[REQUIRED] == 0 && total[REQUISITE] == 0) {
+                // neither REQUIRED nor REQUISITE was configured.
+                // must have at least one SUFFICIENT or OPTIONAL
+                if (logged[OPTIONAL] != 0 || logged[SUFFICIENT] != 0) {
+                    fail = false;
+                }
+                //else { fail = true; }
+            } else {
+                fail = false;
+            }
+        }
+
+        int commited[] = new int[4];
+        // clear it
+        total[0] = total[1] = total[2] = total[3] = 0;
+        if (!fail) {
+            // PHASE 2: 
+
+            for (Module module : modules) {
+                if (module.klass != null) {
+                    ++total[module.getFlag()];
+                    try {
+                        module.module.commit();
+                        ++commited[module.getFlag()];
+                    } catch (Throwable ex) {
+                        if (firstProblem == null) {
+                            firstProblem = ex;
+                        }
+                    }
+                }
+            }
+        }
+
+        // need to decide once again
+        fail = true;
+        if (commited[REQUIRED] != total[REQUIRED] || commited[REQUISITE] != total[REQUISITE]) {
+            //fail = true;
+        } else {
+            if (total[REQUIRED] == 0 && total[REQUISITE] == 0) {
+                /*
+                 * neither REQUIRED nor REQUISITE was configured. must have at
+                 * least one SUFFICIENT or OPTIONAL
+                 */
+                if (commited[OPTIONAL] != 0 || commited[SUFFICIENT] != 0) {
+                    fail = false;
+                } else {
+                    //fail = true;
+                }
+            } else {
+                fail = false;
+            }
+        }
+
+        if (fail) {
+            // either login() or commit() failed. aborting...
+
+            for (Module module : modules) {
+                try {
+                    module.module.abort();
+                } catch ( /*LoginException*/Throwable ex) {
+                    if (firstProblem == null) {
+                        firstProblem = ex;
+                    }
+                }
+            }
+            if (firstProblem instanceof PrivilegedActionException
+                    && firstProblem.getCause() != null) {
+                firstProblem = firstProblem.getCause();
+            }
+            if (firstProblem instanceof LoginException) {
+                throw (LoginException) firstProblem;
+            }
+            throw (LoginException) new LoginException(Messages.getString("auth.37")).initCause(firstProblem); //$NON-NLS-1$
+        }
+        loggedIn = true;
+    }
+
+    public void logout() throws LoginException {
+        PrivilegedExceptionAction<Void> action = new PrivilegedExceptionAction<Void>() {
+            public Void run() throws LoginException {
+                logoutImpl();
+                return null;
+            }
+        };
+        try {
+            if (userProvidedConfig) {
+                AccessController.doPrivileged(action, userContext);
+            } else {
+                AccessController.doPrivileged(action);
+            }
+        } catch (PrivilegedActionException ex) {
+            throw (LoginException) ex.getException();
+        }
+    }
+
+    /**
+     * The real implementation of logout() method whose calls are wrapped into
+     * appropriate doPrivileged calls in logout().
+     */
+    private void logoutImpl() throws LoginException {
+        if (subject == null) {
+            throw new LoginException(Messages.getString("auth.38")); //$NON-NLS-1$
+        }
+        loggedIn = false;
+        Throwable firstProblem = null;
+        int total = 0;
+        for (Module module : modules) {
+            try {
+                module.module.logout();
+                ++total;
+            } catch (Throwable ex) {
+                if (firstProblem == null) {
+                    firstProblem = ex;
+                }
+            }
+        }
+        if (firstProblem != null || total == 0) {
+            if (firstProblem instanceof PrivilegedActionException
+                    && firstProblem.getCause() != null) {
+                firstProblem = firstProblem.getCause();
+            }
+            if (firstProblem instanceof LoginException) {
+                throw (LoginException) firstProblem;
+            }
+            throw (LoginException) new LoginException(Messages.getString("auth.37")).initCause(firstProblem); //$NON-NLS-1$
+        }
+    }
+
+    /**
+     * <p>A class that servers as a wrapper for the CallbackHandler when we use
+     * installed Configuration, but not a passed one. See API docs on the
+     * LoginContext.</p>
+     * 
+     * <p>Simply invokes the given handler with the given AccessControlContext.</p>
+     */
+    private class ContextedCallbackHandler implements CallbackHandler {
+        private final CallbackHandler hiddenHandlerRef;
+
+        ContextedCallbackHandler(CallbackHandler handler) {
+            super();
+            this.hiddenHandlerRef = handler;
+        }
+
+        public void handle(final Callback[] callbacks) throws IOException,
+                UnsupportedCallbackException {
+            try {
+                AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
+                    public Void run() throws IOException, UnsupportedCallbackException {
+                        hiddenHandlerRef.handle(callbacks);
+                        return null;
+                    }
+                }, userContext);
+            } catch (PrivilegedActionException ex) {
+                if (ex.getCause() instanceof UnsupportedCallbackException) {
+                    throw (UnsupportedCallbackException) ex.getCause();
+                }
+                throw (IOException) ex.getCause();
+            }
+        }
+    }
+
+    /** 
+     * A private class that stores an instantiated LoginModule.
+     */
+    private final class Module {
+
+        // An initial info about the module to be used
+        AppConfigurationEntry entry;
+
+        // A mapping of LoginModuleControlFlag onto a simple int constant
+        int flag;
+
+        // The LoginModule itself 
+        LoginModule module;
+
+        // A class of the module
+        Class<?> klass;
+
+        Module(AppConfigurationEntry entry) {
+            this.entry = entry;
+            LoginModuleControlFlag flg = entry.getControlFlag();
+            if (flg == LoginModuleControlFlag.OPTIONAL) {
+                flag = OPTIONAL;
+            } else if (flg == LoginModuleControlFlag.REQUISITE) {
+                flag = REQUISITE;
+            } else if (flg == LoginModuleControlFlag.SUFFICIENT) {
+                flag = SUFFICIENT;
+            } else {
+                flag = REQUIRED;
+                //if(flg!=LoginModuleControlFlag.REQUIRED) throw new Error()
+            }
+        }
+
+        int getFlag() {
+            return flag;
+        }
+
+        /**
+         * Loads class of the LoginModule, instantiates it and then calls
+         * initialize().
+         */
+        void create(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState)
+                throws LoginException {
+            String klassName = entry.getLoginModuleName();
+            if (klass == null) {
+                try {
+                    klass = Class.forName(klassName, false, contextClassLoader);
+                } catch (ClassNotFoundException ex) {
+                    throw (LoginException) new LoginException(Messages.getString(
+                            "auth.39", klassName)).initCause(ex); //$NON-NLS-1$
+                }
+            }
+
+            if (module == null) {
+                try {
+                    module = (LoginModule) klass.newInstance();
+                } catch (IllegalAccessException ex) {
+                    throw (LoginException) new LoginException(Messages.getString(
+                            "auth.3A", klassName)) //$NON-NLS-1$
+                            .initCause(ex);
+                } catch (InstantiationException ex) {
+                    throw (LoginException) new LoginException(Messages.getString(
+                            "auth.3A", klassName)) //$NON-NLS-1$
+                            .initCause(ex);
+                }
+                module.initialize(subject, callbackHandler, sharedState, entry.getOptions());
+            }
+        }
+    }
+}

+ 45 - 0
src/javax/security/auth/login/LoginException.java

@@ -0,0 +1,45 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.login;
+
+import java.security.GeneralSecurityException;
+
+/**
+ * Base class for exceptions that are thrown when a login error occurs.
+ */
+public class LoginException extends GeneralSecurityException {
+
+    private static final long serialVersionUID = -4679091624035232488L;
+
+    /**
+     * Creates a new exception instance and initializes it with default values.
+     */
+    public LoginException() {
+        super();
+    }
+
+    /**
+     * Creates a new exception instance and initializes it with a given message.
+     *
+     * @param message the error message
+     */
+    public LoginException(String message) {
+        super(message);
+    }
+
+}

+ 38 - 0
src/javax/security/auth/spi/LoginModule.java

@@ -0,0 +1,38 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.spi;
+
+import java.util.Map;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginException;
+
+public interface LoginModule {
+
+    void initialize(Subject subject, CallbackHandler callbackHandler,
+            Map<String, ?> sharedState, Map<String, ?> options);
+
+    boolean login() throws LoginException;
+
+    boolean commit() throws LoginException;
+
+    boolean abort() throws LoginException;
+
+    boolean logout() throws LoginException;
+}

+ 218 - 0
src/javax/security/auth/x500/X500Principal.java

@@ -0,0 +1,218 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.x500;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.security.Principal;
+
+import org.apache.harmony.auth.internal.nls.Messages;
+import org.apache.harmony.security.x501.Name;
+
+/**
+ * Represents an X.500 principal, which holds the distinguished name of some
+ * network entity. An example of a distinguished name is {@code "O=SomeOrg,
+ * OU=SomeOrgUnit, C=US"}. The class can be instantiated from a byte representation
+ * of an object identifier (OID), an ASN.1 DER-encoded version, or a simple
+ * string holding the distinguished name. The representations must follow either
+ * RFC 2253, RFC 1779, or RFC2459.
+ */
+public final class X500Principal implements Serializable, Principal {
+
+    private static final long serialVersionUID = -500463348111345721L;
+
+    /**
+     * Defines a constant for the canonical string format of distinguished
+     * names.
+     */
+    public static final String CANONICAL = "CANONICAL"; //$NON-NLS-1$
+
+    /**
+     * Defines a constant for the RFC 1779 string format of distinguished
+     * names.
+     */
+    public static final String RFC1779 = "RFC1779"; //$NON-NLS-1$
+
+    /**
+     * Defines a constant for the RFC 2253 string format of distinguished
+     * names.
+     */
+    public static final String RFC2253 = "RFC2253"; //$NON-NLS-1$
+
+    //Distinguished Name
+    private transient Name dn;
+
+    /**
+     * Creates a new X500Principal from a given ASN.1 DER encoding of a
+     * distinguished name.
+     *
+     * @param name
+     *            the ASN.1 DER-encoded distinguished name
+     *
+     * @throws IllegalArgumentException
+     *             if the ASN.1 DER-encoded distinguished name is incorrect
+     */
+    public X500Principal(byte[] name) {
+        super();
+        if (name == null) {
+            throw new IllegalArgumentException(Messages.getString("auth.00")); //$NON-NLS-1$
+        }
+        try {
+            // FIXME dn = new Name(name);
+            dn = (Name) Name.ASN1.decode(name);
+        } catch (IOException e) {
+            IllegalArgumentException iae = new IllegalArgumentException(Messages
+                    .getString("auth.2B")); //$NON-NLS-1$
+            iae.initCause(e);
+            throw iae;
+        }
+    }
+
+    /**
+     * Creates a new X500Principal from a given ASN.1 DER encoding of a
+     * distinguished name.
+     *
+     * @param in
+     *            an {@code InputStream} holding the ASN.1 DER-encoded
+     *            distinguished name
+     *
+     * @throws IllegalArgumentException
+     *             if the ASN.1 DER-encoded distinguished name is incorrect
+     */
+    public X500Principal(InputStream in) {
+        super();
+        if (in == null) {
+            throw new NullPointerException(Messages.getString("auth.2C")); //$NON-NLS-1$
+        }
+        try {
+            // FIXME dn = new Name(is);
+            dn = (Name) Name.ASN1.decode(in);
+        } catch (IOException e) {
+            IllegalArgumentException iae = new IllegalArgumentException(Messages
+                    .getString("auth.2B")); //$NON-NLS-1$
+            iae.initCause(e);
+            throw iae;
+        }
+    }
+
+    /**
+     * Creates a new X500Principal from a string representation of a
+     * distinguished name.
+     *
+     * @param name
+     *            the string representation of the distinguished name
+     *
+     * @throws IllegalArgumentException
+     *             if the string representation of the distinguished name is
+     *             incorrect
+     */
+    public X500Principal(String name) {
+        super();
+        if (name == null) {
+            throw new NullPointerException(Messages.getString("auth.00")); //$NON-NLS-1$
+        }
+        try {
+            dn = new Name(name);
+        } catch (IOException e) {
+            IllegalArgumentException iae = new IllegalArgumentException(Messages
+                    .getString("auth.2D")); //$NON-NLS-1$
+            iae.initCause(e);
+            throw iae;
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || this.getClass() != o.getClass()) {
+            return false;
+        }
+        X500Principal principal = (X500Principal) o;
+        return dn.getName(CANONICAL).equals(principal.dn.getName(CANONICAL));
+    }
+
+    /**
+     * Returns an ASN.1 DER-encoded representation of the distinguished name
+     * contained in this X.500 principal.
+     *
+     * @return the ASN.1 DER-encoded representation
+     */
+    public byte[] getEncoded() {
+        byte[] src = dn.getEncoded();
+        byte[] dst = new byte[src.length];
+        System.arraycopy(src, 0, dst, 0, dst.length);
+        return dst;
+    }
+
+    /**
+     * Returns a human-readable string representation of the distinguished name
+     * contained in this X.500 principal.
+     *
+     * @return the string representation
+     */
+    public String getName() {
+        return dn.getName(RFC2253);
+    }
+
+    /**
+     * Returns a string representation of the distinguished name contained in
+     * this X.500 principal. The format of the representation can be chosen.
+     * Valid arguments are {@link #RFC1779}, {@link #RFC2253}, and
+     * {@link #CANONICAL}. The representations are specified in RFC 1779 and RFC
+     * 2253, respectively. The canonical form is based on RFC 2253, but adds
+     * some canonicalizing operations like removing leading and trailing
+     * whitespace, lower-casing the whole name, and bringing it into a
+     * normalized Unicode representation.
+     *
+     * @param format
+     *            the name of the format to use for the representation
+     *
+     * @return the string representation
+     *
+     * @throws IllegalArgumentException
+     *             if the {@code format} argument is not one of the three
+     *             mentioned above
+     */
+    public String getName(String format) {
+        return dn.getName(format);
+    }
+
+    @Override
+    public int hashCode() {
+        return dn.getName(CANONICAL).hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return dn.getName(RFC1779);
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        out.writeObject(dn.getEncoded());
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+
+        dn = (Name) Name.ASN1.decode((byte[]) in.readObject());
+    }
+}

+ 78 - 0
src/javax/security/auth/x500/X500PrivateCredential.java

@@ -0,0 +1,78 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.auth.x500;
+
+import java.security.cert.X509Certificate;
+import java.security.PrivateKey;
+import javax.security.auth.Destroyable;
+
+import org.apache.harmony.auth.internal.nls.Messages;
+
+public final class X500PrivateCredential implements Destroyable {
+
+    //X509 certificate
+    private X509Certificate cert;
+
+    //Private key
+    private PrivateKey key;
+
+    //Alias
+    private String alias;
+
+    public X500PrivateCredential(X509Certificate cert, PrivateKey key) {
+        super();
+        if (cert == null) {
+            throw new IllegalArgumentException(Messages.getString("auth.28")); //$NON-NLS-1$
+        }
+        if (key == null) {
+            throw new IllegalArgumentException(Messages.getString("auth.29")); //$NON-NLS-1$
+        }
+        this.cert = cert;
+        this.key = key;
+    }
+
+    public X500PrivateCredential(X509Certificate cert, PrivateKey key, String alias) {
+        this(cert, key);
+        if (alias == null) {
+            throw new IllegalArgumentException(Messages.getString("auth.2A")); //$NON-NLS-1$
+        }
+        this.alias = alias;
+    }
+
+    public X509Certificate getCertificate() {
+        return cert;
+    }
+
+    public PrivateKey getPrivateKey() {
+        return key;
+    }
+
+    public String getAlias() {
+        return alias;
+    }
+
+    public void destroy() {
+        cert = null;
+        key = null;
+        alias = null;
+    }
+
+    public boolean isDestroyed() {
+        return (cert == null && key == null && alias == null);
+    }
+}

+ 35 - 0
src/javax/security/sasl/AuthenticationException.java

@@ -0,0 +1,35 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.sasl;
+
+public class AuthenticationException extends SaslException {
+
+    private static final long serialVersionUID = -3579708765071815007L;
+
+    public AuthenticationException() {
+        super();
+    }
+
+    public AuthenticationException(String detail) {
+        super(detail);
+    }
+
+    public AuthenticationException(String detail, Throwable ex) {
+        super(detail, ex);
+    }
+}

+ 79 - 0
src/javax/security/sasl/AuthorizeCallback.java

@@ -0,0 +1,79 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.sasl;
+
+import java.io.Serializable;
+import javax.security.auth.callback.Callback;
+
+public class AuthorizeCallback implements Callback, Serializable {
+
+    private static final long serialVersionUID = -2353344186490470805L;
+
+    /**
+     * Serialized field for storing authenticationID.
+     */
+    private final String authenticationID;
+
+    /**
+     * Serialized field for storing authorizationID.
+     */
+    private final String authorizationID;
+
+    /**
+     * Serialized field for storing authorizedID.
+     */
+    private String authorizedID;
+
+    /**
+     * Store authorized Serialized field.
+     */
+    private boolean authorized;
+
+    public AuthorizeCallback(String authnID, String authzID) {
+        super();
+        authenticationID = authnID;
+        authorizationID = authzID;
+        authorizedID = authzID;
+    }
+
+    public String getAuthenticationID() {
+        return authenticationID;
+    }
+
+    public String getAuthorizationID() {
+        return authorizationID;
+    }
+
+    public String getAuthorizedID() {
+        return (authorized ? authorizedID : null);
+    }
+
+    public boolean isAuthorized() {
+        return authorized;
+    }
+
+    public void setAuthorized(boolean ok) {
+        authorized = ok;
+    }
+
+    public void setAuthorizedID(String id) {
+        if (id != null) {
+            authorizedID = id;
+        }
+    }
+}

+ 33 - 0
src/javax/security/sasl/RealmCallback.java

@@ -0,0 +1,33 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.sasl;
+
+import javax.security.auth.callback.TextInputCallback;
+
+public class RealmCallback extends TextInputCallback {
+
+    private static final long serialVersionUID = -4342673378785456908L;
+
+    public RealmCallback(String prompt) {
+        super(prompt);
+    }
+
+    public RealmCallback(String prompt, String defaultRealmInfo) {
+        super(prompt, defaultRealmInfo);
+    }
+}

+ 30 - 0
src/javax/security/sasl/RealmChoiceCallback.java

@@ -0,0 +1,30 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.sasl;
+
+import javax.security.auth.callback.ChoiceCallback;
+
+public class RealmChoiceCallback extends ChoiceCallback {
+
+    private static final long serialVersionUID = -8588141348846281332L;
+
+    public RealmChoiceCallback(String prompt, String[] choices, int defaultChoice,
+            boolean multiple) {
+        super(prompt, choices, defaultChoice, multiple);
+    }
+}

+ 204 - 0
src/javax/security/sasl/Sasl.java

@@ -0,0 +1,204 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.sasl;
+
+import java.security.Provider;
+import java.security.Security;
+import javax.security.auth.callback.CallbackHandler;
+
+import org.apache.harmony.auth.internal.nls.Messages;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.HashSet;
+import java.util.Iterator;
+
+public class Sasl {
+    // SaslClientFactory service name
+    private static final String CLIENTFACTORYSRV = "SaslClientFactory"; //$NON-NLS-1$
+
+    // SaslServerFactory service name
+    private static final String SERVERFACTORYSRV = "SaslServerFactory"; //$NON-NLS-1$
+
+    public static final String POLICY_NOPLAINTEXT = "javax.security.sasl.policy.noplaintext"; //$NON-NLS-1$
+
+    public static final String POLICY_NOACTIVE = "javax.security.sasl.policy.noactive"; //$NON-NLS-1$
+
+    public static final String POLICY_NODICTIONARY = "javax.security.sasl.policy.nodictionary"; //$NON-NLS-1$
+
+    public static final String POLICY_NOANONYMOUS = "javax.security.sasl.policy.noanonymous"; //$NON-NLS-1$
+
+    public static final String POLICY_FORWARD_SECRECY = "javax.security.sasl.policy.forward"; //$NON-NLS-1$
+
+    public static final String POLICY_PASS_CREDENTIALS = "javax.security.sasl.policy.credentials"; //$NON-NLS-1$
+
+    public static final String MAX_BUFFER = "javax.security.sasl.maxbuffer"; //$NON-NLS-1$
+
+    public static final String RAW_SEND_SIZE = "javax.security.sasl.rawsendsize"; //$NON-NLS-1$
+
+    public static final String REUSE = "javax.security.sasl.reuse"; //$NON-NLS-1$
+
+    public static final String QOP = "javax.security.sasl.qop"; //$NON-NLS-1$
+
+    public static final String STRENGTH = "javax.security.sasl.strength"; //$NON-NLS-1$
+
+    public static final String SERVER_AUTH = "javax.security.sasl.server.authentication"; //$NON-NLS-1$
+
+    // Default public constructor is overridden
+    private Sasl() {
+        super();
+    }
+
+    // Forms new instance of factory
+    private static Object newInstance(String factoryName, Provider prv) throws SaslException {
+        String msg = Messages.getString("auth.31"); //$NON-NLS-1$
+        Object factory;
+        ClassLoader cl = prv.getClass().getClassLoader();
+        if (cl == null) {
+            cl = ClassLoader.getSystemClassLoader();
+        }
+        try {
+            factory = (Class.forName(factoryName, true, cl)).newInstance();
+            return factory;
+        } catch (IllegalAccessException e) {
+            throw new SaslException(msg + factoryName, e);
+        } catch (ClassNotFoundException e) {
+            throw new SaslException(msg + factoryName, e);
+        } catch (InstantiationException e) {
+            throw new SaslException(msg + factoryName, e);
+        }
+    }
+
+    /**
+     * This method forms the list of SaslClient/SaslServer factories which are
+     * implemented in used providers
+     */
+    private static Collection<?> findFactories(String service) {
+        HashSet<Object> fact = new HashSet<Object>();
+        Provider[] pp = Security.getProviders();
+        if ((pp == null) || (pp.length == 0)) {
+            return fact;
+        }
+        HashSet<String> props = new HashSet<String>();
+        for (int i = 0; i < pp.length; i++) {
+            String prName = pp[i].getName();
+            Enumeration<Object> keys = pp[i].keys();
+            while (keys.hasMoreElements()) {
+                String s = (String) keys.nextElement();
+                if (s.startsWith(service)) {
+                    String prop = pp[i].getProperty(s);
+                    try {
+                        if (props.add(prName.concat(prop))) {
+                            fact.add(newInstance(prop, pp[i]));
+                        }
+                    } catch (SaslException e) {
+                        // ignore this factory
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+        return fact;
+    }
+
+    @SuppressWarnings("unchecked")
+    public static Enumeration<SaslClientFactory> getSaslClientFactories() {
+        Collection<SaslClientFactory> res = (Collection<SaslClientFactory>) findFactories(CLIENTFACTORYSRV);
+        return Collections.enumeration(res);
+
+    }
+
+    @SuppressWarnings("unchecked")
+    public static Enumeration<SaslServerFactory> getSaslServerFactories() {
+        Collection<SaslServerFactory> res = (Collection<SaslServerFactory>) findFactories(SERVERFACTORYSRV);
+        return Collections.enumeration(res);
+    }
+
+    public static SaslServer createSaslServer(String mechanism, String protocol,
+            String serverName, Map<String, ?> prop, CallbackHandler cbh) throws SaslException {
+        if (mechanism == null) {
+            throw new NullPointerException(Messages.getString("auth.32")); //$NON-NLS-1$
+        }
+        Collection<?> res = findFactories(SERVERFACTORYSRV);
+        if (res.isEmpty()) {
+            return null;
+        }
+
+        Iterator<?> iter = res.iterator();
+        while (iter.hasNext()) {
+            SaslServerFactory fact = (SaslServerFactory) iter.next();
+            String[] mech = fact.getMechanismNames(null);
+            boolean is = false;
+            if (mech != null) {
+                for (int j = 0; j < mech.length; j++) {
+                    if (mech[j].equals(mechanism)) {
+                        is = true;
+                        break;
+                    }
+                }
+            }
+            if (is) {
+                SaslServer saslS = fact.createSaslServer(mechanism, protocol, serverName, prop,
+                        cbh);
+                if (saslS != null) {
+                    return saslS;
+                }
+            }
+        }
+        return null;
+    }
+
+    public static SaslClient createSaslClient(String[] mechanisms, String authanticationID,
+            String protocol, String serverName, Map<String, ?> prop, CallbackHandler cbh)
+            throws SaslException {
+        if (mechanisms == null) {
+            throw new NullPointerException(Messages.getString("auth.33")); //$NON-NLS-1$
+        }
+        Collection<?> res = findFactories(CLIENTFACTORYSRV);
+        if (res.isEmpty()) {
+            return null;
+        }
+
+        Iterator<?> iter = res.iterator();
+        while (iter.hasNext()) {
+            SaslClientFactory fact = (SaslClientFactory) iter.next();
+            String[] mech = fact.getMechanismNames(null);
+            boolean is = false;
+            if (mech != null) {
+                for (int j = 0; j < mech.length; j++) {
+                    for (int n = 0; n < mechanisms.length; n++) {
+                        if (mech[j].equals(mechanisms[n])) {
+                            is = true;
+                            break;
+                        }
+                    }
+                }
+            }
+            if (is) {
+                SaslClient saslC = fact.createSaslClient(mechanisms, authanticationID,
+                        protocol, serverName, prop, cbh);
+                if (saslC != null) {
+                    return saslC;
+                }
+            }
+        }
+        return null;
+    }
+}

+ 37 - 0
src/javax/security/sasl/SaslClient.java

@@ -0,0 +1,37 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.sasl;
+
+public interface SaslClient {
+
+    void dispose() throws SaslException;
+
+    byte[] evaluateChallenge(byte[] challenge) throws SaslException;
+
+    String getMechanismName();
+
+    Object getNegotiatedProperty(String propName);
+
+    boolean hasInitialResponse();
+
+    boolean isComplete();
+
+    byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException;
+
+    byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException;
+}

+ 30 - 0
src/javax/security/sasl/SaslClientFactory.java

@@ -0,0 +1,30 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.sasl;
+
+import java.util.Map;
+import javax.security.auth.callback.CallbackHandler;
+
+public interface SaslClientFactory {
+
+    SaslClient createSaslClient(String[] mechanisms, String authorizationId, String protocol,
+            String serverName, Map<String, ?> props, CallbackHandler cbh) throws SaslException;
+
+    String[] getMechanismNames(Map<String, ?> props);
+
+}

+ 69 - 0
src/javax/security/sasl/SaslException.java

@@ -0,0 +1,69 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.sasl;
+
+import java.io.IOException;
+
+public class SaslException extends IOException {
+
+    private static final long serialVersionUID = 4579784287983423626L;
+
+    /**
+     * Serialized field for storing initial cause
+     */
+    private Throwable _exception;
+
+    public SaslException() {
+        super();
+    }
+
+    public SaslException(String detail) {
+        super(detail);
+    }
+
+    public SaslException(String detail, Throwable ex) {
+        super(detail);
+        if (ex != null) {
+            super.initCause(ex);
+            _exception = ex;
+        }
+    }
+
+    @Override
+    public Throwable getCause() {
+        return _exception;
+    }
+
+    @Override
+    public Throwable initCause(Throwable cause) {
+        super.initCause(cause);
+        _exception = cause;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        if (_exception == null) {
+            return super.toString();
+        }
+        StringBuilder sb = new StringBuilder(super.toString());
+        sb.append(", caused by: "); //$NON-NLS-1$
+        sb.append(_exception.toString());
+        return sb.toString();
+    }
+}

+ 37 - 0
src/javax/security/sasl/SaslServer.java

@@ -0,0 +1,37 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.sasl;
+
+public interface SaslServer {
+
+    void dispose() throws SaslException;
+
+    byte[] evaluateResponse(byte[] response) throws SaslException;
+
+    String getAuthorizationID();
+
+    String getMechanismName();
+
+    Object getNegotiatedProperty(String propName);
+
+    boolean isComplete();
+
+    byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException;
+
+    byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException;
+}

+ 30 - 0
src/javax/security/sasl/SaslServerFactory.java

@@ -0,0 +1,30 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package javax.security.sasl;
+
+import java.util.Map;
+import javax.security.auth.callback.CallbackHandler;
+
+public interface SaslServerFactory {
+
+    SaslServer createSaslServer(String mechanisms, String protocol, String serverName,
+            Map<String, ?> props, CallbackHandler cbh) throws SaslException;
+
+    String[] getMechanismNames(Map<String, ?> props);
+
+}

+ 2617 - 0
src/org/alfresco/jlan/app/CifsOnlyXMLServerConfiguration.java

@@ -0,0 +1,2617 @@
+/*
+ * Copyright (C) 2006-2010 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.alfresco.jlan.app;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Locale;
+import java.util.StringTokenizer;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.alfresco.jlan.debug.Debug;
+import org.alfresco.jlan.debug.DebugConfigSection;
+import org.alfresco.jlan.netbios.win32.Win32NetBIOS;
+import org.alfresco.jlan.server.auth.CifsAuthenticator;
+import org.alfresco.jlan.server.auth.UserAccount;
+import org.alfresco.jlan.server.auth.UserAccountList;
+import org.alfresco.jlan.server.auth.acl.ACLParseException;
+import org.alfresco.jlan.server.auth.acl.AccessControl;
+import org.alfresco.jlan.server.auth.acl.AccessControlList;
+import org.alfresco.jlan.server.auth.acl.AccessControlParser;
+import org.alfresco.jlan.server.auth.acl.InvalidACLTypeException;
+import org.alfresco.jlan.server.config.CoreServerConfigSection;
+import org.alfresco.jlan.server.config.GlobalConfigSection;
+import org.alfresco.jlan.server.config.InvalidConfigurationException;
+import org.alfresco.jlan.server.config.SecurityConfigSection;
+import org.alfresco.jlan.server.config.ServerConfiguration;
+import org.alfresco.jlan.server.core.DeviceContextException;
+import org.alfresco.jlan.server.core.ShareType;
+import org.alfresco.jlan.server.core.SharedDeviceList;
+import org.alfresco.jlan.server.filesys.DiskDeviceContext;
+import org.alfresco.jlan.server.filesys.DiskInterface;
+import org.alfresco.jlan.server.filesys.DiskSharedDevice;
+import org.alfresco.jlan.server.filesys.FilesystemsConfigSection;
+import org.alfresco.jlan.server.filesys.SrvDiskInfo;
+import org.alfresco.jlan.server.filesys.VolumeInfo;
+import org.alfresco.jlan.server.filesys.cache.FileStateCache;
+import org.alfresco.jlan.server.filesys.cache.StandaloneFileStateCache;
+import org.alfresco.jlan.server.thread.ThreadRequestPool;
+import org.alfresco.jlan.smb.Dialect;
+import org.alfresco.jlan.smb.DialectSelector;
+import org.alfresco.jlan.smb.server.CIFSConfigSection;
+import org.alfresco.jlan.smb.server.SMBSrvSession;
+import org.alfresco.jlan.smb.server.VirtualCircuitList;
+import org.alfresco.jlan.smb.util.DriveMapping;
+import org.alfresco.jlan.smb.util.DriveMappingList;
+import org.alfresco.jlan.util.IPAddress;
+import org.alfresco.jlan.util.MemorySize;
+import org.alfresco.jlan.util.Platform;
+import org.alfresco.jlan.util.StringList;
+import org.alfresco.jlan.util.X64;
+import org.springframework.extensions.config.ConfigElement;
+import org.springframework.extensions.config.element.GenericConfigElement;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+/**
+ * Cifs Only XML File Server Configuration Class
+ * 
+ * <p>
+ * XML implementation of the SMB server configuration.
+ * 
+ * @author gkspencer
+ */
+public class CifsOnlyXMLServerConfiguration extends ServerConfiguration {
+
+	// Constants
+	//
+	// Node type for an Element
+
+	private static final int ELEMENT_TYPE = 1;
+
+	// CIFS session debug type strings
+	//
+	// Must match the bit mask order.
+
+	private static final String m_sessDbgStr[] = { "NETBIOS", "STATE", "RXDATA", "TXDATA", "DUMPDATA", "NEGOTIATE", "TREE",
+			"SEARCH", "INFO", "FILE", "FILEIO", "TRANSACT", "ECHO", "ERROR", "IPC", "LOCK", "PKTTYPE", "DCERPC", "STATECACHE",
+			"TIMING", "NOTIFY", "STREAMS", "SOCKET", "PKTPOOL", "PKTSTATS", "THREADPOOL", "BENCHMARK", "OPLOCK", "PKTALLOC" };
+
+	// Default session debug flags, if enabled
+
+	private static final int DEFAULT_SESSDEBUG = SMBSrvSession.DBG_ERROR + SMBSrvSession.DBG_INFO + SMBSrvSession.DBG_SEARCH
+			+ SMBSrvSession.DBG_TREE + SMBSrvSession.DBG_TRAN + SMBSrvSession.DBG_STATE;
+
+	// Valid drive letter names for mapped drives
+
+	private static final String _driveLetters = "CDEFGHIJKLMNOPQRSTUVWXYZ";
+
+	// Default thread pool size
+	
+	private static final int DefaultThreadPoolInit	= 25;
+	private static final int DefaultThreadPoolMax	= 50;
+	
+	// Default memory pool settings
+	
+	private static final int[] DefaultMemoryPoolBufSizes  = { 256, 4096, 16384, 66000 };
+	private static final int[] DefaultMemoryPoolInitAlloc = {  20,   20,     5,     5 };
+	private static final int[] DefaultMemoryPoolMaxAlloc  = { 100,   50,    50,    50 };
+	
+	// Memory pool packet size limits
+	
+	private static final int MemoryPoolMinimumPacketSize	= 256;
+	private static final int MemoryPoolMaximumPacketSize	= 128 * (int) MemorySize.KILOBYTE; 
+		
+	// Memory pool allocation limits
+	
+	private static final int MemoryPoolMinimumAllocation	= 5;
+	private static final int MemoryPoolMaximumAllocation    = 500;
+	
+	// Maximum session timeout
+	
+	private static final int MaxSessionTimeout				= 60 * 60;	// 1 hour
+	
+	// Date formatter
+
+	private SimpleDateFormat m_dateFmt = new SimpleDateFormat("dd-MMM-yyyy hh:mm:ss");
+
+	/**
+	 * Default constructor
+	 */
+	public CifsOnlyXMLServerConfiguration() {
+		super("");
+	}
+
+	/**
+	 * Load the configuration from the specified file.
+	 * 
+	 * @param fname java.lang.String
+	 * @exception IOException
+	 * @exception InvalidConfigurationException
+	 */
+	public final void loadConfiguration(String fname)
+		throws IOException, InvalidConfigurationException {
+
+		// Open the configuration file
+
+		InputStream inFile = new FileInputStream(fname);
+		Reader inRead = new InputStreamReader(inFile);
+
+		// Call the main parsing method
+
+		loadConfiguration(inRead);
+	}
+
+	/**
+	 * Load the configuration from the specified input stream
+	 * 
+	 * @param in Reader
+	 * @exception IOException
+	 * @exception InvalidConfigurationException
+	 */
+	public final void loadConfiguration(Reader in)
+		throws IOException, InvalidConfigurationException {
+
+		// Reset the current configuration to the default settings
+
+		removeAllConfigSections();
+
+		// Load and parse the XML configuration document
+
+		try {
+
+			// Load the configuration from the XML file
+
+			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+			DocumentBuilder builder = factory.newDocumentBuilder();
+
+			InputSource xmlSource = new InputSource(in);
+			Document doc = builder.parse(xmlSource);
+
+			// Parse the document
+
+			loadConfiguration(doc);
+		}
+		catch (Exception ex) {
+
+			// Rethrow the exception as a configuration exeception
+
+			throw new InvalidConfigurationException("XML error", ex);
+		}
+		finally {
+
+			// Close the input file
+
+			in.close();
+		}
+	}
+
+	/**
+	 * Load the configuration from the specified document
+	 * 
+	 * @param doc Document
+	 * @exception IOException
+	 * @exception InvalidConfigurationException
+	 */
+	public void loadConfiguration(Document doc)
+		throws IOException, InvalidConfigurationException {
+
+		// Reset the current configuration to the default settings
+
+		removeAllConfigSections();
+
+		// Parse the XML configuration document
+
+		try {
+
+			// Access the root of the XML document, get a list of the child nodes
+
+			Element root = doc.getDocumentElement();
+			NodeList childNodes = root.getChildNodes();
+
+			// Process the debug settings element
+
+			procDebugElement(findChildNode("debug", childNodes));
+
+			// Process the core server configuration settings
+			
+			procServerCoreElement(findChildNode("server-core", childNodes));
+			
+			// Process the global configuration settings
+
+			procGlobalElement(findChildNode("global", childNodes));
+
+			// Process the security element
+
+			procSecurityElement(findChildNode("security", childNodes));
+
+			// Process the shares element
+
+			procSharesElement(findChildNode("shares", childNodes));
+
+			// Process the SMB server specific settings
+
+			procSMBServerElement(findChildNode("SMB", childNodes));
+
+			// Process the drive mappings settings
+
+			procDriveMappingsElement(findChildNode("DriveMappings", childNodes));
+		}
+		catch (Exception ex) {
+
+			// Rethrow the exception as a configuration exeception
+
+			throw new InvalidConfigurationException("XML error", ex);
+		}
+	}
+
+	/**
+	 * Process the server core settings XML element
+	 * 
+	 * @param srvCore Element
+	 * @exception InvalidConfigurationException
+	 */
+	protected final void procServerCoreElement(Element srvCore)
+		throws InvalidConfigurationException {
+
+		// Create the core server configuration section
+
+		CoreServerConfigSection coreConfig = new CoreServerConfigSection(this);
+
+		// Check if the server core element has been specified
+
+		if ( srvCore == null) {
+			
+			// Configure a default memory pool
+			
+			coreConfig.setMemoryPool( DefaultMemoryPoolBufSizes, DefaultMemoryPoolInitAlloc, DefaultMemoryPoolMaxAlloc);
+			
+			// Configure a default thread pool size
+			
+			coreConfig.setThreadPool( DefaultThreadPoolInit, DefaultThreadPoolMax);
+			return;
+		}
+
+		// Check if the thread pool size has been specified
+		
+		Element elem = findChildNode("threadPool", srvCore.getChildNodes());
+		if ( elem != null) {
+			
+			// Get the initial thread pool size
+			
+			String initSizeStr = elem.getAttribute("init");
+			if ( initSizeStr == null || initSizeStr.length() == 0)
+				throw new InvalidConfigurationException("Thread pool initial size not specified");
+			
+			// Validate the initial thread pool size
+			
+			int initSize = 0;
+			
+			try {
+				initSize = Integer.parseInt( initSizeStr);
+			}
+			catch (NumberFormatException ex) {
+				throw new InvalidConfigurationException("Invalid thread pool size value, " + initSizeStr);
+			}
+			
+			// Range check the thread pool size
+			
+			if ( initSize < ThreadRequestPool.MinimumWorkerThreads)
+				throw new InvalidConfigurationException("Thread pool size below minimum allowed size");
+			
+			if ( initSize > ThreadRequestPool.MaximumWorkerThreads)
+				throw new InvalidConfigurationException("Thread pool size above maximum allowed size");
+			
+			// Get the maximum thread pool size
+			
+			String maxSizeStr = elem.getAttribute("max");
+			int maxSize = initSize;
+			
+			if ( maxSizeStr.length() > 0) {
+				
+				// Validate the maximum thread pool size
+				
+				try {
+					maxSize = Integer.parseInt( maxSizeStr);
+				}
+				catch (NumberFormatException ex) {
+					throw new InvalidConfigurationException(" Invalid thread pool maximum size value, " + maxSizeStr);
+				}
+				
+				// Range check the maximum thread pool size
+				
+				if ( maxSize < ThreadRequestPool.MinimumWorkerThreads)
+					throw new InvalidConfigurationException("Thread pool maximum size below minimum allowed size");
+				
+				if ( maxSize > ThreadRequestPool.MaximumWorkerThreads)
+					throw new InvalidConfigurationException("Thread pool maximum size above maximum allowed size");
+				
+				if ( maxSize < initSize)
+					throw new InvalidConfigurationException("Initial size is larger than maxmimum size");
+			}
+			else if ( maxSizeStr != null)
+				throw new InvalidConfigurationException("Thread pool maximum size not specified");
+			
+			// Configure the thread pool
+			
+			coreConfig.setThreadPool( initSize, maxSize);
+		}
+		else {
+			
+			// Configure a default thread pool size
+			
+			coreConfig.setThreadPool( DefaultThreadPoolInit, DefaultThreadPoolMax);
+		}
+		
+		// Check if thread pool debug output is enabled
+		
+		if ( findChildNode("threadPoolDebug", srvCore.getChildNodes()) != null)
+			coreConfig.getThreadPool().setDebug( true);
+		
+		// Check if the memory pool configuration has been specified
+		
+		elem = findChildNode("memoryPool", srvCore.getChildNodes());
+		if ( elem != null) {
+			
+			// Check if the packet sizes/allocations have been specified
+			
+			Element pktElem = findChildNode("packetSizes", elem.getChildNodes());
+			if ( pktElem != null) {
+
+				// Calculate the array size for the packet size/allocation arrays
+				
+				NodeList nodeList = pktElem.getChildNodes();
+				int elemCnt = 0;
+				
+				for ( int i = 0; i < nodeList.getLength(); i++) {
+					if ( nodeList.item( i).getNodeType() == ELEMENT_TYPE)
+						elemCnt++;
+				}
+				
+				// Create the packet size, initial allocation and maximum allocation arrays
+				
+				int[] pktSizes  = new int[elemCnt];
+				int[] initSizes = new int[elemCnt];
+				int[] maxSizes  = new int[elemCnt];
+				
+				int elemIdx = 0;
+				
+				// Process the packet size elements
+				
+				for ( int i = 0; i < nodeList.getLength(); i++) {
+					
+					// Get the current element node
+					
+					Node curNode = nodeList.item( i);
+					if ( curNode.getNodeType() == ELEMENT_TYPE) {
+						
+						// Get the element and check if it is a packet size element
+						
+						Element curElem = (Element) curNode;
+						if ( curElem.getNodeName().equals("packet")) {
+							
+							// Get the packet size
+							
+							int pktSize   = -1;
+							int initAlloc = -1;
+							int maxAlloc  = -1;
+							
+							String pktSizeStr = curElem.getAttribute("size");
+							if ( pktSizeStr == null || pktSizeStr.length() == 0)
+								throw new InvalidConfigurationException("Memory pool packet size not specified");
+							
+							// Parse the packet size
+							
+							try {
+								pktSize = MemorySize.getByteValueInt( pktSizeStr);
+							}
+							catch ( NumberFormatException ex) {
+								throw new InvalidConfigurationException("Memory pool packet size, invalid size value, " + pktSizeStr);
+							}
+
+							// Make sure the packet sizes have been specified in ascending order
+							
+							if ( elemIdx > 0 && pktSizes[elemIdx - 1] >= pktSize)
+								throw new InvalidConfigurationException("Invalid packet size specified, less than/equal to previous packet size");
+							
+							// Get the initial allocation for the current packet size
+							
+							String initSizeStr = curElem.getAttribute("init");
+							if ( initSizeStr == null || initSizeStr.length() == 0)
+								throw new InvalidConfigurationException("Memory pool initial allocation not specified");
+							
+							// Parse the initial allocation
+							
+							try {
+								initAlloc = Integer.parseInt( initSizeStr);
+							}
+							catch (NumberFormatException ex) {
+								throw new InvalidConfigurationException("Invalid initial allocation, " + initSizeStr);
+							}
+							
+							// Range check the initial allocation
+							
+							if ( initAlloc < MemoryPoolMinimumAllocation)
+								throw new InvalidConfigurationException("Initial memory pool allocation below minimum of " + MemoryPoolMinimumAllocation);
+							
+							if ( initAlloc > MemoryPoolMaximumAllocation)
+								throw new InvalidConfigurationException("Initial memory pool allocation above maximum of " + MemoryPoolMaximumAllocation);
+							
+							// Get the maximum allocation for the current packet size
+
+							String maxSizeStr = curElem.getAttribute("max");
+							if ( maxSizeStr == null || maxSizeStr.length() == 0)
+								throw new InvalidConfigurationException("Memory pool maximum allocation not specified");
+							
+							// Parse the maximum allocation
+							
+							try {
+								maxAlloc = Integer.parseInt( maxSizeStr);
+							}
+							catch (NumberFormatException ex) {
+								throw new InvalidConfigurationException("Invalid maximum allocation, " + maxSizeStr);
+							}
+
+							// Range check the maximum allocation
+							
+							if ( maxAlloc < MemoryPoolMinimumAllocation)
+								throw new InvalidConfigurationException("Maximum memory pool allocation below minimum of " + MemoryPoolMinimumAllocation);
+							
+							if ( initAlloc > MemoryPoolMaximumAllocation)
+								throw new InvalidConfigurationException("Maximum memory pool allocation above maximum of " + MemoryPoolMaximumAllocation);
+
+							// Set the current packet size elements
+							
+							pktSizes[elemIdx]  = pktSize;
+							initSizes[elemIdx] = initAlloc;
+							maxSizes[elemIdx]  = maxAlloc;
+							
+							elemIdx++;
+						}
+					}
+						
+				}
+				
+				// Check if all elements were used in the packet size/allocation arrays
+				
+				if ( elemIdx < pktSizes.length) {
+					
+					// Re-allocate the packet size/allocation arrays
+					
+					int[] newPktSizes  = new int[elemIdx];
+					int[] newInitSizes = new int[elemIdx];
+					int[] newMaxSizes  = new int[elemIdx];
+					
+					// Copy the values to the shorter arrays
+					
+					System.arraycopy(pktSizes, 0, newPktSizes, 0, elemIdx);
+					System.arraycopy(initSizes, 0, newInitSizes, 0, elemIdx);
+					System.arraycopy(maxSizes, 0, newMaxSizes, 0, elemIdx);
+					
+					// Move the new arrays into place
+					
+					pktSizes  = newPktSizes;
+					initSizes = newInitSizes;
+					maxSizes  = newMaxSizes;
+				}
+				
+				// Configure the memory pool
+				
+				coreConfig.setMemoryPool( pktSizes, initSizes, maxSizes);
+			}
+		}
+		else {
+			
+			// Configure a default memory pool
+			
+			coreConfig.setMemoryPool( DefaultMemoryPoolBufSizes, DefaultMemoryPoolInitAlloc, DefaultMemoryPoolMaxAlloc);
+		}
+	}
+
+	/**
+	 * Process the global settings XML element
+	 * 
+	 * @param global Element
+	 * @exception InvalidConfigurationException
+	 */
+	protected final void procGlobalElement(Element global)
+		throws InvalidConfigurationException {
+
+		// Create the global configuration section
+
+		GlobalConfigSection globalConfig = new GlobalConfigSection(this);
+
+		// Check if the global element has been specified
+
+		if ( global == null)
+			return;
+
+		// Check if the timezone has been specified
+
+		Element elem = findChildNode("timezone", global.getChildNodes());
+		if ( elem != null) {
+
+			// Check for the timezone name
+
+			String tzName = elem.getAttribute("name");
+			if ( tzName != null && tzName.length() > 0)
+				globalConfig.setTimeZone(tzName);
+
+			// Check for the timezone offset value
+
+			String tzOffset = elem.getAttribute("offset");
+			if ( tzOffset != null && tzOffset.length() > 0 && tzName != null && tzName.length() > 0)
+				throw new InvalidConfigurationException("Specify name or offset for timezone");
+
+			// Validate the timezone offset
+
+			if ( tzOffset != null && tzOffset.length() > 0) {
+				int offset = 0;
+
+				try {
+					offset = Integer.parseInt(tzOffset);
+				}
+				catch (NumberFormatException ex) {
+					throw new InvalidConfigurationException("Invalid timezone offset value, " + tzOffset);
+				}
+
+				// Range check the timezone offset value
+
+				if ( offset < -1440 || offset > 1440)
+					throw new InvalidConfigurationException("Invalid timezone offset, value out of valid range, " + tzOffset);
+
+				// Set the timezone offset in minutes from UTC
+
+				globalConfig.setTimeZoneOffset(offset);
+			}
+		}
+	}
+
+	/**
+	 * Process the SMB server XML element
+	 * 
+	 * @param smb Element
+	 * @exception InvalidConfigurationException
+	 */
+	protected final void procSMBServerElement(Element smb)
+		throws InvalidConfigurationException {
+
+		// Check if the SMB element is valid
+
+		if ( smb == null)
+			throw new InvalidConfigurationException("SMB section must be specified");
+
+		// Create the CIFS server configuration section
+
+		CIFSConfigSection cifsConfig = new CIFSConfigSection(this);
+
+		// Process the main SMB server settings
+
+		procHostElement(findChildNode("host", smb.getChildNodes()), cifsConfig);
+
+		// Debug settings are now specified within the SMB server configuration block
+
+		// Check if NetBIOS debug is enabled
+
+		Element elem = findChildNode("netbiosDebug", smb.getChildNodes());
+		if ( elem != null)
+			cifsConfig.setNetBIOSDebug(true);
+
+		// Check if host announcement debug is enabled
+
+		elem = findChildNode("announceDebug", smb.getChildNodes());
+		if ( elem != null)
+			cifsConfig.setHostAnnounceDebug(true);
+
+		// Check if session debug is enabled
+
+		elem = findChildNode("sessionDebug", smb.getChildNodes());
+		if ( elem != null) {
+
+			// Check for session debug flags
+
+			String flags = elem.getAttribute("flags");
+			int sessDbg = DEFAULT_SESSDEBUG;
+
+			if ( flags != null) {
+
+				// Clear the default debug flags
+
+				sessDbg = 0;
+
+				// Parse the flags
+
+				flags = flags.toUpperCase();
+				StringTokenizer token = new StringTokenizer(flags, ",");
+
+				while (token.hasMoreTokens()) {
+
+					// Get the current debug flag token
+
+					String dbg = token.nextToken().trim();
+
+					// Find the debug flag name
+
+					int idx = 0;
+
+					while (idx < m_sessDbgStr.length && m_sessDbgStr[idx].equalsIgnoreCase(dbg) == false)
+						idx++;
+
+					if ( idx >= m_sessDbgStr.length)
+						throw new InvalidConfigurationException("Invalid session debug flag, " + dbg);
+
+					// Set the debug flag
+
+					sessDbg += 1 << idx;
+				}
+			}
+
+			// Set the session debug flags
+
+			cifsConfig.setSessionDebugFlags(sessDbg);
+		}
+		
+		// Check if NIO based code should be disabled
+		
+		if ( findChildNode( "disableNIO", smb.getChildNodes()) != null)
+			cifsConfig.setDisableNIOCode( true);
+		
+		// Check if a maximum virtual circuits per session limit has been specified
+		
+		elem = findChildNode("virtualCircuits", smb.getChildNodes());
+		if ( elem != null) {
+			
+			// Parse and validate the maximum virtual circuits value
+			
+			String maxVCVal = elem.getAttribute( "maxPerSession");
+			
+			if ( maxVCVal != null && maxVCVal.length() > 0) {
+				try {
+					
+					// Parse the value, and range check
+					
+					int maxVC = Integer.parseInt( maxVCVal);
+					
+					if ( maxVC < VirtualCircuitList.MinCircuits || maxVC > VirtualCircuitList.MaxCircuits)
+						throw new InvalidConfigurationException("Maximum virtual circuits value out of range, valid range " + VirtualCircuitList.MinCircuits + " - " +
+								VirtualCircuitList.MaxCircuits);
+					
+					// Set the maximum virtual circuits per session
+					
+					cifsConfig.setMaximumVirtualCircuits( maxVC);
+				}
+				catch (NumberFormatException ex) {
+					throw new InvalidConfigurationException("Invalid maximum virtual circuits value, " + maxVCVal);
+				}
+			}
+		}
+		
+		// Check if an authenticator has been specified
+
+		Element authElem = findChildNode("authenticator", smb.getChildNodes());
+		if ( authElem != null) {
+
+			// Get the authenticator class and security mode
+
+			Element classElem = findChildNode("class", authElem.getChildNodes());
+			String authClass = null;
+
+			if ( classElem == null) {
+
+				// Check if the authenticator type has been specified
+
+				String authType = authElem.getAttribute("type");
+
+				if ( authType == null)
+					throw new InvalidConfigurationException("Authenticator class not specified");
+
+				// Check the authenticator type and set the appropriate authenticator class
+
+				if ( authType.equalsIgnoreCase("local"))
+					authClass = "org.alfresco.jlan.server.auth.LocalAuthenticator";
+				else if ( authType.equalsIgnoreCase("passthru"))
+					authClass = "org.alfresco.jlan.server.auth.passthru.PassthruAuthenticator";
+
+			}
+			else {
+
+				// Set the authenticator class
+
+				authClass = getText(classElem);
+			}
+
+			Element modeElem = findChildNode("mode", authElem.getChildNodes());
+			int accessMode = CifsAuthenticator.USER_MODE;
+
+			if ( modeElem != null) {
+
+				// Validate the authenticator mode
+
+				String mode = getText(modeElem);
+				if ( mode.equalsIgnoreCase("user"))
+					accessMode = CifsAuthenticator.USER_MODE;
+				else if ( mode.equalsIgnoreCase("share"))
+					accessMode = CifsAuthenticator.SHARE_MODE;
+				else
+					throw new InvalidConfigurationException("Invalid authentication mode, must be USER or SHARE");
+			}
+
+			// Get the allow guest setting
+
+			Element allowGuest = findChildNode("allowGuest", authElem.getChildNodes());
+
+			// Get the parameters for the authenticator class
+
+			ConfigElement params = buildConfigElement(authElem);
+			cifsConfig.setAuthenticator(authClass, params, accessMode, allowGuest != null ? true : false);
+		}
+	}
+
+	/**
+	 * Process the host XML element
+	 * 
+	 * @param host Element 2param cifsConfig CIFSConfigSection
+	 * @exception InvalidConfigurationException
+	 */
+	protected final void procHostElement(Element host, CIFSConfigSection cifsConfig)
+		throws InvalidConfigurationException {
+
+		// Check if the host element is valid
+
+		if ( host == null)
+			throw new InvalidConfigurationException("Host section must be specified");
+
+		// Get the host name attribute
+
+		String attr = host.getAttribute("name");
+		if ( attr == null || attr.length() == 0)
+			throw new InvalidConfigurationException("Host name not specified or invalid");
+		cifsConfig.setServerName(attr.toUpperCase());
+
+		// If the global server name has not been set then use the CIFS server name
+
+		if ( getServerName() == null)
+			setServerName(cifsConfig.getServerName());
+
+		// Get the domain name
+
+		attr = host.getAttribute("domain");
+		if ( attr != null && attr.length() > 0)
+			cifsConfig.setDomainName(attr.toUpperCase());
+
+		// Get the enabled SMB dialects
+
+		Element elem = findChildNode("smbdialects", host.getChildNodes());
+		if ( elem != null) {
+
+			// Clear all configured SMB dialects
+
+			DialectSelector diaSel = cifsConfig.getEnabledDialects();
+			diaSel.ClearAll();
+
+			// Parse the SMB dilaects list
+
+			StringTokenizer token = new StringTokenizer(getText(elem), ",");
+
+			while (token.hasMoreTokens()) {
+
+				// Get the current SMB dialect token
+
+				String dia = token.nextToken().trim();
+
+				// Determine the dialect to be enabled
+
+				if ( dia.equalsIgnoreCase("CORE")) {
+
+					// Enable core dialects
+
+					diaSel.AddDialect(Dialect.Core);
+					diaSel.AddDialect(Dialect.CorePlus);
+				}
+				else if ( dia.equalsIgnoreCase("LANMAN")) {
+
+					// Enable the LanMAn dialects
+
+					diaSel.AddDialect(Dialect.DOSLanMan1);
+					diaSel.AddDialect(Dialect.DOSLanMan2);
+					diaSel.AddDialect(Dialect.LanMan1);
+					diaSel.AddDialect(Dialect.LanMan2);
+					diaSel.AddDialect(Dialect.LanMan2_1);
+				}
+				else if ( dia.equalsIgnoreCase("NT")) {
+
+					// Enable the NT dialect
+
+					diaSel.AddDialect(Dialect.NT);
+				}
+				else
+					throw new InvalidConfigurationException("Invalid SMB dialect, " + dia);
+			}
+
+			// Set the enabled server SMB dialects
+
+			cifsConfig.setEnabledDialects(diaSel);
+		}
+
+		// Check for a server comment
+
+		elem = findChildNode("comment", host.getChildNodes());
+		if ( elem != null)
+			cifsConfig.setComment(getText(elem));
+
+		// Check for a bind address
+
+		elem = findChildNode("bindto", host.getChildNodes());
+		if ( elem != null) {
+
+			// Check if the network adapter name has been specified
+
+			if ( elem.hasAttribute("adapter")) {
+
+				// Get the IP address for the adapter
+
+				InetAddress bindAddr = parseAdapterName(elem.getAttribute("adapter"));
+
+				// Set the bind address for the server
+
+				cifsConfig.setSMBBindAddress(bindAddr);
+			}
+			else {
+
+				// Validate the bind address
+
+				String bindText = getText(elem);
+
+				try {
+
+					// Check the bind address
+
+					InetAddress bindAddr = InetAddress.getByName(bindText);
+
+					// Set the bind address for the server
+
+					cifsConfig.setSMBBindAddress(bindAddr);
+				}
+				catch (UnknownHostException ex) {
+					throw new InvalidConfigurationException(ex.toString());
+				}
+			}
+		}
+
+		// Check if the host announcer should be enabled
+
+		elem = findChildNode("hostAnnounce", host.getChildNodes());
+		if ( elem != null) {
+
+			// Check for an announcement interval
+
+			attr = elem.getAttribute("interval");
+			if ( attr != null && attr.length() > 0) {
+				try {
+					cifsConfig.setHostAnnounceInterval(Integer.parseInt(attr));
+				}
+				catch (NumberFormatException ex) {
+					throw new InvalidConfigurationException("Invalid host announcement interval");
+				}
+			}
+
+			// Check if the domain name has been set, this is required if the host announcer is
+			// enabled
+
+			if ( cifsConfig.getDomainName() == null)
+				throw new InvalidConfigurationException("Domain name must be specified if host announcement is enabled");
+
+			// Enable host announcement
+
+			cifsConfig.setHostAnnouncer(true);
+		}
+
+		// Check for a host announcer port
+
+		elem = findChildNode("HostAnnouncerPort", host.getChildNodes());
+		if ( elem != null) {
+			try {
+				cifsConfig.setHostAnnouncerPort(Integer.parseInt(getText(elem)));
+				if ( cifsConfig.getHostAnnouncerPort() <= 0 || cifsConfig.getHostAnnouncerPort() >= 65535)
+					throw new InvalidConfigurationException("Host announcer port out of valid range");
+			}
+			catch (NumberFormatException ex) {
+				throw new InvalidConfigurationException("Invalid host announcer port");
+			}
+		}
+
+		// Check if NetBIOS SMB is enabled
+
+		elem = findChildNode("netBIOSSMB", host.getChildNodes());
+		if ( elem != null) {
+
+			// Check if NetBIOS over TCP/IP is enabled for the current platform
+
+			boolean platformOK = false;
+
+			if ( elem.hasAttribute("platforms")) {
+				
+				// Get the list of platforms
+
+				String platformsStr = elem.getAttribute("platforms");
+
+				// Parse the list of platforms that NetBIOS over TCP/IP is to be enabled for and
+				// check if the current platform is included
+
+				List<Platform.Type> enabledPlatforms = parsePlatformString(platformsStr);
+				if ( enabledPlatforms.contains(getPlatformType()))
+					platformOK = true;
+			}
+			else {
+				// No restriction on platforms
+
+				platformOK = true;
+			}
+
+			// Enable the NetBIOS SMB support
+
+			cifsConfig.setNetBIOSSMB(platformOK);
+
+			// Only parse the other settings if NetBIOS based SMB is enabled for the current
+			// platform
+
+			if ( platformOK) {
+
+				// Check for the session port
+
+				attr = elem.getAttribute("sessionPort");
+				if ( attr != null && attr.length() > 0) {
+					try {
+						cifsConfig.setSessionPort(Integer.parseInt(attr));
+						if ( cifsConfig.getSessionPort() <= 0 || cifsConfig.getSessionPort() >= 65535)
+							throw new InvalidConfigurationException("NetBIOS SMB session port out of valid range");
+					}
+					catch (NumberFormatException ex) {
+						throw new InvalidConfigurationException("Invalid NetBIOS SMB session port");
+					}
+				}
+
+				// Check for the datagram port
+
+				attr = elem.getAttribute("datagramPort");
+				if ( attr != null && attr.length() > 0) {
+					try {
+						cifsConfig.setDatagramPort(Integer.parseInt(attr));
+						if ( cifsConfig.getDatagramPort() <= 0 || cifsConfig.getDatagramPort() >= 65535)
+							throw new InvalidConfigurationException("NetBIOS SMB datagram port out of valid range");
+					}
+					catch (NumberFormatException ex) {
+						throw new InvalidConfigurationException("Invalid NetBIOS SMB datagram port");
+					}
+				}
+
+				// Check for the name server port
+
+				attr = elem.getAttribute("namingPort");
+				if ( attr != null && attr.length() > 0) {
+					try {
+						cifsConfig.setNameServerPort(Integer.parseInt(attr));
+						if ( cifsConfig.getNameServerPort() <= 0 || cifsConfig.getNameServerPort() >= 65535)
+							throw new InvalidConfigurationException("NetBIOS SMB naming port out of valid range");
+					}
+					catch (NumberFormatException ex) {
+						throw new InvalidConfigurationException("Invalid NetBIOS SMB naming port");
+					}
+				}
+
+				// Check for a bind address
+
+				attr = elem.getAttribute("bindto");
+				if ( attr != null && attr.length() > 0) {
+
+					// Validate the bind address
+
+					try {
+
+						// Check the bind address
+
+						InetAddress bindAddr = InetAddress.getByName(attr);
+
+						// Set the bind address for the NetBIOS name server
+
+						cifsConfig.setNetBIOSBindAddress(bindAddr);
+					}
+					catch (UnknownHostException ex) {
+						throw new InvalidConfigurationException(ex.toString());
+					}
+				}
+
+				// Check for a bind address using the adapter name
+
+				else if ( elem.hasAttribute("adapter")) {
+
+					// Get the bind address via the network adapter name
+
+					InetAddress bindAddr = parseAdapterName(elem.getAttribute("adapter"));
+					cifsConfig.setNetBIOSBindAddress(bindAddr);
+				}
+				else if ( cifsConfig.hasSMBBindAddress()) {
+
+					// Use the SMB bind address for the NetBIOS name server
+
+					cifsConfig.setNetBIOSBindAddress(cifsConfig.getSMBBindAddress());
+				}
+			}
+		}
+		else {
+
+			// Disable NetBIOS SMB support
+
+			cifsConfig.setNetBIOSSMB(false);
+		}
+
+		// Check if TCP/IP SMB is enabled
+
+		elem = findChildNode("tcpipSMB", host.getChildNodes());
+		if ( elem != null) {
+
+			// Check if native SMB is enabled for the current platform
+
+			boolean platformOK = false;
+
+			if ( elem.hasAttribute("platforms")) {
+
+				// Get the list of platforms
+
+				String platformsStr = elem.getAttribute("platforms");
+
+				// Parse the list of platforms that NetBIOS over TCP/IP is to be enabled for and
+				// check if the current platform is included
+
+				List<Platform.Type> enabledPlatforms = parsePlatformString(platformsStr);
+				if ( enabledPlatforms.contains(getPlatformType()))
+					platformOK = true;
+			}
+			else {
+
+				// No restriction on platforms
+
+				platformOK = true;
+			}
+
+			// Enable the TCP/IP SMB support
+
+			cifsConfig.setTcpipSMB(platformOK);
+
+			// Check if the port has been specified
+
+			attr = elem.getAttribute("port");
+			if ( attr != null && attr.length() > 0) {
+				try {
+					cifsConfig.setTcpipSMBPort(Integer.parseInt(attr));
+					if ( cifsConfig.getTcpipSMBPort() <= 0 || cifsConfig.getTcpipSMBPort() >= 65535)
+						throw new InvalidConfigurationException("TCP/IP SMB port out of valid range");
+				}
+				catch (NumberFormatException ex) {
+					throw new InvalidConfigurationException("Invalid TCP/IP SMB port");
+				}
+			}
+		}
+		else {
+
+			// Disable TCP/IP SMB support
+
+			cifsConfig.setTcpipSMB(false);
+		}
+
+		// Check that the broadcast mask has been set if TCP/IP NetBIOS and/or the host announcer is
+		// enabled
+
+		if ( cifsConfig.hasNetBIOSSMB() || cifsConfig.hasEnableAnnouncer()) {
+
+			// Parse the broadcast mask
+
+			elem = findChildNode("broadcast", host.getChildNodes());
+			if ( elem != null) {
+
+				// Check if the broadcast mask is a valid numeric IP address
+
+				if ( IPAddress.isNumericAddress(getText(elem)) == false)
+					throw new InvalidConfigurationException("Invalid broadcast mask, must be n.n.n.n format");
+
+				// Set the network broadcast mask
+
+				cifsConfig.setBroadcastMask(getText(elem));
+			}
+			else {
+
+				// Broadcast mask not configured
+
+				throw new InvalidConfigurationException("Network broadcast mask not specified");
+			}
+		}
+
+		// Check if Win32 NetBIOS is enabled
+
+		elem = findChildNode("Win32NetBIOS", host.getChildNodes());
+		if ( elem != null) {
+
+			// Check if the Win32 NetBIOS server name has been specified
+
+			attr = elem.getAttribute("name");
+			if ( attr != null && attr.length() > 0) {
+
+				// Validate the name
+
+				if ( attr.length() > 16)
+					throw new InvalidConfigurationException("Invalid Win32 NetBIOS name, " + attr);
+
+				// Set the Win32 NetBIOS file server name
+
+				cifsConfig.setWin32NetBIOSName(attr);
+			}
+
+			// Check if the Win32 NetBIOS client accept name has been specified
+
+			attr = elem.getAttribute("accept");
+			if ( attr != null && attr.length() > 0) {
+
+				// Validate the client accept name
+
+				if ( attr.length() > 15)
+					throw new InvalidConfigurationException("Invalid Win32 NetBIOS accept name, " + attr);
+
+				// Set the client accept string
+
+				cifsConfig.setWin32NetBIOSClientAccept(attr);
+			}
+
+			// Check if the Win32 NetBIOS LANA has been specified
+
+			attr = elem.getAttribute("lana");
+			if ( attr != null && attr.length() > 0) {
+
+				// Check if the LANA has been specified as an IP address or adapter name
+
+				int lana = -1;
+
+				if ( IPAddress.isNumericAddress(attr)) {
+
+					// Convert the IP address to a LANA id
+
+					lana = Win32NetBIOS.getLANAForIPAddress(attr);
+					if ( lana == -1)
+						throw new InvalidConfigurationException("Failed to convert IP address " + attr + " to a LANA");
+				}
+				else if ( attr.length() > 1 && Character.isLetter(attr.charAt(0))) {
+
+					// Convert the network adapter to a LANA id
+
+					lana = Win32NetBIOS.getLANAForAdapterName(attr);
+					if ( lana == -1)
+						throw new InvalidConfigurationException("Failed to convert network adapter " + attr + " to a LANA");
+				}
+				else {
+
+					// Validate the LANA number
+
+					try {
+						lana = Integer.parseInt(attr);
+					}
+					catch (NumberFormatException ex) {
+						throw new InvalidConfigurationException("Invalid Win32 NetBIOS LANA specified");
+					}
+
+					// LANA should be in the range 0-255
+
+					if ( lana < 0 || lana > 255)
+						throw new InvalidConfigurationException("Invalid Win32 NetBIOS LANA number, " + lana);
+				}
+
+				// Set the LANA number
+
+				cifsConfig.setWin32LANA(lana);
+			}
+
+			// Check if the native NetBIOS interface has been specified, either 'winsock' or
+			// 'netbios'
+
+			attr = elem.getAttribute("api");
+
+			if ( attr != null && attr.length() > 0) {
+				// Validate the API type
+
+				boolean useWinsock = true;
+
+				if ( attr.equalsIgnoreCase("netbios"))
+					useWinsock = false;
+				else if ( attr.equalsIgnoreCase("winsock") == false)
+					throw new InvalidConfigurationException("Invalid NetBIOS API type, spefify 'winsock' or 'netbios'");
+
+				// Set the NetBIOS API to use
+
+				cifsConfig.setWin32WinsockNetBIOS(useWinsock);
+			}
+
+			// Force the older NetBIOS API code to be used on 64Bit Windows as Winsock NetBIOS is
+			// not available
+
+			if ( cifsConfig.useWinsockNetBIOS() == true && X64.isWindows64()) {
+
+				// Log a warning
+
+				Debug.println("Using older Netbios() API code, Winsock NetBIOS not available on x64");
+
+				// Use the older NetBIOS API code
+
+				cifsConfig.setWin32WinsockNetBIOS(false);
+			}
+
+			// Check if the current operating system is supported by the Win32 NetBIOS handler
+
+			String osName = System.getProperty("os.name");
+			if ( osName.startsWith("Windows")
+					&& (osName.endsWith("95") == false && osName.endsWith("98") == false && osName.endsWith("ME") == false)) {
+
+				// Enable Win32 NetBIOS
+
+				cifsConfig.setWin32NetBIOS(true);
+			}
+			else {
+
+				// Win32 NetBIOS not supported on the current operating system
+
+				cifsConfig.setWin32NetBIOS(false);
+			}
+		}
+		else {
+
+			// Disable Win32 NetBIOS
+
+			cifsConfig.setWin32NetBIOS(false);
+		}
+
+		// Check if the host announcer should be enabled
+
+		elem = findChildNode("Win32Announce", host.getChildNodes());
+		if ( elem != null) {
+
+			// Check for an announcement interval
+
+			attr = elem.getAttribute("interval");
+			if ( attr != null && attr.length() > 0) {
+				try {
+					cifsConfig.setWin32HostAnnounceInterval(Integer.parseInt(attr));
+				}
+				catch (NumberFormatException ex) {
+					throw new InvalidConfigurationException("Invalid host announcement interval");
+				}
+			}
+
+			// Check if the domain name has been set, this is required if the host announcer is
+			// enabled
+
+			if ( cifsConfig.getDomainName() == null)
+				throw new InvalidConfigurationException("Domain name must be specified if host announcement is enabled");
+
+			// Enable Win32 NetBIOS host announcement
+
+			cifsConfig.setWin32HostAnnouncer(true);
+		}
+
+		// Check if NetBIOS and/or TCP/IP SMB have been enabled
+
+		if ( cifsConfig.hasNetBIOSSMB() == false && cifsConfig.hasTcpipSMB() == false && cifsConfig.hasWin32NetBIOS() == false)
+			throw new InvalidConfigurationException("NetBIOS SMB, TCP/IP SMB or Win32 NetBIOS must be enabled");
+
+		// Check if server alias name(s) have been specified
+
+		elem = findChildNode("alias", host.getChildNodes());
+		if ( elem != null) {
+
+			// Get the alias name list
+
+			attr = elem.getAttribute("names");
+			if ( attr == null || attr.length() == 0)
+				throw new InvalidConfigurationException("Alias name(s) not specified");
+
+			// Split the alias name list
+
+			StringList names = new StringList();
+			StringTokenizer nameTokens = new StringTokenizer(attr, ",");
+
+			while (nameTokens.hasMoreTokens()) {
+
+				// Get the current alias name
+
+				String alias = nameTokens.nextToken().trim().toUpperCase();
+
+				// Check if the name already exists in the alias list, or matches the main server
+				// name
+
+				if ( alias.equalsIgnoreCase(getServerName()))
+					throw new InvalidConfigurationException("Alias is the same as the main server name");
+				else if ( names.containsString(alias))
+					throw new InvalidConfigurationException("Same alias specified twice, " + alias);
+				else
+					names.addString(alias);
+			}
+
+			// Set the server alias names
+
+			cifsConfig.addAliasNames(names);
+		}
+
+		// Check if Macintosh extension SMBs should be enabled
+
+		elem = findChildNode("macExtensions", host.getChildNodes());
+		if ( elem != null) {
+
+			// Enable Macintosh extension SMBs
+
+			cifsConfig.setMacintoshExtensions(true);
+		}
+
+		// Check if WINS servers are configured
+
+		elem = findChildNode("WINS", host.getChildNodes());
+
+		if ( elem != null) {
+
+			// Get the primary WINS server
+
+			Element winsSrv = findChildNode("primary", elem.getChildNodes());
+			if ( winsSrv == null)
+				throw new InvalidConfigurationException("No primary WINS server configured");
+
+			// Validate the WINS server address
+
+			InetAddress primaryWINS = null;
+
+			try {
+				primaryWINS = InetAddress.getByName(getText(winsSrv));
+			}
+			catch (UnknownHostException ex) {
+				throw new InvalidConfigurationException("Invalid primary WINS server address, " + winsSrv.getNodeValue());
+			}
+
+			// Check if a secondary WINS server has been specified
+
+			winsSrv = findChildNode("secondary", elem.getChildNodes());
+			InetAddress secondaryWINS = null;
+
+			if ( winsSrv != null) {
+
+				// Validate the secondary WINS server address
+
+				try {
+					secondaryWINS = InetAddress.getByName(getText(winsSrv));
+				}
+				catch (UnknownHostException ex) {
+					throw new InvalidConfigurationException("Invalid secondary WINS server address, " + winsSrv.getNodeValue());
+				}
+			}
+
+			// Set the WINS server address(es)
+
+			cifsConfig.setPrimaryWINSServer(primaryWINS);
+			if ( secondaryWINS != null)
+				cifsConfig.setSecondaryWINSServer(secondaryWINS);
+		}
+		
+		// Check if a session timeout is configured
+		
+		elem = findChildNode("sessionTimeout", host.getChildNodes());
+		if ( elem != null) {
+			
+			// Validate the session timeout value
+
+			String sessTmo = getText( elem);
+			if ( sessTmo != null && sessTmo.length() > 0) {
+				try {
+					
+					// Convert the timeout value to milliseconds
+					
+					int tmo = Integer.parseInt(sessTmo);
+					if ( tmo < 0 || tmo > MaxSessionTimeout)
+						throw new InvalidConfigurationException("Session timeout out of range (0 - " + MaxSessionTimeout + ")");
+					
+					// Convert the session timeout to milliseconds
+					
+					cifsConfig.setSocketTimeout( tmo * 1000);
+				}
+				catch (NumberFormatException ex) {
+					throw new InvalidConfigurationException("Invalid session timeout value, " + sessTmo);
+				}
+			}
+			else
+				throw new InvalidConfigurationException("Session timeout value not specified");
+		}
+	}
+
+	/**
+	 * Process the debug XML element
+	 * 
+	 * @param debug Element
+	 * @exception InvalidConfigurationException
+	 */
+	protected final void procDebugElement(Element debug)
+		throws InvalidConfigurationException {
+
+		// Check if the debug section has been specified
+
+		if ( debug == null)
+			return;
+
+		// Create the debug configuration section
+
+		DebugConfigSection debugConfig = new DebugConfigSection(this);
+
+		// Get the debug output class and parameters
+
+		Element elem = findChildNode("output", debug.getChildNodes());
+		if ( elem == null)
+			throw new InvalidConfigurationException("Output class must be specified to enable debug output");
+
+		// Get the debug output class
+
+		Element debugClass = findChildNode("class", elem.getChildNodes());
+		if ( debugClass == null)
+			throw new InvalidConfigurationException("Class must be specified for debug output");
+
+		// Get the parameters for the debug class
+
+		ConfigElement params = buildConfigElement(elem);
+		debugConfig.setDebug(getText(debugClass), params);
+	}
+
+	/**
+	 * Process the shares XML element
+	 * 
+	 * @param shares Element
+	 * @exception InvalidConfigurationException
+	 */
+	protected final void procSharesElement(Element shares)
+		throws InvalidConfigurationException {
+
+		// Check if the shares element is valid
+
+		if ( shares == null)
+			return;
+
+		// Create the filesystems configuration section
+
+		FilesystemsConfigSection filesysConfig = new FilesystemsConfigSection(this);
+
+		// Iterate the child elements
+
+		NodeList children = shares.getChildNodes();
+
+		if ( children != null) {
+
+			// Iterate the child elements and process the disk/print share elements
+
+			for (int i = 0; i < children.getLength(); i++) {
+
+				// Get the current child node
+
+				Node node = children.item(i);
+
+				if ( node.getNodeType() == ELEMENT_TYPE) {
+
+					// Get the next element from the list
+
+					Element child = (Element) node;
+
+					// Check if this is a disk or print share element
+
+					if ( child.getNodeName().equalsIgnoreCase("diskshare"))
+						addDiskShare(child, filesysConfig);
+				}
+			}
+		}
+	}
+
+	/**
+	 * Process the security XML element
+	 * 
+	 * @param security Element
+	 * @exception InvalidConfigurationException
+	 */
+	protected final void procSecurityElement(Element security)
+		throws InvalidConfigurationException {
+
+		// Check if the security element is valid
+
+		if ( security == null)
+			return;
+
+		// Create the security configuration section
+
+		SecurityConfigSection secConfig = new SecurityConfigSection(this);
+
+		// Check if an access control manager has been specified
+
+		Element aclElem = findChildNode("accessControlManager", security.getChildNodes());
+		if ( aclElem != null) {
+
+			// Get the access control manager class and security mode
+
+			Element classElem = findChildNode("class", aclElem.getChildNodes());
+			if ( classElem == null)
+				throw new InvalidConfigurationException("Access control manager class not specified");
+
+			// Get the parameters for the access control manager class
+
+			ConfigElement params = buildConfigElement(aclElem);
+			secConfig.setAccessControlManager(getText(classElem), params);
+		}
+		else {
+
+			// Use the default access control manager
+
+			secConfig.setAccessControlManager("org.alfresco.jlan.server.auth.acl.DefaultAccessControlManager",
+					new GenericConfigElement("aclManager"));
+		}
+
+		// Check if global access controls have been specified
+
+		Element globalACLs = findChildNode("globalAccessControl", security.getChildNodes());
+		if ( globalACLs != null) {
+
+			// Parse the access control list
+
+			AccessControlList acls = procAccessControlElement(globalACLs, secConfig);
+			if ( acls != null)
+				secConfig.setGlobalAccessControls(acls);
+		}
+
+		// Check if a JCE provider class has been specified
+
+		Element jceElem = findChildNode("JCEProvider", security.getChildNodes());
+		if ( jceElem != null) {
+
+			// Set the JCE provider
+
+			secConfig.setJCEProvider(getText(jceElem));
+		}
+
+		// Add the users
+
+		Element usersElem = findChildNode("users", security.getChildNodes());
+		if ( usersElem != null) {
+
+			// Get the list of user elements
+
+			NodeList userList = usersElem.getChildNodes();
+
+			for (int i = 0; i < userList.getLength(); i++) {
+
+				// Get the current user node
+
+				Node node = userList.item(i);
+
+				if ( node.getNodeType() == ELEMENT_TYPE) {
+					Element userElem = (Element) node;
+					addUser(userElem, secConfig);
+				}
+			}
+		}
+
+		// Check if a share mapper has been specified
+
+		Element mapper = findChildNode("shareMapper", security.getChildNodes());
+
+		if ( mapper != null) {
+
+			// Get the share mapper class
+
+			Element classElem = findChildNode("class", mapper.getChildNodes());
+			if ( classElem == null)
+				throw new InvalidConfigurationException("Share mapper class not specified");
+
+			// Get the parameters for the share mapper class
+
+			ConfigElement params = buildConfigElement(mapper);
+			secConfig.setShareMapper(getText(classElem), params);
+		}
+
+		// Check if the users interface has been specified
+
+		Element usersIface = findChildNode("usersInterface", security.getChildNodes());
+
+		if ( usersIface != null) {
+
+			// Get the users interface class
+
+			Element classElem = findChildNode("class", usersIface.getChildNodes());
+			if ( classElem == null)
+				throw new InvalidConfigurationException("Users interface class not specified");
+
+			// Get the parameters for the users interface class
+
+			ConfigElement params = buildConfigElement(usersIface);
+			secConfig.setUsersInterface(getText(classElem), params);
+		}
+	}
+
+	/**
+	 * Process the drive mappings XML element
+	 * 
+	 * @param mappings Element
+	 * @exception InvalidConfigurationException
+	 */
+	protected final void procDriveMappingsElement(Element mappings)
+		throws InvalidConfigurationException {
+
+		// Check if the drive mappings element is valid
+
+		if ( mappings == null)
+			return;
+
+		// Create the drive mappings configuration section
+
+		DriveMappingsConfigSection mapConfig = new DriveMappingsConfigSection(this);
+
+		// Parse each drive mapping element
+
+		NodeList mapElems = mappings.getChildNodes();
+		DriveMappingList mapList = null;
+
+		if ( mapElems != null && mapElems.getLength() > 0) {
+
+			// Create the mapped drive list
+
+			mapList = new DriveMappingList();
+
+			// Access the CIFS server configuration
+
+			CIFSConfigSection cifsConfig = (CIFSConfigSection) getConfigSection(CIFSConfigSection.SectionName);
+
+			// Get a list of the available shares
+
+			SecurityConfigSection secConfig = (SecurityConfigSection) getConfigSection(SecurityConfigSection.SectionName);
+			SharedDeviceList shareList = secConfig.getShareMapper().getShareList(getServerName(), null, false);
+
+			// Process each drive mapping element
+
+			for (int i = 0; i < mapElems.getLength(); i++) {
+
+				// Get the current mapped drive details
+
+				Node node = mapElems.item(i);
+
+				if ( node.getNodeType() == ELEMENT_TYPE) {
+
+					// Access the mapped drive element
+
+					Element elem = (Element) node;
+
+					if ( elem.getNodeName().equals("mapDrive")) {
+
+						// Get the mapped drive local drive and remote path details
+
+						String localPath = elem.getAttribute("drive").toUpperCase();
+						String shareName = elem.getAttribute("share");
+
+						// Check the local path string
+
+						if ( localPath.length() != 2)
+							throw new InvalidConfigurationException("Invalid local drive specified, " + localPath);
+
+						if ( localPath.charAt(1) != ':' || _driveLetters.indexOf(localPath.charAt(0)) == -1)
+							throw new InvalidConfigurationException("Invalid local drive specified, " + localPath);
+
+						// Check if the share name is a valid local disk share
+
+						if ( shareName.length() == 0)
+							throw new InvalidConfigurationException("Empty share name for mapped drive, " + localPath);
+
+						if ( shareList.findShare(shareName, ShareType.DISK, true) == null)
+							throw new InvalidConfigurationException("Mapped drive share " + shareName + " does not exist");
+
+						// Get the username/password to be used to connect the mapped drive
+
+						String userName = null;
+						String password = null;
+
+						if ( elem.hasAttribute("username"))
+							userName = elem.getAttribute("username");
+
+						if ( elem.hasAttribute("password"))
+							password = elem.getAttribute("password");
+
+						// Get the options flags
+
+						boolean interact = false;
+						boolean prompt = false;
+
+						if ( elem.hasAttribute("interactive")) {
+							if ( elem.getAttribute("interactive").equalsIgnoreCase("YES"))
+								interact = true;
+						}
+
+						if ( elem.hasAttribute("prompt")) {
+							if ( elem.getAttribute("prompt").equalsIgnoreCase("YES"))
+								prompt = true;
+						}
+
+						// Build the remote path
+
+						StringBuffer remPath = new StringBuffer();
+						remPath.append("\\\\");
+
+						if ( cifsConfig.hasWin32NetBIOS() && cifsConfig.getWin32ServerName() != null)
+							remPath.append(cifsConfig.getWin32ServerName());
+						else
+							remPath.append(getServerName());
+						remPath.append("\\");
+						remPath.append(shareName.toUpperCase());
+
+						// Add a drive mapping
+
+						mapList.addMapping(new DriveMapping(localPath, remPath.toString(), userName, password, interact, prompt));
+					}
+				}
+			}
+
+			// Set the mapped drive list
+
+			mapConfig.setMappedDrives(mapList);
+		}
+	}
+
+	/**
+	 * Process an access control sub-section and return the access control list
+	 * 
+	 * @param acl Element
+	 * @param secConfig SecutiryConfigSection
+	 * @throws InvalidConfigurationException
+	 */
+	protected final AccessControlList procAccessControlElement(Element acl, SecurityConfigSection secConfig)
+		throws InvalidConfigurationException {
+
+		// Check if there is an access control manager configured
+
+		if ( secConfig.getAccessControlManager() == null)
+			throw new InvalidConfigurationException("No access control manager configured");
+
+		// Create the access control list
+
+		AccessControlList acls = new AccessControlList();
+
+		// Check if there is a default access level for the ACL group
+
+		String attrib = acl.getAttribute("default");
+
+		if ( attrib != null && attrib.length() > 0) {
+
+			// Get the access level and validate
+
+			try {
+
+				// Parse the access level name
+
+				int access = AccessControlParser.parseAccessTypeString(attrib);
+
+				// Set the default access level for the access control list
+
+				acls.setDefaultAccessLevel(access);
+			}
+			catch (InvalidACLTypeException ex) {
+				throw new InvalidConfigurationException("Default access level error, " + ex.toString());
+			}
+			catch (ACLParseException ex) {
+				throw new InvalidConfigurationException("Default access level error, " + ex.toString());
+			}
+		}
+
+		// Parse each access control element and create the required access control
+
+		NodeList aclElems = acl.getChildNodes();
+
+		if ( aclElems != null && aclElems.getLength() > 0) {
+
+			// Create the access controls
+
+			GenericConfigElement params = null;
+			String type = null;
+
+			for (int i = 0; i < aclElems.getLength(); i++) {
+
+				// Get the current ACL details
+
+				Node node = aclElems.item(i);
+
+				if ( node.getNodeType() == ELEMENT_TYPE) {
+
+					// Access the ACL element
+
+					Element elem = (Element) node;
+					type = elem.getNodeName();
+
+					// Create a new config element
+
+					params = new GenericConfigElement("acl");
+
+					// Convert the element attributes into a list of name value pairs
+
+					NamedNodeMap attrs = elem.getAttributes();
+
+					if ( attrs == null || attrs.getLength() == 0)
+						throw new InvalidConfigurationException("Missing attribute(s) for access control " + type);
+
+					for (int j = 0; j < attrs.getLength(); j++) {
+
+						// Create a name/value pair from the current attribute and add to the
+						// parameter list
+
+						Node attr = attrs.item(j);
+						params.addAttribute( attr.getNodeName(), attr.getNodeValue());
+					}
+
+					try {
+
+						// Create the access control and add to the list
+
+						acls.addControl(secConfig.getAccessControlManager().createAccessControl(type, params));
+					}
+					catch (InvalidACLTypeException ex) {
+						throw new InvalidConfigurationException("Invalid access control type - " + type);
+					}
+					catch (ACLParseException ex) {
+						throw new InvalidConfigurationException("Access control parse error (" + type + "), " + ex.toString());
+					}
+				}
+			}
+		}
+
+		// Check if there are no access control rules but the default access level is set to 'None',
+		// this is not allowed
+		// as the share would not be accessible or visible.
+
+		if ( acls.getDefaultAccessLevel() == AccessControl.NoAccess && acls.numberOfControls() == 0)
+			throw new InvalidConfigurationException("Empty access control list and default access 'None' not allowed");
+
+		// Return the access control list
+
+		return acls;
+	}
+
+	/**
+	 * Add a user
+	 * 
+	 * @param user Element
+	 * @param secConfig SecurityConfigSection
+	 * @exception InvalidConfigurationException
+	 */
+	protected final void addUser(Element user, SecurityConfigSection secConfig)
+		throws InvalidConfigurationException {
+
+		// Get the username
+
+		String attr = user.getAttribute("name");
+		if ( attr == null || attr.length() == 0)
+			throw new InvalidConfigurationException("User name not specified, or zero length");
+
+		// Check if the user already exists
+
+		String userName = attr;
+
+		if ( secConfig.hasUserAccounts() && secConfig.getUserAccounts().findUser(userName) != null)
+			throw new InvalidConfigurationException("User " + userName + " already defined");
+
+		// Get the MD4 hashed password
+
+		byte[] md4 = null;
+		String password = null;
+
+		Element elem = findChildNode("md4", user.getChildNodes());
+		if ( elem != null) {
+
+			// Get the MD4 hashed password string
+
+			String md4Str = getText(elem);
+			if ( md4Str == null || md4Str.length() != 32)
+				throw new InvalidConfigurationException("Invalid MD4 hashed password for user " + userName);
+
+			// Decode the MD4 string
+
+			md4 = new byte[16];
+			for (int i = 0; i < 16; i++) {
+
+				// Get a hex pair and convert
+
+				String hexPair = md4Str.substring(i * 2, (i * 2) + 2);
+				md4[i] = (byte) Integer.parseInt(hexPair, 16);
+			}
+		}
+		else {
+
+			// Get the password for the account
+
+			elem = findChildNode("password", user.getChildNodes());
+			if ( elem == null)
+				throw new InvalidConfigurationException("No password specified for user " + userName);
+
+			// Get the plaintext password
+
+			password = getText(elem);
+		}
+
+		// Create the user account
+
+		UserAccount userAcc = new UserAccount(userName, password);
+		userAcc.setMD4Password(md4);
+
+		// Check if the user in an administrator
+
+		elem = findChildNode("administrator", user.getChildNodes());
+		if ( elem != null)
+			userAcc.setAdministrator(true);
+
+		// Get the real user name and comment
+
+		elem = findChildNode("realname", user.getChildNodes());
+		if ( elem != null)
+			userAcc.setRealName(getText(elem));
+
+		elem = findChildNode("comment", user.getChildNodes());
+		if ( elem != null)
+			userAcc.setComment(getText(elem));
+
+		// Get the home directory
+
+		elem = findChildNode("home", user.getChildNodes());
+		if ( elem != null)
+			userAcc.setHomeDirectory(getText(elem));
+
+		// Add the user account
+
+		UserAccountList accList = secConfig.getUserAccounts();
+		if ( accList == null)
+			secConfig.setUserAccounts(new UserAccountList());
+		secConfig.getUserAccounts().addUser(userAcc);
+	}
+
+	/**
+	 * Add a disk share
+	 * 
+	 * @param disk Element 2param filesysConfig FilesystemConfigSection
+	 * @exception InvalidConfigurationException
+	 */
+	protected final void addDiskShare(Element disk, FilesystemsConfigSection filesysConfig)
+		throws InvalidConfigurationException {
+
+		// Get the share name and comment attributes
+
+		String attr = disk.getAttribute("name");
+		if ( attr == null || attr.length() == 0)
+			throw new InvalidConfigurationException("Disk share name must be specified");
+
+		String name = attr;
+		String comment = null;
+
+		attr = disk.getAttribute("comment");
+		if ( attr != null && attr.length() > 0)
+			comment = attr;
+
+		// Get the disk driver details
+
+		Element driverElem = findChildNode("driver", disk.getChildNodes());
+		if ( driverElem == null)
+			throw new InvalidConfigurationException("No driver specified for disk share " + name);
+
+		Element classElem = findChildNode("class", driverElem.getChildNodes());
+		if ( classElem == null || getText(classElem).length() == 0)
+			throw new InvalidConfigurationException("No driver class specified for disk share " + name);
+
+		// Get the security configuration section
+
+		SecurityConfigSection secConfig = (SecurityConfigSection) getConfigSection(SecurityConfigSection.SectionName);
+
+		// Check if an access control list has been specified
+
+		AccessControlList acls = null;
+		Element aclElem = findChildNode("accessControl", disk.getChildNodes());
+
+		if ( aclElem != null) {
+
+			// Parse the access control list
+
+			acls = procAccessControlElement(aclElem, secConfig);
+		}
+		else {
+
+			// Use the global access control list for this disk share
+
+			acls = secConfig.getGlobalAccessControls();
+		}
+
+		// Get the parameters for the driver
+
+		ConfigElement params = buildConfigElement(driverElem);
+
+		// Check if change notification should be disabled for this device
+
+		boolean changeNotify = findChildNode("disableChangeNotification", disk.getChildNodes()) != null ? false : true;
+
+		// Check if the volume information has been specified
+
+		Element volElem = findChildNode("volume", disk.getChildNodes());
+		VolumeInfo volInfo = null;
+
+		if ( volElem != null) {
+
+			// Create the volume information
+
+			volInfo = new VolumeInfo("");
+
+			// Get the volume label
+
+			attr = volElem.getAttribute("label");
+			if ( attr != null && attr.length() > 0)
+				volInfo.setVolumeLabel(attr);
+
+			// Get the serial number
+
+			attr = volElem.getAttribute("serial");
+			if ( attr != null && attr.length() > 0) {
+				try {
+					volInfo.setSerialNumber(Integer.parseInt(attr));
+				}
+				catch (NumberFormatException ex) {
+					throw new InvalidConfigurationException("Volume serial number invalid, " + attr);
+				}
+			}
+
+			// Get the creation date/time
+
+			attr = volElem.getAttribute("created");
+			if ( attr != null && attr.length() > 0) {
+				try {
+					volInfo.setCreationDateTime(m_dateFmt.parse(attr));
+				}
+				catch (ParseException ex) {
+					throw new InvalidConfigurationException("Volume creation date/time invalid, " + attr);
+				}
+			}
+		}
+		else {
+
+			// Create volume information using the share name
+
+			volInfo = new VolumeInfo(name, (int) System.currentTimeMillis(), new Date(System.currentTimeMillis()));
+		}
+
+		// Check if the disk sizing information has been specified
+
+		SrvDiskInfo diskInfo = null;
+		Element sizeElem = findChildNode("size", disk.getChildNodes());
+
+		if ( sizeElem != null) {
+
+			// Get the total disk size in bytes
+
+			long totSize = -1L;
+			long freeSize = 0;
+
+			attr = sizeElem.getAttribute("totalSize");
+			if ( attr != null && attr.length() > 0)
+				totSize = MemorySize.getByteValue(attr);
+
+			if ( totSize == -1L)
+				throw new InvalidConfigurationException("Total disk size invalid or not specified");
+
+			// Get the free size in bytes
+
+			attr = sizeElem.getAttribute("freeSize");
+			if ( attr != null && attr.length() > 0)
+				freeSize = MemorySize.getByteValue(attr);
+			else
+				freeSize = (totSize / 10L) * 9L;
+
+			if ( freeSize == -1L)
+				throw new InvalidConfigurationException("Free disk size invalid or not specified");
+
+			// Get the block size and blocks per unit values, if specified
+
+			long blockSize = 512L;
+			long blocksPerUnit = 64L; // 32Kb units
+
+			attr = sizeElem.getAttribute("blockSize");
+			if ( attr != null && attr.length() > 0) {
+				try {
+					blockSize = Long.parseLong(attr);
+
+					// Check for a multiple of 512 bytes
+
+					if ( blockSize <= 0 || blockSize % 512 != 0)
+						throw new InvalidConfigurationException("Block size must be a multiple of 512");
+				}
+				catch (NumberFormatException ex) {
+					throw new InvalidConfigurationException("Invalid block size specified, " + attr);
+				}
+			}
+
+			attr = sizeElem.getAttribute("blocksPerUnit");
+			if ( attr != null && attr.length() > 0) {
+				try {
+					blocksPerUnit = Long.parseLong(attr);
+
+					// Check for a valid blocks per unit value
+
+					if ( blocksPerUnit <= 0)
+						throw new InvalidConfigurationException("Invalid blocks per unit, must be greater than zero");
+				}
+				catch (NumberFormatException ex) {
+					throw new InvalidConfigurationException("Invalid blocks per unit value");
+				}
+			}
+
+			// Calculate the sizes and set the disk sizing information
+
+			long unitSize = blockSize * blocksPerUnit;
+			long totUnits = totSize / unitSize;
+			long freeUnits = freeSize / unitSize;
+
+			diskInfo = new SrvDiskInfo(totUnits, blocksPerUnit, blockSize, freeUnits);
+		}
+		else {
+
+			// Default to a 80Gb sized disk with 90% free space
+
+			diskInfo = new SrvDiskInfo(2560000, 64, 512, 2304000);
+		}
+
+		// Check if a state cache is configured
+		
+		Element cacheElem = findChildNode("stateCache", disk.getChildNodes());
+		FileStateCache stateCache = null;
+		
+		if ( cacheElem != null) {
+
+			// Convert the state cache configuration
+			
+			ConfigElement cacheConfig = buildConfigElement( cacheElem);
+			
+			// Get the cache type
+			
+			attr = cacheElem.getAttribute( "type");
+			if ( attr.equalsIgnoreCase( "standalone")) {
+				
+				// Create a standalone file state cache
+				
+				stateCache = new StandaloneFileStateCache();
+			}
+			else if ( attr.equalsIgnoreCase( "cluster")) {
+				
+				// Create a clustered file state cache, need to load the class to avoid a reference to it
+				
+				try {
+					stateCache = (FileStateCache) Class.forName( "org.alfresco.jlan.server.filesys.cache.hazelcast.HazelCastClusterFileStateCache").newInstance();
+				}
+				catch ( ClassNotFoundException ex) {
+					throw new InvalidConfigurationException( "Clustered file state cache not available, check build/Jar");
+				}
+				catch ( Exception ex) {
+					throw new InvalidConfigurationException( "Failed to load clustered file state cache class, " + ex);
+				}
+			}
+			else if ( attr.equalsIgnoreCase( "custom")) {
+
+				// Get the custom state cache class name
+				
+				Element cacheClass = findChildNode( "class", cacheElem.getChildNodes());
+				if ( cacheClass == null || getText( cacheClass).length() == 0)
+					throw new InvalidConfigurationException( "Custom state cache class not specified");
+				
+				// Create a custom file state cache
+				
+				try {
+					Object cacheObj = Class.forName( getText( cacheClass)).newInstance();
+					if ( cacheObj instanceof FileStateCache == false)
+						throw new InvalidConfigurationException( "State cache class is not a FileStateCache based class");
+					
+					stateCache = (FileStateCache) cacheObj; 
+				}
+				catch ( ClassNotFoundException ex) {
+					throw new InvalidConfigurationException( "Clustered file state cache not available, check build/Jar");
+				}
+				catch ( Exception ex) {
+					throw new InvalidConfigurationException( "Failed to load clustered file state cache class, " + ex);
+				}
+			}
+			
+			// Initialize the cache
+			
+			if ( stateCache != null)
+				stateCache.initializeCache( cacheConfig, this);
+			else
+				throw new InvalidConfigurationException( "Failed to initialize state cache for filesystem " + name);
+		}
+		
+		// Check if a share with this name already exists
+
+		if ( filesysConfig.getShares().findShare(name) != null)
+			throw new InvalidConfigurationException("Share " + name + " already exists");
+
+		// Validate the driver class, create a device context and add the new disk share
+
+		try {
+
+			// Load the driver class
+
+			Object drvObj = Class.forName(getText(classElem)).newInstance();
+			if ( drvObj instanceof DiskInterface) {
+
+				// Create the driver
+
+				DiskInterface diskDrv = (DiskInterface) drvObj;
+
+				// Create a context for this share instance, save the configuration parameters as
+				// part of the context
+
+				DiskDeviceContext devCtx = (DiskDeviceContext) diskDrv.createContext(name, params);
+				devCtx.setConfigurationParameters(params);
+
+				// Enable/disable change notification for this device
+
+				devCtx.enableChangeHandler(changeNotify);
+
+				// Set the volume information, may be null
+
+				devCtx.setVolumeInformation(volInfo);
+
+				// Set the disk sizing information, may be null
+
+				devCtx.setDiskInformation(diskInfo);
+
+				// Set the share name in the context
+
+				devCtx.setShareName(name);
+
+				// Create the default file state cache type if the filesystem requires it, for backwards compatability
+				
+				if ( devCtx.requiresStateCache() && stateCache == null) {
+					stateCache = new StandaloneFileStateCache();
+					stateCache.initializeCache( new GenericConfigElement( "stateCache"), this);
+				}
+				
+				if ( devCtx.requiresStateCache() == false && stateCache != null)
+					throw new InvalidConfigurationException( "Filesystem does not use state caching");
+
+				devCtx.setStateCache( stateCache);
+				
+				// Create the disk shared device and add to the server's list of shares
+
+				DiskSharedDevice diskDev = new DiskSharedDevice(name, diskDrv, devCtx);
+				diskDev.setComment(comment);
+				diskDev.setConfiguration( this);
+
+				// Add any access controls to the share
+
+				diskDev.setAccessControlList(acls);
+
+                // Check if the filesystem uses the file state cache, if so then add to the file state reaper
+                
+                if ( devCtx.hasStateCache()) {
+                    
+                    // Register the state cache with the reaper thread
+                    
+                    filesysConfig.addFileStateCache( name, devCtx.getStateCache());
+                }
+                
+				// Start the filesystem
+
+				devCtx.startFilesystem(diskDev);
+
+				// Pass the driver/context details to the state cache
+				
+				if ( devCtx.hasStateCache())
+					devCtx.getStateCache().setDriverDetails(diskDev);
+				
+				// Add the new share to the list of available shares
+
+				filesysConfig.addShare(diskDev);
+			}
+		}
+		catch (ClassNotFoundException ex) {
+			throw new InvalidConfigurationException("Disk driver class " + getText(classElem) + " not found");
+		}
+		catch (DeviceContextException ex) {
+			throw new InvalidConfigurationException("Driver context error", ex);
+		}
+		catch (Exception ex) {
+			throw new InvalidConfigurationException("Disk share setup error", ex);
+		}
+	}
+
+	/**
+	 * Find the specified child node in the node list
+	 * 
+	 * @param name String
+	 * @param list NodeList
+	 * @return Element
+	 */
+	protected final Element findChildNode(String name, NodeList list) {
+
+		// Check if the list is valid
+
+		if ( list == null)
+			return null;
+
+		// Search for the required element
+
+		for (int i = 0; i < list.getLength(); i++) {
+
+			// Get the current child node
+
+			Node child = list.item(i);
+			if ( child.getNodeName().equals(name) && child.getNodeType() == ELEMENT_TYPE)
+				return (Element) child;
+		}
+
+		// Element not found
+
+		return null;
+	}
+
+	/**
+	 * Get the value text for the specified element
+	 * 
+	 * @param elem Element
+	 * @return String
+	 */
+	protected final String getText(Element elem) {
+
+		// Check if the element has children
+
+		NodeList children = elem.getChildNodes();
+		String text = "";
+
+		if ( children != null && children.getLength() > 0 && children.item(0).getNodeType() != ELEMENT_TYPE)
+			text = children.item(0).getNodeValue();
+
+		// Return the element text value
+
+		return text;
+	}
+
+	/**
+	 * Build a configuration element list from an elements child nodes
+	 * 
+	 * @param root Element
+	 * @return GenericConfigElement
+	 */
+	protected final GenericConfigElement buildConfigElement(Element root) {
+		return buildConfigElement(root, null);
+	}
+
+	/**
+	 * Build a configuration element list from an elements child nodes
+	 * 
+	 * @param root Element
+	 * @param cfgElem GenericConfigElement
+	 * @return GenericConfigElement
+	 */
+	protected final GenericConfigElement buildConfigElement(Element root, GenericConfigElement cfgElem) {
+
+		// Create the top level element, if not specified
+
+		GenericConfigElement rootElem = cfgElem;
+
+		if ( rootElem == null) {
+
+			// Create the root element
+
+			rootElem = new GenericConfigElement(root.getNodeName());
+
+			// Add any attributes
+
+			NamedNodeMap attribs = root.getAttributes();
+			if ( attribs != null) {
+				for (int i = 0; i < attribs.getLength(); i++) {
+					Node attribNode = attribs.item(i);
+					rootElem.addAttribute(attribNode.getNodeName(), attribNode.getNodeValue());
+				}
+			}
+		}
+
+		// Get the child node list
+
+		NodeList nodes = root.getChildNodes();
+		if ( nodes == null)
+			return rootElem;
+
+		// Process the child node list
+
+		GenericConfigElement childElem = null;
+
+		for (int i = 0; i < nodes.getLength(); i++) {
+
+			// Get the current node
+
+			Node node = nodes.item(i);
+
+			if ( node.getNodeType() == ELEMENT_TYPE) {
+
+				// Access the Element
+
+				Element elem = (Element) node;
+
+				// Check if the element has any child nodes
+
+				NodeList children = elem.getChildNodes();
+
+				if ( children != null && children.getLength() > 1) {
+
+					// Add the child nodes as child configuration elements
+
+					childElem = buildConfigElement(elem, null);
+				}
+				else {
+
+					// Create a normal name/value
+
+					if ( children.getLength() > 0) {
+						childElem = new GenericConfigElement(elem.getNodeName());
+						childElem.setValue(children.item(0).getNodeValue());
+					}
+					else
+						childElem = new GenericConfigElement(elem.getNodeName());
+
+					// Add any attributes
+
+					NamedNodeMap attribs = elem.getAttributes();
+					if ( attribs != null) {
+						for (int j = 0; j < attribs.getLength(); j++) {
+							Node attribNode = attribs.item(j);
+							childElem.addAttribute(attribNode.getNodeName(), attribNode.getNodeValue());
+						}
+					}
+				}
+
+				// Add the child configuration element
+
+				rootElem.addChild(childElem);
+			}
+		}
+
+		// Return the configuration element
+
+		return rootElem;
+	}
+
+	/**
+	 * Add a configuration element
+	 */
+	/**
+	 * Parse a platform type string into a list of platform ids
+	 * 
+	 * @param platforms String
+	 * @return List<Integer>
+	 * @exception InvalidConfigurationException
+	 */
+	protected final List<Platform.Type> parsePlatformString(String platforms)
+		throws InvalidConfigurationException {
+		// Create the list to hold the platform ids
+
+		List<Platform.Type> platformIds = new ArrayList<Platform.Type>();
+
+		if ( platforms == null)
+			return platformIds;
+
+		// Split the platform list
+
+		StringTokenizer tokens = new StringTokenizer(platforms.toUpperCase(Locale.ENGLISH), ",");
+
+		while (tokens.hasMoreTokens()) {
+
+			// Get the current platform token and validate
+
+			String platform = tokens.nextToken().trim();
+
+			// Validate the platform id
+
+			Platform.Type id = Platform.Type.Unknown;
+
+			if ( platform.equalsIgnoreCase("WINDOWS"))
+				id = Platform.Type.WINDOWS;
+			else if ( platform.equalsIgnoreCase("LINUX"))
+				id = Platform.Type.LINUX;
+			else if ( platform.equalsIgnoreCase("MACOSX"))
+				id = Platform.Type.MACOSX;
+			else if ( platform.equalsIgnoreCase("SOLARIS"))
+				id = Platform.Type.SOLARIS;
+
+			if ( id == Platform.Type.Unknown)
+				throw new InvalidConfigurationException("Invalid platform type '" + platform + "'");
+
+			// Add the platform id to the list
+
+			platformIds.add(id);
+		}
+
+		// Return the platform id list
+
+		return platformIds;
+	}
+
+	/**
+	 * Parse an adapter name string and return the matching address
+	 * 
+	 * @param adapter String
+	 * @return InetAddress
+	 * @exception InvalidConfigurationException
+	 */
+	protected final InetAddress parseAdapterName(String adapter)
+		throws InvalidConfigurationException {
+
+		NetworkInterface ni = null;
+
+		try {
+			ni = NetworkInterface.getByName(adapter);
+		}
+		catch (SocketException ex) {
+			throw new InvalidConfigurationException("Invalid adapter name, " + adapter);
+		}
+
+		if ( ni == null)
+			throw new InvalidConfigurationException("Invalid network adapter name, " + adapter);
+
+		// Get the IP address for the adapter
+
+		InetAddress adapAddr = null;
+		Enumeration<InetAddress> addrEnum = ni.getInetAddresses();
+
+		while (addrEnum.hasMoreElements() && adapAddr == null) {
+
+			// Get the current address
+
+			InetAddress addr = addrEnum.nextElement();
+			if ( IPAddress.isNumericAddress(addr.getHostAddress()))
+				adapAddr = addr;
+		}
+
+		// Check if we found the IP address to bind to
+
+		if ( adapAddr == null)
+			throw new InvalidConfigurationException("Adapter " + adapter + " does not have a valid IP address");
+
+		// Return the adapter address
+
+		return adapAddr;
+	}
+}

+ 111 - 0
src/org/alfresco/jlan/app/DriveMappingsConfigSection.java

@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2006-2010 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.alfresco.jlan.app;
+
+import org.alfresco.jlan.server.config.ConfigId;
+import org.alfresco.jlan.server.config.ConfigSection;
+import org.alfresco.jlan.server.config.InvalidConfigurationException;
+import org.alfresco.jlan.server.config.ServerConfiguration;
+import org.alfresco.jlan.smb.util.DriveMappingList;
+
+/**
+ *  Drive Mappings Configuration Section Class
+ *
+ * @author gkspencer
+ */
+public class DriveMappingsConfigSection extends ConfigSection {
+
+  // Drive mappings configuration section name
+  
+  public static final String SectionName = "DriveMappings";
+  
+  //  Win32 local drive mappings to be added when the SMB/CIFS server has started
+  
+  private DriveMappingList m_mappedDrives;
+  
+  //  Enable debug output
+  
+  private boolean m_debug;
+  
+  /**
+   * Class constructor
+   * 
+   * @param config ServerConfiguration
+   */
+  public DriveMappingsConfigSection(ServerConfiguration config) {
+    super( SectionName, config);
+  }
+
+  /**
+   * Check if debug output is enabled
+   * 
+   * @return boolean
+   */
+  public final boolean hasDebug() {
+    return m_debug;
+  }
+  
+  /**
+   * Determine if there are mapped drives specified to be added when the SMB/CIFS server has started
+   * 
+   * @return boolean
+   */
+  public final boolean hasMappedDrives() {
+    return m_mappedDrives != null ? true : false;
+  }
+
+  /**
+   * Return the mapped drives list
+   * 
+   * @return DriveMappingList
+   */
+  public final DriveMappingList getMappedDrives() {
+    return m_mappedDrives;
+  }
+  
+  /**
+   * Add a list of mapped drives
+   *
+   * @param mappedDrives DriveMappingList
+   * @return int
+   * @exception InvalidConfigurationException
+   */
+  public final int setMappedDrives(DriveMappingList mappedDrives)
+    throws InvalidConfigurationException {
+      
+    //  Inform listeners, validate the configuration change
+
+    int sts = fireConfigurationChange(ConfigId.SMBMappedDrives, mappedDrives);
+    m_mappedDrives = mappedDrives;
+    
+    //  Return the change status
+    
+    return sts;
+  }
+  
+  /**
+   * Enable/disable debug output
+   * 
+   * @param dbg boolean
+   */
+  public final void setDebug(boolean dbg) {
+    m_debug = dbg;
+  }
+}

+ 658 - 0
src/org/alfresco/jlan/app/JLANCifsServer.java

@@ -0,0 +1,658 @@
+/*
+ * Copyright (C) 2006-2010 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.alfresco.jlan.app;
+
+import java.io.File;
+import java.io.PrintStream;
+import java.lang.reflect.Constructor;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import org.alfresco.jlan.debug.Debug;
+import org.alfresco.jlan.debug.DebugConfigSection;
+import org.alfresco.jlan.netbios.server.NetBIOSNameServer;
+import org.alfresco.jlan.netbios.win32.Win32NetBIOS;
+import org.alfresco.jlan.server.NetworkServer;
+import org.alfresco.jlan.server.ServerListener;
+import org.alfresco.jlan.server.config.ServerConfiguration;
+import org.alfresco.jlan.smb.SMBErrorText;
+import org.alfresco.jlan.smb.SMBStatus;
+import org.alfresco.jlan.smb.server.CIFSConfigSection;
+import org.alfresco.jlan.smb.server.SMBServer;
+import org.alfresco.jlan.smb.util.DriveMapping;
+import org.alfresco.jlan.smb.util.DriveMappingList;
+import org.alfresco.jlan.util.ConsoleIO;
+import org.alfresco.jlan.util.Platform;
+import org.alfresco.jlan.util.win32.Win32Utils;
+
+/**
+ * JLAN CIFS Server Application
+ * 
+ * @author gkspencer
+ */
+public class JLANCifsServer implements ServerListener {
+
+	// Constants
+	//
+	// Checkpoints
+
+	public static final int CheckPointStarting = 0;
+	public static final int CheckPointConfigLoading = 1;
+	public static final int CheckPointConfigLoaded = 2;
+	public static final int CheckPointCheckIPAddress = 3;
+	public static final int CheckPointCreateSMBServer = 4;
+	public static final int CheckPointServersStart = 5;
+	public static final int CheckPointServersStarted = 6;
+	public static final int CheckPointRunning = 7;
+	public static final int CheckPointServersStop = 8;
+	public static final int CheckPointServersStopped = 9;
+	public static final int CheckPointFinished = 10;
+
+	// Default configuration file name
+
+	private static final String DEFAULT_CONFIGFILENAME = "jlanserver.xml";
+
+	// Flag to enable/disable local IP address checking
+
+	private static final boolean CheckLocalIPAddress = false;
+
+	// Server shutdown flag
+
+	protected static boolean m_shutdown = false;
+
+	// Server restart flag
+
+	protected static boolean m_restart = false;
+
+	// Flag to enable user to shutdown the server via the console
+
+	protected static boolean m_allowShutViaConsole = true;
+
+	// Flag to control output of a stacktrace if an error occurs
+
+	protected static boolean m_dumpStackOnError = true;
+
+	// Server configuration
+
+	private ServerConfiguration m_srvConfig;
+
+	/**
+	 * Start the JLAN Server
+	 * 
+	 * @param args an array of command-line arguments
+	 */
+	public static void main(String[] args) {
+
+		// Create the main JLANServer object
+
+		JLANCifsServer jlanServer = new JLANCifsServer();
+
+		// Loop until shutdown
+
+		while (m_shutdown == false) {
+
+			// Start the server
+
+			jlanServer.start(args);
+
+			// DEBUG
+
+			if ( Debug.EnableInfo && m_restart == true) {
+				Debug.println("Restarting server ...");
+				Debug.println("--------------------------------------------------");
+			}
+		}
+	}
+
+	/**
+	 * Class constructor
+	 */
+	protected JLANCifsServer() {
+	}
+
+	/**
+	 * Set/clear the allow shutdown via console flag
+	 * 
+	 * @param consoleShut boolean
+	 */
+	public static final void setAllowConsoleShutdown(boolean consoleShut) {
+		m_allowShutViaConsole = consoleShut;
+	}
+
+	/**
+	 * Enable/disable exception stack dumps
+	 * 
+	 * @param ena boolean
+	 */
+	protected final void enableExceptionStackDump(boolean ena) {
+		m_dumpStackOnError = ena;
+	}
+
+	/**
+	 * Start the JLAN Server
+	 * 
+	 * @param args String[]
+	 */
+	protected void start(String[] args) {
+
+		// Command line parameter should specify the configuration file
+
+		PrintStream out = createOutputStream();
+
+		// Clear the shutdown/restart flags
+
+		m_shutdown = true;
+		m_restart = false;
+
+		// Checkpoint - server starting
+
+		checkPoint(out, CheckPointStarting);
+
+		// Load the configuration
+
+		m_srvConfig = null;
+
+		try {
+
+			// Checkpoint - configuration loading
+
+			checkPoint(out, CheckPointConfigLoading);
+
+			// Load the configuration
+
+			m_srvConfig = loadConfiguration(out, args);
+
+			// Checkpoint - configuration loaded
+
+			checkPoint(out, CheckPointConfigLoaded);
+		}
+		catch (Exception ex) {
+
+			// Failed to load server configuration
+
+			checkPointError(out, CheckPointConfigLoading, ex);
+			return;
+		}
+
+		// Check if the local IP address returns a valid value, '127.0.0.1' indicates a mis-configuration in the hosts
+		// file
+
+		if ( CheckLocalIPAddress) {
+
+			try {
+
+				// Checkpoint - check IP address
+
+				checkPoint(out, CheckPointCheckIPAddress);
+
+				// Get the local address
+
+				String localAddr = InetAddress.getLocalHost().getHostAddress();
+				if ( localAddr.equals("127.0.0.1")) {
+					out.println("%% Local IP address resolves to 127.0.0.1, this may be caused by a mis-configured hosts file");
+					return;
+				}
+			}
+			catch (UnknownHostException ex) {
+
+				// Failed to get local host IP address details
+
+				checkPointError(out, CheckPointCheckIPAddress, ex);
+				return;
+			}
+		}
+
+		// NetBIOS name server, SMB, FTP and NFS servers
+
+		try {
+
+			// Create the SMB server and NetBIOS name server, if enabled
+
+			if ( m_srvConfig.hasConfigSection(CIFSConfigSection.SectionName)) {
+
+				// Checkpoint - create SMB/CIFS server
+
+				checkPoint(out, CheckPointCreateSMBServer);
+
+				// Get the CIFS server configuration
+
+				CIFSConfigSection cifsConfig = (CIFSConfigSection) m_srvConfig.getConfigSection(CIFSConfigSection.SectionName);
+
+				// Load the Win32 NetBIOS library
+				//
+				// For some strange reason the native code loadLibrary() call hangs if done later by the SMBServer.
+				// Forcing the Win32NetBIOS class to load here and run the static initializer fixes the problem.
+
+				if ( cifsConfig.hasWin32NetBIOS())
+					Win32NetBIOS.LanaEnumerate();
+
+				// Create the NetBIOS name server if NetBIOS SMB is enabled
+
+				if ( cifsConfig.hasNetBIOSSMB())
+					m_srvConfig.addServer(createNetBIOSServer(m_srvConfig));
+
+				// Create the SMB server
+
+				m_srvConfig.addServer(createSMBServer(m_srvConfig));
+			}
+
+			// Checkpoint - starting servers
+
+			checkPoint(out, CheckPointServersStart);
+
+			// Get the debug configuration
+
+			DebugConfigSection dbgConfig = (DebugConfigSection) m_srvConfig.getConfigSection(DebugConfigSection.SectionName);
+
+			// Start the configured servers
+
+			for (int i = 0; i < m_srvConfig.numberOfServers(); i++) {
+
+				// Get the current server
+
+				NetworkServer server = m_srvConfig.getServer(i);
+
+				// DEBUG
+
+				if ( Debug.EnableInfo && dbgConfig != null && dbgConfig.hasDebug())
+					Debug.println("Starting server " + server.getProtocolName() + " ...");
+
+				// Start the server
+
+				m_srvConfig.getServer(i).startServer();
+			}
+
+			// Checkpoint - servers started
+
+			checkPoint(out, CheckPointServersStarted);
+
+			// Check if the server is running as a service
+
+			boolean service = false;
+
+			if ( ConsoleIO.isValid() == false)
+				service = true;
+
+			// Checkpoint - servers running
+
+			checkPoint(out, CheckPointRunning);
+
+			// Wait while the server runs, user may stop or restart the server by typing a key
+
+			m_shutdown = false;
+
+			while (m_shutdown == false && m_restart == false) {
+
+				// Check if the user has requested a shutdown, if running interactively
+
+				if ( service == false && m_allowShutViaConsole) {
+
+					// Wait for the user to enter the shutdown key
+
+					int inChar = ConsoleIO.readCharacter();
+
+					if ( inChar == 'x' || inChar == 'X')
+						m_shutdown = true;
+					else if ( inChar == 'r' || inChar == 'R')
+						m_restart = true;
+					else if ( inChar == -1) {
+						
+						// Sleep for a short while
+
+						try {
+							Thread.sleep(500);
+						}
+						catch (InterruptedException ex) {
+						}
+					}
+				}
+				else {
+
+					// Sleep for a short while
+
+					try {
+						Thread.sleep(500);
+					}
+					catch (InterruptedException ex) {
+					}
+				}
+			}
+
+			// Checkpoint - servers stopping
+
+			checkPoint(out, CheckPointServersStop);
+
+			// Shutdown the servers
+
+			int idx = m_srvConfig.numberOfServers() - 1;
+
+			while (idx >= 0) {
+
+				// Get the current server
+
+				NetworkServer server = m_srvConfig.getServer(idx--);
+
+				// DEBUG
+
+				if ( Debug.EnableInfo && dbgConfig != null && dbgConfig.hasDebug())
+					Debug.println("Shutting server " + server.getProtocolName() + " ...");
+
+				// Stop the server
+
+				server.shutdownServer(false);
+			}
+
+			// Close the configuration
+			
+			m_srvConfig.closeConfiguration();
+			
+			// Checkpoint - servers stopped
+
+			checkPoint(out, CheckPointServersStopped);
+		}
+		catch (Exception ex) {
+
+			// Server error
+
+			checkPointError(out, CheckPointServersStarted, ex);
+		}
+		finally {
+
+			// Close all active servers
+
+			int idx = m_srvConfig.numberOfServers() - 1;
+
+			while (idx >= 0) {
+				NetworkServer srv = m_srvConfig.getServer(idx--);
+				if ( srv.isActive())
+					srv.shutdownServer(true);
+			}
+		}
+
+		// Checkpoint - finished
+
+		checkPoint(out, CheckPointFinished);
+	}
+
+	/**
+	 * Shutdown the server when running as an NT service
+	 * 
+	 * @param args String[]
+	 */
+	public final static void shutdownServer(String[] args) {
+		m_shutdown = true;
+	}
+
+	/**
+	 * Create the SMB server
+	 * 
+	 * @param config ServerConfiguration
+	 * @return NetworkServer
+	 * @exception Exception
+	 */
+	protected final NetworkServer createSMBServer(ServerConfiguration config)
+		throws Exception {
+
+		// Create an SMB server
+
+		NetworkServer smbServer = new SMBServer(config);
+
+		// Check if there are any drive mappings configured
+
+		if ( Platform.isPlatformType() == Platform.Type.WINDOWS
+				&& config.hasConfigSection(DriveMappingsConfigSection.SectionName))
+			smbServer.addServerListener(this);
+
+		// Return the SMB server
+
+		return smbServer;
+	}
+
+	/**
+	 * Create the NetBIOS name server
+	 * 
+	 * @param config ServerConfiguration
+	 * @return NetworkServer
+	 * @exception Exception
+	 */
+	protected final NetworkServer createNetBIOSServer(ServerConfiguration config)
+		throws Exception {
+
+		// Create a NetBIOS name server
+
+		return new NetBIOSNameServer(config);
+	}
+
+	/**
+	 * Create a network server using reflection
+	 * 
+	 * @param className String
+	 * @param config ServerConfiguration
+	 * @return NetworkServer
+	 * @exception Exception
+	 */
+	protected final NetworkServer createServer(String className, ServerConfiguration config)
+		throws Exception {
+
+		// Create the server instance using reflection
+
+		NetworkServer srv = null;
+
+		// Find the server constructor
+
+		Class<?>[] classes = new Class[1];
+		classes[0] = ServerConfiguration.class;
+		Constructor<?> srvConstructor = Class.forName(className).getConstructor(classes);
+
+		// Create the network server
+
+		Object[] args = new Object[1];
+		args[0] = config;
+		srv = (NetworkServer) srvConstructor.newInstance(args);
+
+		// Return the network server instance
+
+		return srv;
+	}
+
+	/**
+	 * Load the server configuration, default is to load using an XML configuration file.
+	 * 
+	 * @param out PrintStream
+	 * @param cmdLineArgs String[]
+	 * @return ServerConfiguration
+	 * @exception Exception
+	 */
+	protected ServerConfiguration loadConfiguration(PrintStream out, String[] cmdLineArgs)
+		throws Exception {
+
+		String fileName = null;
+
+		if ( cmdLineArgs.length < 1) {
+
+			// Search for a default configuration file in the users home directory
+
+			fileName = System.getProperty("user.home") + File.separator + DEFAULT_CONFIGFILENAME;
+		}
+		else
+			fileName = cmdLineArgs[0];
+
+		// Load the configuration
+
+		ServerConfiguration srvCfg = null;
+
+		// Create an XML configuration
+
+		srvCfg = new XMLServerConfiguration();
+		srvCfg.loadConfiguration(fileName);
+
+		// Return the server configuration
+
+		return srvCfg;
+	}
+
+	/**
+	 * Create the output stream for logging
+	 * 
+	 * @return PrintStream
+	 */
+	protected PrintStream createOutputStream() {
+		return System.out;
+	}
+
+	/**
+	 * Checkpoint method, called at various points of the server startup and shutdown
+	 * 
+	 * @param out PrintStream
+	 * @param check int
+	 */
+	protected void checkPoint(PrintStream out, int check) {
+	}
+
+	/**
+	 * Checkpoint error method, called if an error occurs during server startup/shutdown
+	 * 
+	 * @param out PrintStream
+	 * @param check int
+	 * @param ex Exception
+	 */
+	protected void checkPointError(PrintStream out, int check, Exception ex) {
+
+		// Default error output goes to the console
+
+		String msg = "%% Error occurred";
+
+		switch (check) {
+
+			// Configuration load error
+
+			case CheckPointConfigLoading:
+				msg = "%% Failed to load server configuration";
+				break;
+
+			// Checking local network address error
+
+			case CheckPointCheckIPAddress:
+				msg = "%% Failed to get local IP address details";
+				break;
+
+			//
+
+			case CheckPointServersStarted:
+				msg = "%% Server error";
+				break;
+		}
+
+		// Output the error message and a stack trace
+
+		out.println(msg);
+		if ( m_dumpStackOnError)
+			ex.printStackTrace(out);
+	}
+
+	/**
+	 * Handle server startup/shutdown events
+	 * 
+	 * @param server NetworkServer
+	 * @param event int
+	 */
+	public void serverStatusEvent(NetworkServer server, int event) {
+
+		// Check for an SMB server event
+
+		if ( server instanceof SMBServer) {
+
+			// Get the drive mappings configuration
+
+			DriveMappingsConfigSection mapConfig = (DriveMappingsConfigSection) m_srvConfig
+					.getConfigSection(DriveMappingsConfigSection.SectionName);
+			if ( mapConfig == null)
+				return;
+
+			// Check for a server startup event, add drive mappings now that the server is running
+
+			if ( event == ServerListener.ServerStartup) {
+
+				// Get the mapped drives list
+
+				DriveMappingList mapList = mapConfig.getMappedDrives();
+
+				// Add the mapped drives
+
+				for (int i = 0; i < mapList.numberOfMappings(); i++) {
+
+					// Get the current drive mapping
+
+					DriveMapping driveMap = mapList.getMappingAt(i);
+
+					// DEBUG
+
+					if ( Debug.EnableInfo && mapConfig.hasDebug())
+						Debug.println("Mapping drive " + driveMap.getLocalDrive() + " to " + driveMap.getRemotePath() + " ...");
+
+					// Create a local mapped drive to the JLAN Server
+
+					int sts = Win32Utils.MapNetworkDrive(driveMap.getRemotePath(), driveMap.getLocalDrive(), driveMap
+							.getUserName(), driveMap.getPassword(), driveMap.hasInteractive(), driveMap.hasPrompt());
+
+					// Check if the drive was mapped successfully
+
+					if ( sts != 0)
+						Debug.println("Failed to map drive " + driveMap.getLocalDrive() + " to " + driveMap.getRemotePath()
+								+ ", status = " + SMBErrorText.ErrorString(SMBStatus.Win32Err, sts));
+				}
+			}
+			else if ( event == ServerListener.ServerShutdown) {
+
+				// Get the mapped drives list
+
+				DriveMappingList mapList = mapConfig.getMappedDrives();
+
+				// Remove the mapped drives
+
+				for (int i = 0; i < mapList.numberOfMappings(); i++) {
+
+					// Get the current drive mapping
+
+					DriveMapping driveMap = mapList.getMappingAt(i);
+
+					// DEBUG
+
+					if ( Debug.EnableInfo && mapConfig.hasDebug())
+						Debug.println("Removing mapped drive " + driveMap.getLocalDrive() + " to " + driveMap.getRemotePath()
+								+ " ...");
+
+					// Remove a mapped drive
+
+					int sts = Win32Utils.DeleteNetworkDrive(driveMap.getLocalDrive(), false, true);
+
+					// Check if the drive was unmapped successfully
+
+					if ( sts != 0)
+						Debug.println("Failed to delete mapped drive " + driveMap.getLocalDrive() + " from "
+								+ driveMap.getRemotePath() + ", status = " + SMBErrorText.ErrorString(SMBStatus.Win32Err, sts));
+				}
+			}
+			// else if (( event & 0xFF) == SMBServer.CIFSNetBIOSNamesAdded)
+			// Debug.println("NetBIOS name added event, lana=" + ( event >> 16));
+		}
+	}
+
+}

+ 761 - 0
src/org/alfresco/jlan/app/JLANServer.java

@@ -0,0 +1,761 @@
+/*
+ * Copyright (C) 2006-2010 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.alfresco.jlan.app;
+
+import java.io.File;
+import java.io.PrintStream;
+import java.lang.reflect.Constructor;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import org.alfresco.jlan.debug.Debug;
+import org.alfresco.jlan.debug.DebugConfigSection;
+import org.alfresco.jlan.ftp.FTPConfigSection;
+import org.alfresco.jlan.netbios.server.NetBIOSNameServer;
+import org.alfresco.jlan.netbios.win32.Win32NetBIOS;
+import org.alfresco.jlan.oncrpc.nfs.NFSConfigSection;
+import org.alfresco.jlan.server.NetworkServer;
+import org.alfresco.jlan.server.ServerListener;
+import org.alfresco.jlan.server.config.ServerConfiguration;
+import org.alfresco.jlan.smb.SMBErrorText;
+import org.alfresco.jlan.smb.SMBStatus;
+import org.alfresco.jlan.smb.server.CIFSConfigSection;
+import org.alfresco.jlan.smb.server.SMBServer;
+import org.alfresco.jlan.smb.util.DriveMapping;
+import org.alfresco.jlan.smb.util.DriveMappingList;
+import org.alfresco.jlan.util.ConsoleIO;
+import org.alfresco.jlan.util.Platform;
+import org.alfresco.jlan.util.win32.Win32Utils;
+
+/**
+ * JLAN File Server Application
+ * 
+ * @author gkspencer
+ */
+public class JLANServer implements ServerListener {
+
+	// Constants
+	//
+	// Checkpoints
+
+	public static final int CheckPointStarting = 0;
+	public static final int CheckPointConfigLoading = 1;
+	public static final int CheckPointConfigLoaded = 2;
+	public static final int CheckPointCheckIPAddress = 3;
+	public static final int CheckPointCreateSMBServer = 4;
+	public static final int CheckPointCreateFTPServer = 5;
+	public static final int CheckPointCreateNFSServer = 6;
+	public static final int CheckPointServersStart = 7;
+	public static final int CheckPointServersStarted = 8;
+	public static final int CheckPointRunning = 9;
+	public static final int CheckPointServersStop = 10;
+	public static final int CheckPointServersStopped = 11;
+	public static final int CheckPointFinished = 12;
+
+	// Default configuration file name
+
+	private static final String DEFAULT_CONFIGFILENAME = "jlanserver.xml";
+
+	// Flag to enable/disable local IP address checking
+
+	private static final boolean CheckLocalIPAddress = false;
+
+	// Server shutdown flag
+
+	protected static boolean m_shutdown = false;
+
+	// Server restart flag
+
+	protected static boolean m_restart = false;
+
+	// Flag to enable user to shutdown the server via the console
+
+	protected static boolean m_allowShutViaConsole = true;
+
+	// Flag to control output of a stacktrace if an error occurs
+
+	protected static boolean m_dumpStackOnError = true;
+
+	// Server configuration
+
+	private ServerConfiguration m_srvConfig;
+
+	/**
+	 * Start the JLAN Server
+	 * 
+	 * @param args an array of command-line arguments
+	 */
+	public static void main(String[] args) {
+
+		// Create the main JLAN server object
+
+		JLANServer jlanServer = new JLANServer();
+
+		// Loop until shutdown
+
+		while (m_shutdown == false) {
+
+			// Start the server
+
+			jlanServer.start(args);
+
+			// DEBUG
+
+			if ( Debug.EnableInfo && m_restart == true) {
+				Debug.println("Restarting server ...");
+				Debug.println("--------------------------------------------------");
+			}
+		}
+	}
+
+	/**
+	 * Class constructor
+	 */
+	protected JLANServer() {
+	}
+
+	/**
+	 * Set/clear the allow shutdown via console flag
+	 * 
+	 * @param consoleShut boolean
+	 */
+	public static final void setAllowConsoleShutdown(boolean consoleShut) {
+		m_allowShutViaConsole = consoleShut;
+	}
+
+	/**
+	 * Enable/disable exception stack dumps
+	 * 
+	 * @param ena boolean
+	 */
+	protected final void enableExceptionStackDump(boolean ena) {
+		m_dumpStackOnError = ena;
+	}
+
+	/**
+	 * Start the JLAN Server
+	 * 
+	 * @param args String[]
+	 */
+	protected void start(String[] args) {
+
+		// Command line parameter should specify the configuration file
+
+		PrintStream out = createOutputStream();
+
+		// Clear the shutdown/restart flags
+
+		m_shutdown = true;
+		m_restart = false;
+
+		// Checkpoint - server starting
+
+		checkPoint(out, CheckPointStarting);
+
+		// Load the configuration
+
+		m_srvConfig = null;
+
+		try {
+
+			// Checkpoint - configuration loading
+
+			checkPoint(out, CheckPointConfigLoading);
+
+			// Load the configuration
+
+			m_srvConfig = loadConfiguration(out, args);
+
+			// Checkpoint - configuration loaded
+
+			checkPoint(out, CheckPointConfigLoaded);
+		}
+		catch (Exception ex) {
+
+			// Failed to load server configuration
+
+			checkPointError(out, CheckPointConfigLoading, ex);
+			return;
+		}
+
+		// Check if the local IP address returns a valid value, '127.0.0.1' indicates a mis-configuration in the hosts
+		// file
+
+		if ( CheckLocalIPAddress) {
+
+			try {
+
+				// Checkpoint - check IP address
+
+				checkPoint(out, CheckPointCheckIPAddress);
+
+				// Get the local address
+
+				String localAddr = InetAddress.getLocalHost().getHostAddress();
+				if ( localAddr.equals("127.0.0.1")) {
+					out.println("%% Local IP address resolves to 127.0.0.1, this may be caused by a mis-configured hosts file");
+					return;
+				}
+			}
+			catch (UnknownHostException ex) {
+
+				// Failed to get local host IP address details
+
+				checkPointError(out, CheckPointCheckIPAddress, ex);
+				return;
+			}
+		}
+
+		// NetBIOS name server, SMB, FTP and NFS servers
+
+		try {
+
+			// Create the SMB server and NetBIOS name server, if enabled
+
+			if ( m_srvConfig.hasConfigSection(CIFSConfigSection.SectionName)) {
+
+				// Checkpoint - create SMB/CIFS server
+
+				checkPoint(out, CheckPointCreateSMBServer);
+
+				// Get the CIFS server configuration
+
+				CIFSConfigSection cifsConfig = (CIFSConfigSection) m_srvConfig.getConfigSection(CIFSConfigSection.SectionName);
+
+				// Load the Win32 NetBIOS library
+				//
+				// For some strange reason the native code loadLibrary() call hangs if done later by the SMBServer.
+				// Forcing the Win32NetBIOS class to load here and run the static initializer fixes the problem.
+
+				if ( cifsConfig.hasWin32NetBIOS())
+					Win32NetBIOS.LanaEnumerate();
+
+				// Create the NetBIOS name server if NetBIOS SMB is enabled
+
+				if ( cifsConfig.hasNetBIOSSMB())
+					m_srvConfig.addServer(createNetBIOSServer(m_srvConfig));
+
+				// Create the SMB server
+
+				m_srvConfig.addServer(createSMBServer(m_srvConfig));
+			}
+
+			// Create the FTP server, if enabled
+
+			if ( m_srvConfig.hasConfigSection(FTPConfigSection.SectionName)) {
+
+				// Checkpoint - create FTP server
+
+				checkPoint(out, CheckPointCreateFTPServer);
+
+				// Create the FTP server
+
+				m_srvConfig.addServer(createFTPServer(m_srvConfig));
+			}
+
+			// Create the NFS server and mount server, if enabled
+
+			if ( m_srvConfig.hasConfigSection(NFSConfigSection.SectionName)) {
+
+				// Checkpoint - create NFS server
+
+				checkPoint(out, CheckPointCreateNFSServer);
+
+				// Get the NFS server configuration
+
+				NFSConfigSection nfsConfig = (NFSConfigSection) m_srvConfig.getConfigSection(NFSConfigSection.SectionName);
+
+				// Check if the port mapper is enabled
+
+				if ( nfsConfig.hasNFSPortMapper())
+					m_srvConfig.addServer(createNFSPortMapper(m_srvConfig));
+
+				// Create the mount server
+
+				m_srvConfig.addServer(createNFSMountServer(m_srvConfig));
+
+				// Create the NFS server
+
+				m_srvConfig.addServer(createNFSServer(m_srvConfig));
+			}
+
+			// Checkpoint - starting servers
+
+			checkPoint(out, CheckPointServersStart);
+
+			// Get the debug configuration
+
+			DebugConfigSection dbgConfig = (DebugConfigSection) m_srvConfig.getConfigSection(DebugConfigSection.SectionName);
+
+			// Start the configured servers
+
+			for (int i = 0; i < m_srvConfig.numberOfServers(); i++) {
+
+				// Get the current server
+
+				NetworkServer server = m_srvConfig.getServer(i);
+
+				// DEBUG
+
+				if ( Debug.EnableInfo && dbgConfig != null && dbgConfig.hasDebug())
+					Debug.println("Starting server " + server.getProtocolName() + " ...");
+
+				// Start the server
+
+				m_srvConfig.getServer(i).startServer();
+			}
+
+			// Checkpoint - servers started
+
+			checkPoint(out, CheckPointServersStarted);
+
+			// Check if the server is running as a service
+
+			boolean service = false;
+
+			if ( ConsoleIO.isValid() == false)
+				service = true;
+
+			// Checkpoint - servers running
+
+			checkPoint(out, CheckPointRunning);
+
+			// Wait while the server runs, user may stop or restart the server by typing a key
+
+			m_shutdown = false;
+
+			while (m_shutdown == false && m_restart == false) {
+
+				// Check if the user has requested a shutdown, if running interactively
+
+				if ( service == false && m_allowShutViaConsole) {
+
+					// Wait for the user to enter the shutdown key
+
+					int inChar = ConsoleIO.readCharacter();
+
+					if ( inChar == 'x' || inChar == 'X')
+						m_shutdown = true;
+					else if ( inChar == 'r' || inChar == 'R')
+						m_restart = true;
+					else if ( inChar == 'g' || inChar == 'G') {
+						Debug.println( "Running garbage collection ...");
+						System.gc();
+					}
+					else if ( inChar == -1) {
+						
+						// Sleep for a short while
+
+						try {
+							Thread.sleep(500);
+						}
+						catch (InterruptedException ex) {
+						}
+					}
+				}
+				else {
+
+					// Sleep for a short while
+
+					try {
+						Thread.sleep(500);
+					}
+					catch (InterruptedException ex) {
+					}
+				}
+			}
+
+			// Checkpoint - servers stopping
+
+			checkPoint(out, CheckPointServersStop);
+
+			// Shutdown the servers
+
+			int idx = m_srvConfig.numberOfServers() - 1;
+
+			while (idx >= 0) {
+
+				// Get the current server
+
+				NetworkServer server = m_srvConfig.getServer(idx--);
+
+				// DEBUG
+
+				if ( Debug.EnableInfo && dbgConfig != null && dbgConfig.hasDebug())
+					Debug.println("Shutting server " + server.getProtocolName() + " ...");
+
+				// Stop the server
+
+				server.shutdownServer(false);
+			}
+
+			// Close the configuration
+			
+			m_srvConfig.closeConfiguration();
+			
+			// Checkpoint - servers stopped
+
+			checkPoint(out, CheckPointServersStopped);
+		}
+		catch (Exception ex) {
+
+			// Server error
+
+			checkPointError(out, CheckPointServersStarted, ex);
+		}
+		finally {
+
+			// Close all active servers
+
+			int idx = m_srvConfig.numberOfServers() - 1;
+
+			while (idx >= 0) {
+				NetworkServer srv = m_srvConfig.getServer(idx--);
+				if ( srv.isActive())
+					srv.shutdownServer(true);
+			}
+		}
+
+		// Checkpoint - finished
+
+		checkPoint(out, CheckPointFinished);
+	}
+
+	/**
+	 * Shutdown the server when running as an NT service
+	 * 
+	 * @param args String[]
+	 */
+	public final static void shutdownServer(String[] args) {
+		m_shutdown = true;
+	}
+
+	/**
+	 * Create the SMB server
+	 * 
+	 * @param config ServerConfiguration
+	 * @return NetworkServer
+	 * @exception Exception
+	 */
+	protected final NetworkServer createSMBServer(ServerConfiguration config)
+		throws Exception {
+
+		// Create an SMB server
+
+		NetworkServer smbServer = new SMBServer(config);
+
+		// Check if there are any drive mappings configured
+
+		if ( Platform.isPlatformType() == Platform.Type.WINDOWS
+				&& config.hasConfigSection(DriveMappingsConfigSection.SectionName))
+			smbServer.addServerListener(this);
+
+		// Return the SMB server
+
+		return smbServer;
+	}
+
+	/**
+	 * Create the NetBIOS name server
+	 * 
+	 * @param config ServerConfiguration
+	 * @return NetworkServer
+	 * @exception Exception
+	 */
+	protected final NetworkServer createNetBIOSServer(ServerConfiguration config)
+		throws Exception {
+
+		// Create a NetBIOS name server
+
+		return new NetBIOSNameServer(config);
+	}
+
+	/**
+	 * Create the FTP server
+	 * 
+	 * @param config ServerConfiguration
+	 * @return NetworkServer
+	 * @exception Exception
+	 */
+	protected final NetworkServer createFTPServer(ServerConfiguration config)
+		throws Exception {
+
+		// Create the FTP server instance
+
+		return createServer("org.alfresco.jlan.ftp.FTPServer", config);
+	}
+
+	/**
+	 * Create the NFS server
+	 * 
+	 * @param config ServerConfiguration
+	 * @return NetworkServer
+	 * @exception Exception
+	 */
+	protected final NetworkServer createNFSServer(ServerConfiguration config)
+		throws Exception {
+
+		// Create the NFS server instance
+
+		return createServer("org.alfresco.jlan.oncrpc.nfs.NFSServer", config);
+	}
+
+	/**
+	 * Create the NFS mount server
+	 * 
+	 * @param config ServerConfiguration
+	 * @return NetworkServer
+	 * @exception Exception
+	 */
+	protected final NetworkServer createNFSMountServer(ServerConfiguration config)
+		throws Exception {
+
+		// Create the mount server instance
+
+		return createServer("org.alfresco.jlan.oncrpc.mount.MountServer", config);
+	}
+
+	/**
+	 * Create the NFS port mapper server
+	 * 
+	 * @param config ServerConfiguration
+	 * @return NetworkServer
+	 */
+	protected final NetworkServer createNFSPortMapper(ServerConfiguration config)
+		throws Exception {
+
+		// Create the port mapper server instance
+
+		return createServer("org.alfresco.jlan.oncrpc.portmap.PortMapperServer", config);
+	}
+
+	/**
+	 * Create a network server using reflection
+	 * 
+	 * @param className String
+	 * @param config ServerConfiguration
+	 * @return NetworkServer
+	 * @exception Exception
+	 */
+	protected final NetworkServer createServer(String className, ServerConfiguration config)
+		throws Exception {
+
+		// Create the server instance using reflection
+
+		NetworkServer srv = null;
+
+		// Find the server constructor
+
+		Class<?>[] classes = new Class[1];
+		classes[0] = ServerConfiguration.class;
+		Constructor<?> srvConstructor = Class.forName(className).getConstructor(classes);
+
+		// Create the network server
+
+		Object[] args = new Object[1];
+		args[0] = config;
+		srv = (NetworkServer) srvConstructor.newInstance(args);
+
+		// Return the network server instance
+
+		return srv;
+	}
+
+	/**
+	 * Load the server configuration, default is to load using an XML configuration file.
+	 * 
+	 * @param out PrintStream
+	 * @param cmdLineArgs String[]
+	 * @return ServerConfiguration
+	 * @exception Exception
+	 */
+	protected ServerConfiguration loadConfiguration(PrintStream out, String[] cmdLineArgs)
+		throws Exception {
+
+		String fileName = null;
+
+		if ( cmdLineArgs.length < 1) {
+
+			// Search for a default configuration file in the users home directory
+
+			fileName = System.getProperty("user.home") + File.separator + DEFAULT_CONFIGFILENAME;
+		}
+		else
+			fileName = cmdLineArgs[0];
+
+		// Load the configuration
+
+		ServerConfiguration srvConfig = null;
+
+		// Create an XML configuration
+
+		srvConfig = new XMLServerConfiguration();
+		srvConfig.loadConfiguration(fileName);
+
+		// Return the server configuration
+
+		return srvConfig;
+	}
+
+	/**
+	 * Create the output stream for logging
+	 * 
+	 * @return PrintStream
+	 */
+	protected PrintStream createOutputStream() {
+		return System.out;
+	}
+
+	/**
+	 * Checkpoint method, called at various points of the server startup and shutdown
+	 * 
+	 * @param out PrintStream
+	 * @param check int
+	 */
+	protected void checkPoint(PrintStream out, int check) {
+	}
+
+	/**
+	 * Checkpoint error method, called if an error occurs during server startup/shutdown
+	 * 
+	 * @param out PrintStream
+	 * @param check int
+	 * @param ex Exception
+	 */
+	protected void checkPointError(PrintStream out, int check, Exception ex) {
+
+		// Default error output goes to the console
+
+		String msg = "%% Error occurred";
+
+		switch (check) {
+
+			// Configuration load error
+
+			case CheckPointConfigLoading:
+				msg = "%% Failed to load server configuration";
+				break;
+
+			// Checking local network address error
+
+			case CheckPointCheckIPAddress:
+				msg = "%% Failed to get local IP address details";
+				break;
+
+			//
+
+			case CheckPointServersStarted:
+				msg = "%% Server error";
+				break;
+		}
+
+		// Output the error message and a stack trace
+
+		out.println(msg);
+		if ( m_dumpStackOnError)
+			ex.printStackTrace(out);
+	}
+
+	/**
+	 * Handle server startup/shutdown events
+	 * 
+	 * @param server NetworkServer
+	 * @param event int
+	 */
+	public void serverStatusEvent(NetworkServer server, int event) {
+
+		// Check for an SMB server event
+
+		if ( server instanceof SMBServer) {
+
+			// Get the drive mappings configuration
+
+			DriveMappingsConfigSection mapConfig = (DriveMappingsConfigSection) m_srvConfig
+					.getConfigSection(DriveMappingsConfigSection.SectionName);
+			if ( mapConfig == null)
+				return;
+
+			// Check for a server startup event, add drive mappings now that the server is running
+
+			if ( event == ServerListener.ServerStartup) {
+
+				// Get the mapped drives list
+
+				DriveMappingList mapList = mapConfig.getMappedDrives();
+
+				// Add the mapped drives
+
+				for (int i = 0; i < mapList.numberOfMappings(); i++) {
+
+					// Get the current drive mapping
+
+					DriveMapping driveMap = mapList.getMappingAt(i);
+
+					// DEBUG
+
+					if ( Debug.EnableInfo && mapConfig.hasDebug())
+						Debug.println("Mapping drive " + driveMap.getLocalDrive() + " to " + driveMap.getRemotePath() + " ...");
+
+					// Create a local mapped drive to the JLAN Server
+
+					int sts = Win32Utils.MapNetworkDrive(driveMap.getRemotePath(), driveMap.getLocalDrive(), driveMap
+							.getUserName(), driveMap.getPassword(), driveMap.hasInteractive(), driveMap.hasPrompt());
+
+					// Check if the drive was mapped successfully
+
+					if ( sts != 0)
+						Debug.println("Failed to map drive " + driveMap.getLocalDrive() + " to " + driveMap.getRemotePath()
+								+ ", status = " + SMBErrorText.ErrorString(SMBStatus.Win32Err, sts));
+				}
+			}
+			else if ( event == ServerListener.ServerShutdown) {
+
+				// Get the mapped drives list
+
+				DriveMappingList mapList = mapConfig.getMappedDrives();
+
+				// Remove the mapped drives
+
+				for (int i = 0; i < mapList.numberOfMappings(); i++) {
+
+					// Get the current drive mapping
+
+					DriveMapping driveMap = mapList.getMappingAt(i);
+
+					// DEBUG
+
+					if ( Debug.EnableInfo && mapConfig.hasDebug())
+						Debug.println("Removing mapped drive " + driveMap.getLocalDrive() + " to " + driveMap.getRemotePath()
+								+ " ...");
+
+					// Remove a mapped drive
+
+					int sts = Win32Utils.DeleteNetworkDrive(driveMap.getLocalDrive(), false, true);
+
+					// Check if the drive was unmapped successfully
+
+					if ( sts != 0)
+						Debug.println("Failed to delete mapped drive " + driveMap.getLocalDrive() + " from "
+								+ driveMap.getRemotePath() + ", status = " + SMBErrorText.ErrorString(SMBStatus.Win32Err, sts));
+				}
+			}
+		}
+	}
+}

+ 464 - 0
src/org/alfresco/jlan/app/JLANServerService.java

@@ -0,0 +1,464 @@
+/*
+ * Copyright (C) 2006-2010 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.alfresco.jlan.app;
+
+import java.io.File;
+import java.io.PrintStream;
+import java.lang.reflect.Constructor;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import org.alfresco.jlan.debug.Debug;
+import org.alfresco.jlan.debug.DebugConfigSection;
+import org.alfresco.jlan.ftp.FTPConfigSection;
+import org.alfresco.jlan.netbios.server.NetBIOSNameServer;
+import org.alfresco.jlan.netbios.win32.Win32NetBIOS;
+import org.alfresco.jlan.oncrpc.nfs.NFSConfigSection;
+import org.alfresco.jlan.server.NetworkServer;
+import org.alfresco.jlan.server.config.ServerConfiguration;
+import org.alfresco.jlan.smb.server.CIFSConfigSection;
+import org.alfresco.jlan.smb.server.SMBServer;
+import org.tanukisoftware.wrapper.WrapperListener;
+import org.tanukisoftware.wrapper.WrapperManager;
+
+
+/**
+ * JLAN Server Service Class
+ *
+ * @author gkspencer
+ */
+public class JLANServerService implements WrapperListener, Runnable {
+
+	//	Default configuration file name
+	
+	private static final String DEFAULT_CONFIGFILENAME = "jlanserver.xml";
+	
+	//	Server shutdown flag
+	
+	private boolean m_shutdown;
+
+	//	Server configuration
+	
+	private ServerConfiguration m_config;
+	
+	//	Thread used to start the various servers
+	
+	private Thread m_serverThread;
+			
+	/**
+	 * Service start requested
+	 * 
+	 * @param args String[]
+	 * @return Integer 
+	 */
+	public Integer start(String[] args) {
+
+		//  Command line parameter should specify the configuration file
+
+		PrintStream out = System.out;
+		String fileName = null;
+
+		if (args.length < 1) {
+
+			//	Search for a default configuration file in the users home directory
+			
+			fileName = System.getProperty("user.home") + File.separator + DEFAULT_CONFIGFILENAME;
+		}
+		else
+			fileName = args[0];
+
+		//  Load the configuration
+
+		m_config = null;
+		
+		try {
+
+			//	Create an XML configuration
+
+			m_config = new XMLServerConfiguration();
+			m_config.loadConfiguration(fileName);
+		}
+		catch (Exception ex) {
+
+			//  Failed to load server configuration
+
+			out.println("%% Failed to load server configuration");
+			ex.printStackTrace(out);
+			return new Integer(2);
+		}
+
+		//	Check if the local IP address returns a valid value, '127.0.0.1' indicates a mis-configuration in the hosts file
+		
+		try {
+			
+			//	Get the local address
+			
+			String localAddr = InetAddress.getLocalHost().getHostAddress();
+			if ( localAddr.equals("127.0.0.1")) {
+				out.println("%% Local IP address resolves to 127.0.0.1, this may be caused by a mis-configured hosts file");
+				return new Integer(3);
+			}
+		}
+		catch (UnknownHostException ex) {
+			
+			//	Failed to get local host IP address details
+			
+			out.println("%% Failed to get local IP address details");
+			ex.printStackTrace(out);
+			return new Integer(4);
+		}
+		
+    //  NetBIOS name server, SMB, FTP and NFS servers
+
+    try {
+
+      //  Create the SMB server and NetBIOS name server, if enabled
+      
+      if ( m_config.hasConfigSection( CIFSConfigSection.SectionName)) {
+        
+        // Get the CIFS server configuration
+        
+        CIFSConfigSection cifsConfig = (CIFSConfigSection) m_config.getConfigSection( CIFSConfigSection.SectionName);
+        
+        //  Load the Win32 NetBIOS library
+        //
+        //  For some strange reason the native code loadLibrary() call hangs if done later by the SMBServer.
+        //  Forcing the Win32NetBIOS class to load here and run the static initializer fixes the problem.
+
+        if ( cifsConfig.hasWin32NetBIOS())
+          Win32NetBIOS.LanaEnumerate();
+        
+        //  Create the NetBIOS name server if NetBIOS SMB is enabled
+        
+        if  (cifsConfig.hasNetBIOSSMB())
+          m_config.addServer( createNetBIOSServer(m_config));
+
+        //  Create the SMB server
+        
+        m_config.addServer( createSMBServer(m_config));
+      }
+
+      //  Create the FTP server, if enabled
+      
+      if ( m_config.hasConfigSection( FTPConfigSection.SectionName)) {
+        
+        //  Create the FTP server
+      
+        m_config.addServer( createFTPServer( m_config));
+      }
+        
+      //  Create the NFS server and mount server, if enabled
+      
+      if ( m_config.hasConfigSection( NFSConfigSection.SectionName)) {
+        
+        //  Get the NFS server configuration
+        
+        NFSConfigSection nfsConfig = (NFSConfigSection) m_config.getConfigSection( NFSConfigSection.SectionName);
+        
+        //  Check if the port mapper is enabled
+        
+        if ( nfsConfig.hasNFSPortMapper())
+          m_config.addServer( createNFSPortMapper( m_config));
+          
+        //  Create the mount server
+        
+        m_config.addServer( createNFSMountServer( m_config));
+        
+        //  Create the NFS server
+        
+        m_config.addServer( createNFSServer( m_config));
+      }
+
+			//	Start the configured servers in a seperate thread
+			
+			m_serverThread = new Thread(this);
+			m_serverThread.start();
+		}
+		catch (Exception ex) {
+			out.println("%% Server error");
+			ex.printStackTrace(out);
+			return new Integer(5);
+		}
+
+		//	Indicate that the service started
+		
+		return null;
+	}
+
+	/**
+	 * Service stop requested
+	 * 
+	 * @param exitCode int
+	 * @return int 
+	 */
+	public int stop(int exitCode) {
+
+		//	Set the shutdown flag
+		
+		m_shutdown = true;
+		
+    //  Get the debug configuration
+    
+    DebugConfigSection dbgConfig = (DebugConfigSection) m_config.getConfigSection( DebugConfigSection.SectionName);
+    
+		//	Check if the server list is valid
+		
+		if ( m_config.numberOfServers() > 0) {
+
+			//	Shutdown the servers
+				
+			for ( int i = 0; i < m_config.numberOfServers(); i++) {
+					
+				//	Indicate that the service is stopping
+				
+				WrapperManager.signalStopping(5000);
+				
+				//	Get the current server
+						
+				NetworkServer server = m_config.getServer(i);
+						
+				//	DEBUG
+						
+				if ( Debug.EnableInfo && dbgConfig != null && dbgConfig.hasDebug())
+					Debug.println("Shutting server " + server.getProtocolName() + " ...");
+							
+				//	Start the server
+					
+				m_config.getServer(i).shutdownServer(false);
+			}
+		}
+
+		//	Indicate that the service is stopped
+		
+		WrapperManager.signalStopped(5000);
+				
+		//	Return the status code
+		
+		return exitCode;
+	}
+
+	/**
+	 * Handle control events
+	 * 
+	 * @param event int
+	 */
+	public void controlEvent(int event) {
+		
+		//	Check if the wrapper manager is handling events
+		
+		if ( WrapperManager.isControlledByNativeWrapper() == false) {
+			
+			//	The wrapper manager is not handling events, handle it here
+			
+			if ( event == WrapperManager.WRAPPER_CTRL_C_EVENT ||
+					 event == WrapperManager.WRAPPER_CTRL_CLOSE_EVENT ||
+					 event == WrapperManager.WRAPPER_CTRL_SHUTDOWN_EVENT) {
+					 	
+				//	Stop the service
+			
+				WrapperManager.stop(0);
+		  }
+		}
+	}
+
+	/**
+	 * Main application startup
+	 * 
+	 * @param args String[]
+	 */
+	public static void main(String[] args) {
+		
+		//	Start the main JLAN Server application via the service wrapper
+		
+		WrapperManager.start( new JLANServerService(), args);
+	}
+
+	/**
+	 * Create the SMB server
+	 * 
+	 * @param config ServerConfiguration
+	 * @return NetworkServer
+	 * @exception Exception
+	 */
+	protected final static NetworkServer createSMBServer(ServerConfiguration config)
+		throws Exception {
+			
+		//	Create an SMB server
+		
+		return new SMBServer(config);
+	}
+	
+	/**
+	 * Create the NetBIOS name server
+	 * 
+	 * @param config ServerConfiguration
+	 * @return NetworkServer
+	 * @exception Exception
+	 */
+	protected final static NetworkServer createNetBIOSServer(ServerConfiguration config)
+		throws Exception {
+			
+		//	Create a NetBIOS name server
+		
+		return new NetBIOSNameServer(config);
+	}
+	
+	/**
+	 * Create the FTP server
+	 * 
+	 * @param config ServerConfiguration
+	 * @return NetworkServer
+	 * @exception Exception
+	 */
+	protected final static NetworkServer createFTPServer(ServerConfiguration config)
+		throws Exception {
+			
+		//	Create an FTP server
+		
+		return createServer( "org.alfresco.jlan.ftp.FTPServer", config);
+	}
+	
+	/**
+	 * Create the NFS server
+	 * 
+	 * @param config ServerConfiguration
+	 * @return NetworkServer
+	 * @exception Exception
+	 */
+	protected final static NetworkServer createNFSServer(ServerConfiguration config)
+		throws Exception {
+			
+    //  Create the NFS server instance
+    
+    return createServer( "org.alfresco.jlan.oncrpc.nfs.NFSServer", config);
+	}
+	
+	/**
+	 * Create the NFS mount server
+	 * 
+	 * @param config ServerConfiguration
+	 * @return NetworkServer
+	 * @exception Exception
+	 */
+	protected final static NetworkServer createNFSMountServer(ServerConfiguration config)
+		throws Exception {
+			
+    //  Create the mount server instance
+    
+    return createServer( "org.alfresco.jlan.oncrpc.mount.MountServer", config);
+	}
+
+	/**
+	 * Create the NFS port mapper server
+	 * 
+	 * @param config ServerConfiguration
+	 * @return NetworkServer
+	 */
+	protected final static NetworkServer createNFSPortMapper(ServerConfiguration config)
+		throws Exception {
+			
+    //  Create the port mapper server instance
+    
+    return createServer( "org.alfresco.jlan.oncprc.portmap.PortMapperServer", config);
+	}
+	
+	/**
+	 * Create a network server using reflection
+	 * 
+	 * @param className String
+	 * @param config ServerConfiguration
+	 * @return NetworkServer
+	 * @exception Exception
+	 */
+	protected final static NetworkServer createServer(String className, ServerConfiguration config)
+		throws Exception {
+
+		//	Create the server instance using reflection
+	
+		NetworkServer srv = null;
+	
+		//	Find the server constructor
+	
+		Class<?>[] classes = new Class[1];
+		classes[0] = ServerConfiguration.class;
+		Constructor<?> srvConstructor = Class.forName(className).getConstructor(classes);
+	
+		//	Create the network server
+	
+		Object[] args = new Object[1];
+		args[0] = config;
+		srv = (NetworkServer) srvConstructor.newInstance(args);
+
+		//	Return the network server instance
+		
+		return srv;
+	}
+
+	/**
+	 * Thread method 
+	 */
+	public void run() {
+
+		//	Check if there are any servers configured
+		
+		if ( m_config.numberOfServers() > 0) {
+
+			//	Clear the shutdown flag
+			
+			m_shutdown = false;
+			
+      //  Get the debug configuration
+      
+      DebugConfigSection dbgConfig = (DebugConfigSection) m_config.getConfigSection( DebugConfigSection.SectionName);
+      
+			//	Start the servers
+			
+			for ( int i = 0; i < m_config.numberOfServers(); i++) {
+					
+				//	Indicate that the servers are starting
+					
+				WrapperManager.signalStarting(10000);
+					
+				//	Get the current server
+					
+				NetworkServer server = m_config.getServer(i);
+					
+				//	DEBUG
+					
+				if ( Debug.EnableInfo && dbgConfig != null && dbgConfig.hasDebug())
+					Debug.println("Starting server " + server.getProtocolName() + " ...");
+						
+				//	Start the server
+					
+				m_config.getServer(i).startServer();
+			}
+			
+			//	Wait for shutdown request
+			
+			while ( m_shutdown == false) {
+				try {
+					Thread.sleep(250);
+				}
+				catch (Exception ex) {
+				}
+			}
+		}
+	}
+}

+ 89 - 0
src/org/alfresco/jlan/app/Portmap.java

@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2006-2010 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.alfresco.jlan.app;
+
+import org.alfresco.jlan.debug.Debug;
+import org.alfresco.jlan.oncrpc.nfs.NFSConfigSection;
+import org.alfresco.jlan.oncrpc.portmap.PortMapperServer;
+import org.alfresco.jlan.server.config.ServerConfiguration;
+import org.alfresco.jlan.util.ConsoleIO;
+
+/**
+ * Portmapper service class
+ * 
+ * @author gkspencer
+ */
+public class Portmap {
+
+	/**
+	 * Main application
+	 * 
+	 * @param args String[]
+	 */
+	public static void main(String[] args) {
+		
+		try {
+			
+			// Create the default configuration
+			
+			ServerConfiguration srvConfig = new ServerConfiguration( "PORTMAP");
+			NFSConfigSection nfsConfig = new NFSConfigSection(srvConfig);
+			
+			nfsConfig.setPortMapperDebug( true);
+			
+			// Create the portmapper service
+			
+			PortMapperServer portMapper = new PortMapperServer( srvConfig);
+			
+			// Start the portmapper
+			
+			portMapper.startServer();
+			
+			//  Wait while the server runs, user may stop server by typing a key
+
+			boolean shutdown = false;
+		      
+			while (shutdown == false) {
+						
+				//	Check if the user has requested a shutdown, if running interactively
+						 
+				int inChar = ConsoleIO.readCharacter();
+		          
+				if ( inChar == 'x' || inChar == 'X')
+					shutdown = true;
+						  
+				//	Sleep for a short while
+							
+				try {
+					Thread.sleep(500);
+				}
+				catch (InterruptedException ex) {
+				}
+			}
+			
+			// Shutdown the portmapper service
+			
+			portMapper.shutdownServer( false);
+		}
+		catch (Exception ex) {
+			Debug.println( ex);
+		}
+	}
+}

+ 1022 - 0
src/org/alfresco/jlan/app/XMLServerConfiguration.java

@@ -0,0 +1,1022 @@
+/*
+ * Copyright (C) 2006-2010 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.alfresco.jlan.app;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.util.StringTokenizer;
+
+import org.alfresco.jlan.ftp.FTPConfigSection;
+import org.alfresco.jlan.ftp.FTPPath;
+import org.alfresco.jlan.ftp.FTPSiteInterface;
+import org.alfresco.jlan.ftp.InvalidPathException;
+import org.alfresco.jlan.oncrpc.nfs.NFSConfigSection;
+import org.alfresco.jlan.server.config.InvalidConfigurationException;
+import org.alfresco.jlan.server.filesys.cache.hazelcast.ClusterConfigSection;
+import org.springframework.extensions.config.ConfigElement;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+/**
+ * XML File Server Configuration Class
+ * 
+ * <p>
+ * XML implementation of the SMB server configuration. Save/load the server configuration to an XML
+ * format file using the DOM API.
+ * 
+ * @author gkspencer
+ */
+public class XMLServerConfiguration extends CifsOnlyXMLServerConfiguration {
+
+	// Constants
+	//
+	// Default FTP server port and anonymous account name
+
+	private static final int DEFAULT_FTP_PORT = 21;
+	private static final String ANONYMOUS_FTP_ACCOUNT = "anonymous";
+
+	// FTP server debug type strings
+
+	private static final String m_ftpDebugStr[] = { "STATE", "RXDATA", "TXDATA", "DUMPDATA", "SEARCH", "INFO", "FILE", "FILEIO",
+			"ERROR", "PKTTYPE", "TIMING", "DATAPORT", "DIRECTORY", "SSL" };
+
+	// NFS server debug type strings
+
+	private static final String m_nfsDebugStr[] = { "RXDATA", "TXDATA", "DUMPDATA", "SEARCH", "INFO", "FILE", "FILEIO", "ERROR",
+			"TIMING", "DIRECTORY", "SESSION" };
+
+	// Global server enable flags
+
+	private boolean m_cifsEnabled;
+	private boolean m_ftpEnabled;
+	private boolean m_nfsEnabled;
+
+	/**
+	 * Default constructor
+	 */
+	public XMLServerConfiguration() {
+		super();
+	}
+
+	/**
+	 * Load the configuration from the specified document
+	 * 
+	 * @param doc Document
+	 * @exception IOException
+	 * @exception InvalidConfigurationException
+	 */
+	public void loadConfiguration(Document doc)
+		throws IOException, InvalidConfigurationException {
+
+		// Reset the current configuration to the default settings
+
+		removeAllConfigSections();
+
+		// Parse the XML configuration document
+
+		try {
+
+			// Access the root of the XML document, get a list of the child nodes
+
+			Element root = doc.getDocumentElement();
+			NodeList childNodes = root.getChildNodes();
+
+			// Process the cluster settings element
+
+			procClusterElement(findChildNode("cluster", childNodes));
+			
+			// Process the debug settings element
+
+			procDebugElement(findChildNode("debug", childNodes));
+
+			// Process the main server enable element
+
+			procServersElement(findChildNode("servers", childNodes));
+
+			// Process the core server configuration settings
+			
+			procServerCoreElement(findChildNode("server-core", childNodes));
+			
+			// Process the global configuration settings
+
+			procGlobalElement(findChildNode("global", childNodes));
+
+			// Process the security element
+
+			procSecurityElement(findChildNode("security", childNodes));
+
+			// Process the shares element
+
+			procSharesElement(findChildNode("shares", childNodes));
+
+			// Process the SMB server specific settings
+
+			if ( isCIFSServerEnabled())
+				procSMBServerElement(findChildNode("SMB", childNodes));
+
+			// Process the FTP server configuration
+
+			if ( isFTPServerEnabled())
+				procFTPServerElement(findChildNode("FTP", childNodes));
+
+			// Process the NFS server configuration
+
+			if ( isNFSServerEnabled())
+				procNFSServerElement(findChildNode("NFS", childNodes));
+		}
+		catch (Exception ex) {
+
+			// Rethrow the exception as a configuration exeception
+
+			throw new InvalidConfigurationException("XML error", ex);
+		}
+	}
+
+	/**
+	 * Check if the CIFS server is enabled
+	 * 
+	 * @return boolean
+	 */
+	public final boolean isCIFSServerEnabled() {
+		return m_cifsEnabled;
+	}
+
+	/**
+	 * Check if the FTP server is enabled
+	 * 
+	 * @return boolean
+	 */
+	public final boolean isFTPServerEnabled() {
+		return m_ftpEnabled;
+	}
+
+	/**
+	 * Check if the NFS server is enabled
+	 * 
+	 * @return boolean
+	 */
+	public final boolean isNFSServerEnabled() {
+		return m_nfsEnabled;
+	}
+
+	/**
+	 * Process the servers XML element
+	 * 
+	 * @param servers Element
+	 * @exception InvalidConfigurationException
+	 */
+	protected final void procServersElement(Element servers)
+		throws InvalidConfigurationException {
+
+		// Check if the servers element has been specified, if not then this is an old format
+		// configuration
+
+		if ( servers != null) {
+
+			// Check if the SMB server is enabled
+
+			if ( findChildNode("SMB", servers.getChildNodes()) != null || findChildNode("CIFS", servers.getChildNodes()) != null)
+				m_cifsEnabled = true;
+
+			// Check if the FTP server is enabled
+
+			if ( findChildNode("FTP", servers.getChildNodes()) != null)
+				m_ftpEnabled = true;
+
+			// Check if the NFS server is enabled
+
+			if ( findChildNode("NFS", servers.getChildNodes()) != null)
+				m_nfsEnabled = true;
+		}
+	}
+
+	/**
+	 * Process the FTP server XML element
+	 * 
+	 * @param ftp Element
+	 * @exception InvalidConfigurationException
+	 */
+	protected final void procFTPServerElement(Element ftp)
+		throws InvalidConfigurationException {
+
+		// Check if the FTP element is valid, if not then disable the FTP server
+
+		if ( ftp == null) {
+
+			// Check if the FTP server is enabled, if so then there must be an FTP configuration
+			// section
+
+			if ( isFTPServerEnabled())
+				throw new InvalidConfigurationException("FTP server enabled, but not configured");
+			return;
+		}
+
+		// Create the FTP server configuration section
+
+		FTPConfigSection ftpConfig = new FTPConfigSection(this);
+
+		// Check for a bind address
+
+		Element elem = findChildNode("bindto", ftp.getChildNodes());
+		if ( elem != null) {
+
+			// Check if the network adapter name has been specified
+
+			if ( elem.hasAttribute("adapter")) {
+
+				// Get the IP address for the adapter
+
+				InetAddress bindAddr = parseAdapterName(elem.getAttribute("adapter"));
+
+				// Set the bind address for the server
+
+				ftpConfig.setFTPBindAddress(bindAddr);
+			}
+			else {
+
+				// Validate the bind address
+
+				String bindText = getText(elem);
+
+				try {
+
+					// Check the bind address
+
+					InetAddress bindAddr = InetAddress.getByName(bindText);
+
+					// Set the bind address for the FTP server
+
+					ftpConfig.setFTPBindAddress(bindAddr);
+				}
+				catch (UnknownHostException ex) {
+					throw new InvalidConfigurationException(ex.toString());
+				}
+			}
+		}
+
+		// Check for an FTP server port
+
+		elem = findChildNode("port", ftp.getChildNodes());
+		if ( elem != null) {
+			try {
+				ftpConfig.setFTPPort(Integer.parseInt(getText(elem)));
+				if ( ftpConfig.getFTPPort() <= 0 || ftpConfig.getFTPPort() >= 65535)
+					throw new InvalidConfigurationException("FTP server port out of valid range");
+			}
+			catch (NumberFormatException ex) {
+				throw new InvalidConfigurationException("Invalid FTP server port");
+			}
+		}
+		else {
+
+			// Use the default FTP port
+
+			ftpConfig.setFTPPort(DEFAULT_FTP_PORT);
+		}
+
+		// Check if anonymous login is allowed
+
+		elem = findChildNode("allowAnonymous", ftp.getChildNodes());
+		if ( elem != null) {
+
+			// Enable anonymous login to the FTP server
+
+			ftpConfig.setAllowAnonymousFTP(true);
+
+			// Check if an anonymous account has been specified
+
+			String anonAcc = elem.getAttribute("user");
+			if ( anonAcc != null && anonAcc.length() > 0) {
+
+				// Set the anonymous account name
+
+				ftpConfig.setAnonymousFTPAccount(anonAcc);
+
+				// Check if the anonymous account name is valid
+
+				if ( ftpConfig.getAnonymousFTPAccount() == null || ftpConfig.getAnonymousFTPAccount().length() == 0)
+					throw new InvalidConfigurationException("Anonymous FTP account invalid");
+			}
+			else {
+
+				// Use the default anonymous account name
+
+				ftpConfig.setAnonymousFTPAccount(ANONYMOUS_FTP_ACCOUNT);
+			}
+		}
+		else {
+
+			// Disable anonymous logins
+
+			ftpConfig.setAllowAnonymousFTP(false);
+		}
+
+		// Check if a root path has been specified
+
+		elem = findChildNode("rootDirectory", ftp.getChildNodes());
+		if ( elem != null) {
+
+			// Get the root path
+
+			String rootPath = getText(elem);
+
+			// Validate the root path
+
+			try {
+
+				// Parse the path
+
+				new FTPPath(rootPath);
+
+				// Set the root path
+
+				ftpConfig.setFTPRootPath(rootPath);
+			}
+			catch (InvalidPathException ex) {
+				throw new InvalidConfigurationException("Invalid FTP root directory, " + rootPath);
+			}
+		}
+
+		// Check if a data port range has been specified
+
+		elem = findChildNode("dataPorts", ftp.getChildNodes());
+		if ( elem != null) {
+
+			// Check for the from port range value
+
+			int rangeFrom = -1;
+			int rangeTo = -1;
+
+			String rangeStr = elem.getAttribute("rangeFrom");
+			if ( rangeStr != null && rangeStr.length() > 0) {
+
+				// Validate the range string
+
+				try {
+					rangeFrom = Integer.parseInt(rangeStr);
+				}
+				catch (NumberFormatException ex) {
+					throw new InvalidConfigurationException("Invalid FTP rangeFrom value, " + rangeStr);
+				}
+			}
+
+			// Check for the to port range value
+
+			rangeStr = elem.getAttribute("rangeTo");
+			if ( rangeStr != null && rangeStr.length() > 0) {
+
+				// Validate the range string
+
+				try {
+					rangeTo = Integer.parseInt(rangeStr);
+				}
+				catch (NumberFormatException ex) {
+					throw new InvalidConfigurationException("Invalid FTP rangeTo value, " + rangeStr);
+				}
+			}
+
+			// Validate the data port range values
+
+			if ( rangeFrom == -1 || rangeTo == -1)
+				throw new InvalidConfigurationException("FTP data port range from/to must be specified");
+
+			if ( rangeFrom < 1024 || rangeFrom > 65535)
+				throw new InvalidConfigurationException("Invalid FTP data port rangeFrom value, " + rangeFrom);
+
+			if ( rangeTo < 1024 || rangeTo > 65535)
+				throw new InvalidConfigurationException("Invalid FTP data port rangeTo value, " + rangeTo);
+
+			if ( rangeFrom >= rangeTo)
+				throw new InvalidConfigurationException("Invalid FTP data port range, " + rangeFrom + "-" + rangeTo);
+
+			// Set the FTP data port range
+
+			ftpConfig.setFTPDataPortLow(rangeFrom);
+			ftpConfig.setFTPDataPortHigh(rangeTo);
+		}
+
+		// Check if FTP debug is enabled
+
+		elem = findChildNode("debug", ftp.getChildNodes());
+		if ( elem != null) {
+
+			// Check for FTP debug flags
+
+			String flags = elem.getAttribute("flags");
+			int ftpDbg = 0;
+
+			if ( flags != null) {
+
+				// Parse the flags
+
+				flags = flags.toUpperCase();
+				StringTokenizer token = new StringTokenizer(flags, ",");
+
+				while (token.hasMoreTokens()) {
+
+					// Get the current debug flag token
+
+					String dbg = token.nextToken().trim();
+
+					// Find the debug flag name
+
+					int idx = 0;
+
+					while (idx < m_ftpDebugStr.length && m_ftpDebugStr[idx].equalsIgnoreCase(dbg) == false)
+						idx++;
+
+					if ( idx >= m_ftpDebugStr.length)
+						throw new InvalidConfigurationException("Invalid FTP debug flag, " + dbg);
+
+					// Set the debug flag
+
+					ftpDbg += 1 << idx;
+				}
+			}
+
+			// Set the FTP debug flags
+
+			ftpConfig.setFTPDebug(ftpDbg);
+		}
+
+		// Check if a site interface has been specified
+
+		elem = findChildNode("siteInterface", ftp.getChildNodes());
+		if ( elem != null) {
+
+			// Get the site interface class name
+
+			Element classElem = findChildNode("class", elem.getChildNodes());
+			if ( classElem == null)
+				throw new InvalidConfigurationException("Class not specified for FTP site interface");
+
+			String siteClass = getText(classElem);
+
+			// Validate the site interface class
+
+			try {
+
+				// Load the driver class
+
+				Object siteObj = Class.forName(siteClass).newInstance();
+				if ( siteObj instanceof FTPSiteInterface) {
+
+					// Initialize the site interface
+
+					ConfigElement params = buildConfigElement(elem);
+					FTPSiteInterface ftpSiteInterface = (FTPSiteInterface) siteObj;
+
+					ftpSiteInterface.initializeSiteInterface(this, params);
+
+					// Set the site interface
+
+					ftpConfig.setFTPSiteInterface(ftpSiteInterface);
+				}
+			}
+			catch (ClassNotFoundException ex) {
+				throw new InvalidConfigurationException("FTP site interface class " + siteClass + " not found");
+			}
+			catch (Exception ex) {
+				throw new InvalidConfigurationException("FTP site interface setup error, " + ex.toString());
+			}
+		}
+
+		// Check if an authenticator has been specified
+
+		elem = findChildNode("authenticator", ftp.getChildNodes());
+		if ( elem != null) {
+
+			// Get the FTP authenticator class
+
+			Element classElem = findChildNode("class", elem.getChildNodes());
+			if ( classElem == null)
+				throw new InvalidConfigurationException("FTP Authenticator class not specified");
+
+			// Get the parameters for the FTP authenticator class
+
+			ConfigElement params = buildConfigElement(elem);
+			ftpConfig.setAuthenticator(getText(classElem), params);
+		}
+
+		// FTPS parameter parsing
+		//
+		// Check if a key store path has been specified
+		
+		elem = findChildNode("keyStore", ftp.getChildNodes());
+		if ( elem != null) {
+
+			// Get the path to the key store, check that the file exists
+			
+			String keyStorePath = getText( elem);
+			File keyStoreFile = new File( keyStorePath);
+			
+			if ( keyStoreFile.exists() == false)
+				throw new InvalidConfigurationException("FTPS key store file does not exist, " + keyStorePath);
+			else if ( keyStoreFile.isDirectory())
+				throw new InvalidConfigurationException("FTPS key store path is a directory, " + keyStorePath);
+			
+			// Set the key store path
+			
+			ftpConfig.setKeyStorePath( keyStorePath);
+		}
+
+		// Check if a key store type has been specified
+		
+		elem = findChildNode("keyStoreType", ftp.getChildNodes());
+		if ( elem != null) {
+			
+			// Get the key store type, and validate
+			
+			String keyStoreType = getText( elem);
+			
+			if ( keyStoreType == null || keyStoreType.length() == 0)
+				throw new InvalidConfigurationException("FTPS key store type is invalid");
+			
+			try {
+				KeyStore.getInstance( keyStoreType);
+			}
+			catch ( KeyStoreException ex) {
+				throw new InvalidConfigurationException("FTPS key store type is invalid, " + keyStoreType, ex);
+			}
+			
+			// Set the key store type
+			
+			ftpConfig.setKeyStoreType( keyStoreType);
+		}
+		
+		// Check if the key store passphrase has been specified
+		
+		elem = findChildNode("keyStorePassphrase", ftp.getChildNodes());
+		if ( elem != null) {
+
+			// Set the key store passphrase
+			
+			ftpConfig.setKeyStorePassphrase( getText( elem));
+		}
+		
+		// Check if the trust store path has been specified
+		
+		elem = findChildNode("trustStore", ftp.getChildNodes());
+		if ( elem != null) {
+
+			// Get the path to the trust store, check that the file exists
+			
+			String trustStorePath = getText( elem);
+			File trustStoreFile = new File( trustStorePath);
+			
+			if ( trustStoreFile.exists() == false)
+				throw new InvalidConfigurationException("FTPS trust store file does not exist, " + trustStorePath);
+			else if ( trustStoreFile.isDirectory())
+				throw new InvalidConfigurationException("FTPS trust store path is a directory, " + trustStorePath);
+			
+			// Set the trust store path
+			
+			ftpConfig.setTrustStorePath( trustStorePath);
+		}
+		
+		// Check if a trust store type has been specified
+		
+		elem = findChildNode("trustStoreType", ftp.getChildNodes());
+		if ( elem != null) {
+			
+			// Get the trust store type, and validate
+			
+			String trustStoreType = getText( elem);
+			
+			if ( trustStoreType == null || trustStoreType.length() == 0)
+				throw new InvalidConfigurationException("FTPS trust store type is invalid");
+			
+			try {
+				KeyStore.getInstance( trustStoreType);
+			}
+			catch ( KeyStoreException ex) {
+				throw new InvalidConfigurationException("FTPS trust store type is invalid, " + trustStoreType, ex);
+			}
+			
+			// Set the trust store type
+			
+			ftpConfig.setTrustStoreType( trustStoreType);
+		}
+		
+		// Check if the trust store passphrase has been specified
+		
+		elem = findChildNode("trustStorePassphrase", ftp.getChildNodes());
+		if ( elem != null) {
+
+			// Set the key store passphrase
+			
+			ftpConfig.setTrustStorePassphrase( getText( elem));
+		}
+		
+		// Check if only secure sessions should be allowed to logon
+		
+		elem = findChildNode("requireSecureSession", ftp.getChildNodes());
+		if ( elem != null) {
+
+			// Only allow secure sessions to logon to the FTP server
+
+			ftpConfig.setRequireSecureSession( true);
+		}
+		
+		// Check that all the required FTPS parameters have been set
+		// MNT-7301 FTPS server requires unnecessarly to have a trustStore while a keyStore should be sufficient
+		if ( ftpConfig.getKeyStorePath() != null) {
+			
+			// Make sure all parameters are set
+			
+			if ( ftpConfig.getKeyStorePath() == null)
+				throw new InvalidConfigurationException("FTPS configuration requires keyStore to be set");
+		}
+		
+		// Check if SSLEngine debug output should be enabled
+		
+		elem = findChildNode("sslEngineDebug", ftp.getChildNodes());
+		if ( elem != null) {
+
+			// Enable SSLEngine debug output
+
+			System.setProperty("javax.net.debug", "ssl,handshake");
+		}
+	}
+
+	/**
+	 * Process the NFS server XML element
+	 * 
+	 * @param nfs Element
+	 * @exception InvalidConfigurationException
+	 */
+	protected final void procNFSServerElement(Element nfs)
+		throws InvalidConfigurationException {
+
+		// Check if the NFS element is valid
+
+		if ( nfs == null)
+			return;
+
+		// Create the NFS server configuration section
+
+		NFSConfigSection nfsConfig = new NFSConfigSection(this);
+
+		// Check if the port mapper is enabled
+
+		if ( findChildNode("enablePortMapper", nfs.getChildNodes()) != null)
+			nfsConfig.setNFSPortMapper(true);
+
+		// Check for the thread pool size
+
+		Element elem = findChildNode("ThreadPool", nfs.getChildNodes());
+
+		// Check for the old TCPThreadPool value if the new value is not available
+
+		if ( elem == null)
+			elem = findChildNode("TCPThreadPool", nfs.getChildNodes());
+
+		if ( elem != null) {
+
+			try {
+
+				// Convert the pool size value
+
+				int poolSize = Integer.parseInt(getText(elem));
+
+				// Range check the pool size value
+
+				if ( poolSize < 4)
+					throw new InvalidConfigurationException("NFS thread pool size is below minimum of 4");
+
+				// Set the thread pool size
+
+				nfsConfig.setNFSThreadPoolSize(poolSize);
+			}
+			catch (NumberFormatException ex) {
+				throw new InvalidConfigurationException("Invalid NFS thread pool size setting, " + getText(elem));
+			}
+		}
+
+		// NFS packet pool size
+
+		elem = findChildNode("PacketPool", nfs.getChildNodes());
+
+		if ( elem != null) {
+
+			try {
+
+				// Convert the packet pool size value
+
+				int pktPoolSize = Integer.parseInt(getText(elem));
+
+				// Range check the pool size value
+
+				if ( pktPoolSize < 10)
+					throw new InvalidConfigurationException("NFS packet pool size is below minimum of 10");
+
+				if ( pktPoolSize < nfsConfig.getNFSThreadPoolSize() + 1)
+					throw new InvalidConfigurationException("NFS packet pool must be at least thread pool size plus one");
+
+				// Set the packet pool size
+
+				nfsConfig.setNFSPacketPoolSize(pktPoolSize);
+			}
+			catch (NumberFormatException ex) {
+				throw new InvalidConfigurationException("Invalid NFS packet pool size setting, " + getText(elem));
+			}
+		}
+
+		// Check for a port mapper server port
+
+		if ( findChildNode("disablePortMapperRegistration", nfs.getChildNodes()) != null) {
+			
+			// Disable port mapper registration for the mount/NFS servers
+			
+			nfsConfig.setPortMapperPort( -1);
+		}
+		else {
+			elem = findChildNode("PortMapperPort", nfs.getChildNodes());
+			if ( elem != null) {
+				try {
+					nfsConfig.setPortMapperPort(Integer.parseInt(getText(elem)));
+					if ( nfsConfig.getPortMapperPort() <= 0 || nfsConfig.getPortMapperPort() >= 65535)
+						throw new InvalidConfigurationException("Port mapper server port out of valid range");
+				}
+				catch (NumberFormatException ex) {
+					throw new InvalidConfigurationException("Invalid port mapper server port");
+				}
+			}
+		}
+
+		// Check for a mount server port
+
+		elem = findChildNode("MountServerPort", nfs.getChildNodes());
+		if ( elem != null) {
+			try {
+				nfsConfig.setMountServerPort(Integer.parseInt(getText(elem)));
+				if ( nfsConfig.getMountServerPort() <= 0 || nfsConfig.getMountServerPort() >= 65535)
+					throw new InvalidConfigurationException("Mount server port out of valid range");
+			}
+			catch (NumberFormatException ex) {
+				throw new InvalidConfigurationException("Invalid mount server port");
+			}
+		}
+
+		// Check for an NFS server port
+
+		elem = findChildNode("NFSServerPort", nfs.getChildNodes());
+		if ( elem != null) {
+			try {
+				nfsConfig.setNFSServerPort(Integer.parseInt(getText(elem)));
+				if ( nfsConfig.getNFSServerPort() <= 0 || nfsConfig.getNFSServerPort() >= 65535)
+					throw new InvalidConfigurationException("NFS server port out of valid range");
+			}
+			catch (NumberFormatException ex) {
+				throw new InvalidConfigurationException("Invalid NFS server port");
+			}
+		}
+
+		// Check for an RPC registration port
+
+		elem = findChildNode("RPCRegisterPort", nfs.getChildNodes());
+		if ( elem != null) {
+			try {
+				nfsConfig.setRPCRegistrationPort(Integer.parseInt(getText(elem)));
+				if ( nfsConfig.getRPCRegistrationPort() <= 0 || nfsConfig.getRPCRegistrationPort() >= 65535)
+					throw new InvalidConfigurationException("RPC registration port out of valid range");
+			}
+			catch (NumberFormatException ex) {
+				throw new InvalidConfigurationException("Invalid RPC registration port");
+			}
+		}
+
+		// Check if an RPC authenticator has been specified
+
+		elem = findChildNode("rpcAuthenticator", nfs.getChildNodes());
+		if ( elem != null) {
+
+			// Get the RPC authenticator class
+
+			Element classElem = findChildNode("class", elem.getChildNodes());
+			if ( classElem == null)
+				throw new InvalidConfigurationException("RPC Authenticator class not specified");
+
+			// Get the parameters for the RPC authenticator class
+
+			ConfigElement params = buildConfigElement(elem);
+			nfsConfig.setRpcAuthenticator(getText(classElem), params);
+		}
+		else {
+			
+			// Use the null RPC authenticator as the default
+			
+			nfsConfig.setRpcAuthenticator( "org.alfresco.jlan.oncrpc.DefaultRpcAuthenticator", new ConfigElement( "", ""));
+		}
+
+		// Check if NFS debug is enabled
+
+		elem = findChildNode("debug", nfs.getChildNodes());
+		if ( elem != null) {
+
+			// Check for NFS debug flags
+
+			String flags = elem.getAttribute("flags");
+			int nfsDbg = 0;
+
+			if ( flags != null) {
+
+				// Parse the flags
+
+				flags = flags.toUpperCase();
+				StringTokenizer token = new StringTokenizer(flags, ",");
+
+				while (token.hasMoreTokens()) {
+
+					// Get the current debug flag token
+
+					String dbg = token.nextToken().trim();
+
+					// Find the debug flag name
+
+					int idx = 0;
+
+					while (idx < m_nfsDebugStr.length && m_nfsDebugStr[idx].equalsIgnoreCase(dbg) == false)
+						idx++;
+
+					if ( idx >= m_nfsDebugStr.length)
+						throw new InvalidConfigurationException("Invalid NFS debug flag, " + dbg);
+
+					// Set the debug flag
+
+					nfsDbg += 1 << idx;
+				}
+			}
+
+			// Set the NFS debug flags
+
+			nfsConfig.setNFSDebug(nfsDbg);
+		}
+
+		// Check if mount server debug output is enabled
+
+		elem = findChildNode("mountServerDebug", nfs.getChildNodes());
+		if ( elem != null)
+			nfsConfig.setMountServerDebug(true);
+
+		// Check if port mapper debug output is enabled
+
+		elem = findChildNode("portMapperDebug", nfs.getChildNodes());
+		if ( elem != null)
+			nfsConfig.setPortMapperDebug(true);
+
+		// Check if the file cache timers have been specified
+
+		elem = findChildNode("FileCache", nfs.getChildNodes());
+
+		if ( elem != null) {
+			try {
+
+				// Check for a single value or I/O and close timer values
+
+				String numVal = getText(elem);
+				long cacheIOTimer = -1;
+				long cacheCloseTimer = -1;
+
+				int pos = numVal.indexOf(':');
+
+				if ( pos == -1) {
+
+					// Only change the I/O timer
+
+					cacheIOTimer = Integer.parseInt(numVal);
+				}
+				else {
+
+					// Split the string value into read and write values, and convert to integers
+
+					String val = numVal.substring(0, pos);
+					cacheIOTimer = Integer.parseInt(val);
+
+					val = numVal.substring(pos + 1);
+					cacheCloseTimer = Integer.parseInt(val);
+				}
+
+				// Range check the I/O timer
+
+				if ( cacheIOTimer < 0 || cacheIOTimer > 30)
+					throw new InvalidConfigurationException("Invalid NFS file cache I/O timer value, " + cacheIOTimer);
+				else {
+
+					// Convert the timer to milliseconds
+
+					nfsConfig.setNFSFileCacheIOTimer(cacheIOTimer * 1000L);
+				}
+
+				// Range check the close timer, if specified
+
+				if ( cacheCloseTimer != -1) {
+					if ( cacheCloseTimer < 0 || cacheCloseTimer > 120)
+						throw new InvalidConfigurationException("Invalid NFS file cache close timer value, " + cacheCloseTimer);
+					else {
+
+						// Convert the timer to milliseconds
+
+						nfsConfig.setNFSFileCacheCloseTimer(cacheCloseTimer * 1000L);
+					}
+				}
+			}
+			catch (NumberFormatException ex) {
+				throw new InvalidConfigurationException("Invalid NFS file cache timer value, " + ex.toString());
+			}
+		}
+
+		// Check if NFS file cache debug output is enabled
+
+		if ( findChildNode("fileCacheDebug", nfs.getChildNodes()) != null)
+			nfsConfig.setNFSFileCacheDebug(true);
+	}
+	
+	/**
+	 * Process the servers XML element
+	 * 
+	 * @param cluster Element
+	 * @exception InvalidConfigurationException
+	 */
+	protected final void procClusterElement(Element cluster)
+		throws InvalidConfigurationException {
+
+		// Check if the cluster element has been specified
+
+		if ( cluster != null) {
+
+			// Check if the Hazelcast classes are available on the classpath
+			
+			try {
+				
+				// Check for various Hazelcast classes
+				
+				Class.forName( "com.hazelcast.core.HazelcastInstance");
+				Class.forName( "com.hazelcast.core.IMap");
+				Class.forName( "com.hazelcast.core.ITopic");
+				
+				// Check for the cluster configuration section
+				
+				Class.forName( "org.alfresco.jlan.server.filesys.cache.hazelcast.ClusterConfigSection");
+			}
+			catch ( ClassNotFoundException ex) {
+				throw new InvalidConfigurationException( "Hazelcast classes not found on the classpath, required for cluster support");
+			}
+
+			// Create the cluster configuration section
+			
+			ClusterConfigSection clusterConfig = new ClusterConfigSection( this);
+			
+			// Get the path to the HazelCast configuration file
+
+			Element elem = findChildNode("configFile", cluster.getChildNodes());
+			
+			if ( elem != null) {
+				
+				// Check that the configuration file exists, and is a file
+
+				String configPath = getText( elem);
+				
+				try {
+					File confFile = new File( configPath);
+					if ( confFile.exists() == false)
+						throw new InvalidConfigurationException( "HazelCast configuration file does not exist, " + configPath);
+					
+					if ( confFile.isDirectory())
+						throw new InvalidConfigurationException( "HazelCast configuration file is a folder path, " + configPath);
+					
+					// Set the Hazelcast cluster configuration file location
+					
+					clusterConfig.setConfigFile( configPath);
+				}
+				catch ( Exception ex) {
+					throw new InvalidConfigurationException( "HazelCast configuration file not valid", ex);
+				}
+			}
+			else
+				throw new InvalidConfigurationException( "HazelCast configuration file not specified");
+		}
+	}
+}

+ 208 - 0
src/org/alfresco/jlan/client/AsynchRequest.java

@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2006-2010 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.alfresco.jlan.client;
+
+/**
+ * Asynchronous Request Class
+ * 
+ * <p>Abstract class used to track the details of an asynchronous SMB/CIFS request where the request is sent to the server but no
+ * reply is received until a particular event occurs on the server, such as a directory change notification.
+ * 
+ * @author gkspencer
+ */
+public abstract class AsynchRequest {
+
+	//	Multiplex id that uniquely identifies this request
+	
+	private int m_id;
+	
+	//	Request name
+	
+	private String m_name;
+	
+	//	Asynchronous request completed flag
+	
+	private boolean m_completed;
+	
+	//	Auto-reset flag, used for asynchronous requests that need to be setup again each they have completed
+	
+	private boolean m_autoReset;
+	
+	/**
+	 * Class constructor
+	 * 
+	 * @param mid int
+	 */
+	protected AsynchRequest(int mid) {
+		m_id = mid;
+	}
+
+	/**
+	 * Class constructor
+	 * 
+	 * @param mid int
+	 * @param name String
+	 */
+	protected AsynchRequest(int mid, String name) {
+		m_id = mid;
+		m_name = name;
+	}
+
+	/**
+	 * Get the request id
+	 * 
+	 * @return int
+	 */
+	public final int getId() {
+		return m_id;
+	}
+
+	/**
+	 * Return the request name
+	 * 
+	 * @return String
+	 */
+	public final String getName() {
+		return m_name != null ? m_name : "";
+	}
+
+	/**
+	 * Check if the asynchronous request has completed
+	 * 
+	 * @return boolean
+	 */
+	public final boolean hasCompleted() {
+		return m_completed;
+	}
+
+	/**
+	 * Check if the request should be automatically reset
+	 * 
+	 * @return boolean
+	 */
+	public final boolean hasAutoReset() {
+		return m_autoReset;
+	}
+
+	/**
+	 * Enable/disable auto-reset of the request
+	 * 
+	 * @param auto boolean
+	 */
+	public final void setAutoReset(boolean auto) {
+		m_autoReset = auto;
+	}
+
+	/**
+	 * Process the asynchronous response packet for this request
+	 * 
+	 * @param sess Session
+	 * @param pkt SMBPacket
+	 */
+	protected abstract void processResponse(Session sess, SMBPacket pkt);
+
+	/**
+	 * Resubmit the request to the server
+	 * 
+	 * @param sess Session
+	 * @param pkt SMBPacket
+	 * @return boolean
+	 */
+	protected abstract boolean resubmitRequest(Session sess, SMBPacket pkt);
+
+	/**
+	 * Set the asynchronous request completion status
+	 * 
+	 * @param sts boolean
+	 */
+	protected final void setCompleted(boolean sts) {
+		m_completed = sts;
+	}
+
+	/**
+	 * Set the request id
+	 * 
+	 * @param id int
+	 */
+	protected final void setId(int id) {
+		m_id = id;
+	}
+
+	/**
+	 * Set the request name
+	 * 
+	 * @param name String
+	 */
+	protected final void setName(String name) {
+		m_name = name;
+	}
+
+	/**
+	 * Return the request as a string
+	 * 
+	 * @return String
+	 */
+	public String toString() {
+		StringBuffer str = new StringBuffer();
+
+		str.append("[");
+		str.append(getId());
+		str.append(":");
+		str.append(getName());
+		str.append(":");
+		str.append(hasCompleted() ? "Completed" : "Pending");
+		if ( hasAutoReset())
+			str.append(",Auto");
+		str.append("]");
+
+		return str.toString();
+	}
+
+	/**
+	 * Compare objects for equality
+	 * 
+	 * @return boolean
+	 */
+	public boolean equals(Object obj) {
+
+		// Check if the object is the same type
+
+		if ( obj instanceof AsynchRequest) {
+
+			// Compare the request id
+
+			AsynchRequest ar = (AsynchRequest) obj;
+			return ar.getId() == getId();
+		}
+
+		// Not the same object type
+
+		return false;
+	}
+
+	/**
+	 * Return a hashcode for the request
+	 * 
+	 * @return int
+	 */
+	public int hashCode() {
+		return getId();
+	}
+}

+ 409 - 0
src/org/alfresco/jlan/client/AuthenticateSession.java

@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2006-2010 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.alfresco.jlan.client;
+
+import java.io.*;
+
+import org.alfresco.jlan.debug.Debug;
+import org.alfresco.jlan.netbios.NetworkSession;
+import org.alfresco.jlan.smb.Capability;
+import org.alfresco.jlan.smb.Dialect;
+import org.alfresco.jlan.smb.NTTime;
+import org.alfresco.jlan.smb.PCShare;
+import org.alfresco.jlan.smb.PacketType;
+import org.alfresco.jlan.smb.SMBDate;
+import org.alfresco.jlan.smb.SMBException;
+import org.alfresco.jlan.util.DataPacker;
+import org.alfresco.jlan.util.HexDump;
+
+/**
+ * Authenticate Session Class
+ * 
+ * <p>
+ * Used for passthru authentication mechanisms.
+ * 
+ * @author gkspencer
+ */
+public class AuthenticateSession extends Session {
+
+	/**
+	 * Class constructor
+	 * 
+	 * @param shr PCShare
+	 * @param sess NetworkSession
+	 * @param dialect int
+	 * @param pkt SMBPacket
+	 */
+	protected AuthenticateSession(PCShare shr, NetworkSession sess, int dialect, SMBPacket pkt) {
+		super(shr, dialect, pkt);
+
+		// Save the session and packet
+
+		setSession(sess);
+
+		// Extract the details from the negotiate response packet
+
+		processNegotiateResponse();
+	}
+
+	/**
+	 * Perform a session setup to create a session on the remote server validating the user.
+	 * 
+	 * @param userName String
+	 * @param ascPwd ASCII password hash
+	 * @param uniPwd Unicode password hash
+	 * @param vc Virtual circuit number
+	 */
+	public final void doSessionSetup(String userName, byte[] ascPwd, byte[] uniPwd, int vc)
+		throws IOException, SMBException {
+
+		// Create a session setup packet
+
+		SMBPacket pkt = new SMBPacket();
+
+		pkt.setCommand(PacketType.SessionSetupAndX);
+
+		// Check if the negotiated SMB dialect is NT LM 1.2 or an earlier dialect
+
+		if ( getDialect() == Dialect.NT) {
+
+			// NT LM 1.2 SMB dialect
+
+			pkt.setParameterCount(13);
+			pkt.setAndXCommand(0xFF); // no secondary command
+			pkt.setParameter(1, 0); // offset to next command
+			pkt.setParameter(2, SessionFactory.DefaultPacketSize());
+			pkt.setParameter(3, getMaximumMultiplexedRequests());
+			pkt.setParameter(4, vc); // virtual circuit number
+			pkt.setParameterLong(5, 0); // session key
+
+			// Set the share password length(s)
+
+			pkt.setParameter(7, ascPwd != null ? ascPwd.length : 0); // ANSI password length
+			pkt.setParameter(8, uniPwd != null ? uniPwd.length : 0); // Unicode password length
+
+			pkt.setParameter(9, 0); // reserved, must be zero
+			pkt.setParameter(10, 0); // reserved, must be zero
+
+			// Send the client capabilities
+
+			int caps = Capability.LargeFiles + Capability.Unicode + Capability.NTSMBs + Capability.NTStatus
+					+ Capability.RemoteAPIs;
+			pkt.setParameterLong(11, caps);
+
+			// Store the encrypted passwords
+			//
+			// Store the ASCII password hash, if specified
+
+			int pos = pkt.getByteOffset();
+			pkt.setPosition(pos);
+
+			if ( ascPwd != null)
+				pkt.packBytes(ascPwd, ascPwd.length);
+
+			// Store the Unicode password hash, if specified
+
+			if ( uniPwd != null)
+				pkt.packBytes(uniPwd, uniPwd.length);
+
+			// Pack the account/client details
+
+			pkt.packString(userName, false);
+
+			// Check if the share has a domain, if not then use the default domain string
+
+			if ( getPCShare().hasDomain())
+				pkt.packString(getPCShare().getDomain(), false);
+			else
+				pkt.packString(SessionFactory.getDefaultDomain(), false);
+
+			pkt.packString("Java VM", false);
+			pkt.packString("JLAN", false);
+
+			// Set the packet length
+
+			pkt.setByteCount(pkt.getPosition() - pos);
+		}
+		else {
+
+			// Earlier SMB dialect
+
+			pkt.setUserId(1);
+
+			pkt.setParameterCount(10);
+			pkt.setAndXCommand(0xFF); // no secondary command
+			pkt.setParameter(1, 0); // offset to next command
+			pkt.setParameter(2, SessionFactory.DefaultPacketSize());
+			pkt.setParameter(3, 2); // max multiplexed pending requests
+			pkt.setParameter(4, 0); // getSessionId ());
+			pkt.setParameter(5, 0);
+			pkt.setParameter(6, 0);
+			pkt.setParameter(7, ascPwd != null ? ascPwd.length : 0);
+			pkt.setParameter(8, 0);
+			pkt.setParameter(9, 0);
+
+			// Put the password into the SMB packet
+
+			byte[] buf = pkt.getBuffer();
+			int pos = pkt.getByteOffset();
+
+			if ( ascPwd != null) {
+				for (int i = 0; i < ascPwd.length; i++)
+					buf[pos++] = ascPwd[i];
+			}
+
+			// Build the account/client details
+
+			StringBuffer clbuf = new StringBuffer();
+
+			clbuf.append(getPCShare().getUserName());
+			clbuf.append((char) 0x00);
+
+			// Check if the share has a domain, if not then use the unknown domain string
+
+			if ( getPCShare().hasDomain())
+				clbuf.append(getPCShare().getDomain());
+			else
+				clbuf.append(SessionFactory.getDefaultDomain());
+			clbuf.append((char) 0x00);
+
+			clbuf.append("Java VM");
+			clbuf.append((char) 0x00);
+
+			clbuf.append("JLAN");
+			clbuf.append((char) 0x00);
+
+			// Copy the remaining data to the SMB packet
+
+			byte[] byts = clbuf.toString().getBytes();
+			for (int i = 0; i < byts.length; i++)
+				buf[pos++] = byts[i];
+
+			int pwdLen = ascPwd != null ? ascPwd.length : 0;
+			pkt.setByteCount(pwdLen + byts.length);
+		}
+
+		// Exchange an SMB session setup packet with the remote file server
+
+		pkt.ExchangeSMB(this, pkt, true);
+
+		// Save the session user id
+
+		setUserId(pkt.getUserId());
+
+		// Check if the session was created as a guest
+
+		if ( pkt.getParameterCount() >= 3) {
+
+			// Set the guest status for the session
+
+			setGuest(pkt.getParameter(2) != 0 ? true : false);
+		}
+
+		// The response packet should also have the server OS, LAN Manager type
+		// and primary domain name.
+
+		if ( pkt.getByteCount() > 0) {
+
+			// Get the packet buffer and byte offset
+
+			byte[] buf = pkt.getBuffer();
+			int offset = pkt.getByteOffset();
+			int maxlen = offset + pkt.getByteCount();
+
+			// Get the server OS
+
+			String srvOS = DataPacker.getString(buf, offset, maxlen);
+			setOperatingSystem(srvOS);
+
+			offset += srvOS.length() + 1;
+			maxlen -= srvOS.length() + 1;
+
+			// Get the LAN Manager type
+
+			String lanman = DataPacker.getString(buf, offset, maxlen);
+			setLANManagerType(lanman);
+
+			// Check if we have the primary domain for this session
+
+			if ( getDomain() == null || getDomain().length() == 0) {
+
+				// Get the domain name string
+
+				offset += lanman.length() + 1;
+				maxlen += lanman.length() + 1;
+
+				String dom = DataPacker.getString(buf, offset, maxlen);
+				setDomain(dom);
+			}
+		}
+
+		// Check for a core protocol session, set the maximum packet size
+
+		if ( getDialect() == Dialect.Core || getDialect() == Dialect.CorePlus) {
+
+			// Set the maximum packet size to be used on this session
+
+			setMaximumPacketSize(pkt.getParameter(2));
+		}
+	}
+
+	/**
+	 * Process the negotiate response SMB packet
+	 * 
+	 */
+	private void processNegotiateResponse() {
+
+		// Set the security mode flags
+
+		int keyLen = 0;
+		boolean unicodeStr = false;
+		int encAlgorithm = PasswordEncryptor.LANMAN;
+		int defFlags2 = 0;
+
+		if ( getDialect() == Dialect.NT) {
+
+			// Read the returned negotiate parameters, for NT dialect the parameters are not aligned
+
+			m_pkt.resetParameterPointer();
+			m_pkt.skipBytes(2); // skip the dialect index
+
+			setSecurityMode(m_pkt.unpackByte());
+
+			// Set the maximum virtual circuits and multiplxed requests allowed by the server
+
+			setMaximumMultiplexedRequests(m_pkt.unpackWord());
+			setMaximumVirtualCircuits(m_pkt.unpackWord());
+
+			// Set the maximum buffer size
+
+			setMaximumPacketSize(m_pkt.unpackInt());
+
+			// Skip the maximum raw buffer size and session key
+
+			m_pkt.skipBytes(8);
+
+			// Set the server capabailities
+
+			setCapabilities(m_pkt.unpackInt());
+
+			// Get the server system time and timezone
+
+			SMBDate srvTime = NTTime.toSMBDate(m_pkt.unpackLong());
+			int tzone = m_pkt.unpackWord();
+
+			// Get the encryption key length
+
+			keyLen = m_pkt.unpackByte();
+
+			// Indicate that strings are UniCode
+
+			unicodeStr = true;
+
+			// Use NTLMv1 password encryption
+
+			encAlgorithm = PasswordEncryptor.NTLM1;
+
+			// Set the default flags for subsequent SMB requests
+
+			defFlags2 = SMBPacket.FLG2_LONGFILENAMES + SMBPacket.FLG2_UNICODE + SMBPacket.FLG2_LONGERRORCODE;
+		}
+		else if ( getDialect() > Dialect.CorePlus) {
+
+			// Set the security mode and encrypted password mode
+
+			int secMode = m_pkt.getParameter(1);
+			setSecurityMode((secMode & 0x01) != 0 ? Session.SecurityModeUser : Session.SecurityModeShare);
+
+			if ( m_pkt.getParameterCount() >= 11)
+				keyLen = m_pkt.getParameter(11) & 0xFF; // should always be 8
+
+			// Set the maximum virtual circuits and multiplxed requests allowed by the server
+
+			setMaximumMultiplexedRequests(m_pkt.getParameter(3));
+			setMaximumVirtualCircuits(m_pkt.getParameter(4));
+
+			// Check if Unicode strings are being used
+
+			if ( m_pkt.isUnicode())
+				unicodeStr = true;
+
+			// Set the default flags for subsequent SMB requests
+
+			defFlags2 = SMBPacket.FLG2_LONGFILENAMES;
+		}
+
+		// Set the default packet flags for this session
+
+		setDefaultFlags2(defFlags2);
+
+		// Get the server details from the negotiate SMB packet
+
+		if ( m_pkt.getByteCount() > 0) {
+
+			// Get the returned byte area length and offset
+
+			int bytsiz = m_pkt.getByteCount();
+			int bytpos = m_pkt.getByteOffset();
+			byte[] buf = m_pkt.getBuffer();
+
+			// Extract the challenge response key, if specified
+
+			if ( keyLen > 0) {
+
+				// Allocate a buffer for the challenge response key
+
+				byte[] encryptKey = new byte[keyLen];
+
+				// Copy the challenge response key
+
+				for (int keyIdx = 0; keyIdx < keyLen; keyIdx++)
+					encryptKey[keyIdx] = buf[bytpos++];
+
+				// Set the sessions encryption key
+
+				setEncryptionKey(encryptKey);
+
+				// DEBUG
+
+				if ( Debug.EnableInfo && Session.hasDebugOption(Session.DBGDumpPacket)) {
+					Debug.print("** Encryption Key: ");
+					Debug.print(HexDump.hexString(encryptKey));
+					Debug.println(", length = " + keyLen);
+				}
+			}
+
+			// Extract the domain name
+
+			String dom;
+
+			if ( unicodeStr == false)
+				dom = DataPacker.getString(buf, bytpos, bytsiz);
+			else
+				dom = DataPacker.getUnicodeString(buf, bytpos, bytsiz / 2);
+			setDomain(dom);
+
+			// DEBUG
+
+			if ( Debug.EnableInfo && Session.hasDebugOption(Session.DBGDumpPacket))
+				Debug.println("** Server domain : " + getDomain() + ".");
+		}
+	}
+}

+ 2237 - 0
src/org/alfresco/jlan/client/CIFSDiskSession.java

@@ -0,0 +1,2237 @@
+/*
+ * Copyright (C) 2006-2010 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.alfresco.jlan.client;
+
+import java.io.*;
+import java.util.*;
+
+import org.alfresco.jlan.client.info.DeviceAttributesInfo;
+import org.alfresco.jlan.client.info.DeviceInfo;
+import org.alfresco.jlan.client.info.DiskInfo;
+import org.alfresco.jlan.client.info.FileInfo;
+import org.alfresco.jlan.client.info.VolumeInfo;
+import org.alfresco.jlan.client.smb.DirectoryWatcher;
+import org.alfresco.jlan.server.filesys.AccessMode;
+import org.alfresco.jlan.server.filesys.FileAction;
+import org.alfresco.jlan.server.filesys.FileAttribute;
+import org.alfresco.jlan.smb.DataType;
+import org.alfresco.jlan.smb.Dialect;
+import org.alfresco.jlan.smb.FileInfoLevel;
+import org.alfresco.jlan.smb.LockingAndX;
+import org.alfresco.jlan.smb.NTTime;
+import org.alfresco.jlan.smb.OpLock;
+import org.alfresco.jlan.smb.PCShare;
+import org.alfresco.jlan.smb.PacketType;
+import org.alfresco.jlan.smb.SMBException;
+import org.alfresco.jlan.smb.SMBStatus;
+import org.alfresco.jlan.smb.SharingMode;
+import org.alfresco.jlan.smb.TransactBuffer;
+import org.alfresco.jlan.smb.WinNT;
+import org.alfresco.jlan.smb.nt.LoadException;
+import org.alfresco.jlan.smb.nt.SaveException;
+import org.alfresco.jlan.smb.nt.SecurityDescriptor;
+import org.alfresco.jlan.smb.nt.SymLink;
+import org.alfresco.jlan.util.DataBuffer;
+import org.alfresco.jlan.util.DataPacker;
+import org.alfresco.jlan.smb.nt.NTIOCtl;
+
+/**
+ * SMB CIFS disk session class
+ * 
+ * <p>
+ * The CIFSDiskSession class extends the DiskSession class and provides CIFS protocol specific
+ * implementations for the DiskSession methods.
+ * 
+ * <p>
+ * An CIFSDiskSession object will be created by the SessionFactory static class when the negotiated
+ * SMB dialect indicates that the remote server supports an SMB dialect greater than Core or
+ * CorePlus.
+ * 
+ * <p>
+ * The SessionFactory.OpenDisk() method is used to create a session to a remote disk share. A
+ * PCShare object specifies the remote server and share to connect to, along with any required
+ * access control.
+ * 
+ * @author gkspencer
+ */
+public final class CIFSDiskSession extends DiskSession {
+
+	// Constants
+	//
+	// SMB session keep-alive interval
+
+	private final static long SessionKeepAlive = 60000L;
+
+	// List of pending asynchronous requests
+
+	private List<AsynchRequest> m_asynchRequests;
+
+	// List of open files with an oplock
+	
+	private HashMap<Integer, CIFSFile> m_oplockFiles;
+	
+	/**
+	 * Class constructor
+	 * 
+	 * @param shr Remote server details.
+	 * @param dialect SMB dialect that this session is using
+	 */
+	protected CIFSDiskSession(PCShare shr, int dialect) {
+		super(shr, dialect);
+	}
+
+	/**
+	 * Close this connection with the remote server share.
+	 * 
+	 * @exception java.io.IOException If an I/O error occurs.
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	public void CloseSession()
+		throws java.io.IOException, SMBException {
+
+		// Build a tree disconnect packet
+
+		m_pkt.setCommand(PacketType.TreeDisconnect);
+		m_pkt.setUserId(getUserId());
+		m_pkt.setTreeId(m_treeid);
+
+		m_pkt.setParameterCount(0);
+		m_pkt.setByteCount(0);
+
+		// Send the tree disconnect packet
+
+		m_pkt.ExchangeSMB(this, m_pkt);
+
+		// Indicate that the session has been closed
+
+		m_treeid = DiskSession.Closed;
+
+		// Close the network session
+
+		super.CloseSession();
+	}
+
+	/**
+	 * Create a new directory on the remote file server.
+	 * 
+	 * @param dir Directory name string. If the directory name does not have a leading '\' the
+	 *            current working directory for this session will be prepended to the string.
+	 * @exception java.io.IOException If an I/O error occurs.
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	public final void CreateDirectory(String dir)
+		throws java.io.IOException, SMBException {
+
+		// Build the new path
+
+		String newPath = dir;
+		if ( newPath.startsWith("\\") == false)
+			newPath = PCShare.makePath(getWorkingDirectory(), dir);
+
+		// Pre-NT dialect create directory
+
+		if ( getDialect() != Dialect.NT || isUnicode() == false) {
+
+			// Create an SMB create directory packet
+
+			m_pkt.setCommand(PacketType.CreateDirectory);
+			m_pkt.setUserId(this.getUserId());
+			m_pkt.setTreeId(this.getTreeId());
+
+			m_pkt.setFlags(getDefaultFlags());
+			m_pkt.setFlags2(getDefaultFlags2());
+
+			m_pkt.setParameterCount(0);
+
+			// Copy the directory name data block to the SMB packet
+
+			m_pkt.resetBytePointer();
+			m_pkt.packByte(DataType.ASCII);
+			m_pkt.packString(newPath, m_pkt.isUnicode());
+
+			m_pkt.setByteCount();
+
+			// Send/receive the SMB create directory packet
+
+			m_pkt.ExchangeSMB(this, m_pkt, true);
+		}
+		else {
+
+			// Use the NTCreateAndX SMB to create the directory
+
+			CIFSFile dirFile = NTCreate(newPath, AccessMode.NTRead, FileAttribute.NTDirectory, SharingMode.READWRITE,
+					FileAction.NTCreate, 0, WinNT.CreateDirectory);
+
+			// Close the directory file
+
+			dirFile.Close();
+		}
+	}
+
+	/**
+	 * Create and open a file on the remote file server.
+	 * 
+	 * @param fname Remote file name string.
+	 * @return SMBFile for the opened file, else null.
+	 * @exception java.io.IOException If an I/O error occurs
+	 * @exception SMBException If an SMB error occurs
+	 */
+	public final SMBFile CreateFile(String fname)
+		throws java.io.IOException, SMBException {
+
+		// Create a new file
+
+		return OpenFile(fname, AccessMode.WriteOnly);
+	}
+
+	/**
+	 * Delete the specified directory on the remote file server.
+	 * 
+	 * @param dir Directory name string. If the directory name does not have a leading '\' the
+	 *            current working directory for this session will be preprended to the string.
+	 * @exception java.io.IOException If an I/O error occurs.
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	public final void DeleteDirectory(String dir)
+		throws java.io.IOException, SMBException {
+
+		// Create an SMB delete directory packet
+
+		m_pkt.setFlags(getDefaultFlags());
+		m_pkt.setFlags2(getDefaultFlags2());
+
+		m_pkt.setCommand(PacketType.DeleteDirectory);
+		m_pkt.setUserId(this.getUserId());
+		m_pkt.setTreeId(this.getTreeId());
+
+		m_pkt.setParameterCount(0);
+
+		// Check if the directory name contains a path
+
+		String delPath = dir;
+		if ( delPath.startsWith("\\") == false)
+			delPath = PCShare.makePath(getWorkingDirectory(), dir);
+
+		// Pack the path to be deleted
+
+		m_pkt.resetBytePointer();
+
+		m_pkt.packByte(DataType.ASCII);
+		m_pkt.packString(delPath, m_pkt.isUnicode());
+
+		m_pkt.setByteCount();
+
+		// Send/receive the SMB delete directory packet
+
+		m_pkt.ExchangeSMB(this, m_pkt, true);
+	}
+
+	/**
+	 * Delete the specified file on the remote file server.
+	 * 
+	 * @param fname File name of the remote file to delete. If the file name does not have a leading
+	 *            '\' the current working directory for this session will be prepended to the
+	 *            string.
+	 * @param attr File attributes of the file(s) to delete.
+	 * @exception java.io.IOException If an I/O error occurs.
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	public final void DeleteFile(String fname, int attr)
+		throws java.io.IOException, SMBException {
+
+		// Create an SMB delete file packet
+
+		m_pkt.setFlags(getDefaultFlags());
+		m_pkt.setFlags2(getDefaultFlags2());
+
+		m_pkt.setCommand(PacketType.DeleteFile);
+		m_pkt.setUserId(this.getUserId());
+		m_pkt.setTreeId(this.getTreeId());
+
+		m_pkt.setParameterCount(1);
+		m_pkt.setParameter(0, attr);
+
+		// Check if the file name contains a path
+
+		String delName = fname;
+		if ( delName.startsWith("\\") == false)
+			delName = PCShare.makePath(getWorkingDirectory(), fname);
+
+		// Copy the file name data block to the SMB packet
+
+		m_pkt.resetBytePointer();
+		m_pkt.packByte(DataType.ASCII);
+		m_pkt.packString(delName, m_pkt.isUnicode());
+
+		m_pkt.setByteCount();
+
+		// Send/receive the SMB delete file packet
+
+		m_pkt.ExchangeSMB(this, m_pkt, true);
+	}
+
+	/**
+	 * Get disk information for this remote disk.
+	 * 
+	 * @return Disk information object, or null.
+	 * @exception java.io.IOException If an I/O error occurs.
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	public final DiskInfo getDiskInformation()
+		throws java.io.IOException, SMBException {
+
+		// Check if the NT dialect has been negotiated, or LanMan
+
+		if ( this.getDialect() != Dialect.NT) {
+
+			// Create a query disk information SMB packet
+
+			m_pkt.setFlags(getDefaultFlags());
+			m_pkt.setFlags2(getDefaultFlags2());
+
+			m_pkt.setCommand(PacketType.DiskInformation);
+			m_pkt.setUserId(this.getUserId());
+			m_pkt.setTreeId(this.getTreeId());
+			m_pkt.setParameterCount(0);
+			m_pkt.setByteCount(0);
+
+			// Send/receive the SMB file information packet
+
+			m_pkt.ExchangeSMB(this, m_pkt, true);
+
+			// Extract the disk information from the received SMB packet
+
+			int totunit = m_pkt.getParameter(0);
+			int blkperunit = m_pkt.getParameter(1);
+			int blksize = m_pkt.getParameter(2);
+			int freeblk = m_pkt.getParameter(3);
+
+			// Create a disk information object
+
+			return new DiskInfo(getPCShare(), totunit, blkperunit, blksize, freeblk);
+		}
+		else {
+
+			// Create the transaction request
+
+			TransactBuffer reqBuf = new TransactBuffer(PacketType.Trans2QueryFileSys, null, 0, 2, 0);
+
+			// Pack the parameter block
+
+			DataBuffer paramBuf = reqBuf.getParameterBuffer();
+
+			paramBuf.putShort(FileInfoLevel.FSInfoQuerySize);
+
+			// Perform the get file system information transaction
+
+			TransPacket tpkt = new TransPacket(m_pkt.getBuffer());
+			TransactBuffer respBuf = tpkt.doTransaction(this, reqBuf);
+
+			// Unpack the response data
+
+			DiskInfo dinfo = null;
+
+			if ( respBuf != null && respBuf.hasDataBuffer()) {
+
+				// Unpack the file system information
+
+				DataBuffer dataBuf = respBuf.getDataBuffer();
+
+				long fsTotalUnit = dataBuf.getLong();
+				long fsAvailUnit = dataBuf.getLong();
+
+				int fsSectorsPerUnit = dataBuf.getInt();
+				int fsBytesPerSector = dataBuf.getInt();
+
+				// Create the disk information details
+
+				dinfo = new DiskInfo(getPCShare(), fsTotalUnit, fsSectorsPerUnit, fsBytesPerSector, fsAvailUnit);
+			}
+
+			// Return the disk information
+
+			return dinfo;
+		}
+	}
+
+	/**
+	 * Get file information for the specified file.
+	 * 
+	 * @param fname File name of the file to return information for.
+	 * @see org.alfresco.jlan.smb.FileInfoLevel
+	 * @param level Information level required
+	 * @return FileInfo if the request was successful, else null.
+	 * @exception java.io.IOException If an I/O error occurs.
+	 * @exception java.io.FileNotFoundException If the remote file does not exist.
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	public final FileInfo getFileInformation(String fname, int level)
+		throws java.io.IOException, java.io.FileNotFoundException, SMBException {
+
+		// Build the file name/path string
+
+		String pathName = fname;
+		if ( pathName.startsWith("\\") == false)
+			pathName = PCShare.makePath(getWorkingDirectory(), fname);
+
+		// Create the request transaction buffer
+
+		TransactBuffer reqBuf = new TransactBuffer(PacketType.Trans2QueryPath, null, 0, 512, 0);
+
+		// Pack the parameter block
+
+		DataBuffer paramBuf = reqBuf.getParameterBuffer();
+
+		paramBuf.putShort(level);
+		paramBuf.putInt(0);
+		paramBuf.putString(pathName, isUnicode());
+
+		// Perform the get file information transaction
+
+		TransPacket tpkt = new TransPacket(m_pkt.getBuffer());
+		TransactBuffer respBuf = tpkt.doTransaction(this, reqBuf);
+
+		// Unpack the received file information data
+
+		FileInfo finfo = null;
+
+		if ( respBuf != null && respBuf.hasDataBuffer()) {
+
+			// Unpack the file information
+
+			DataBuffer buf = respBuf.getDataBuffer();
+
+			switch (level) {
+				case FileInfoLevel.PathStandard:
+					finfo = FileInfoPacker.unpackFileInfoStandard("", buf, false);
+					break;
+				case FileInfoLevel.PathQueryEASize:
+					finfo = FileInfoPacker.unpackFileInfoStandard("", buf, true);
+					break;
+				case FileInfoLevel.PathAllEAs:
+					finfo = FileInfoPacker.unpackQueryAllEAs("", buf);
+					break;
+				case FileInfoLevel.PathFileBasicInfo:
+					finfo = FileInfoPacker.unpackQueryBasicInfo("", buf);
+					break;
+				case FileInfoLevel.PathFileStandardInfo:
+					finfo = FileInfoPacker.unpackQueryStandardInfo("", buf);
+					break;
+				case FileInfoLevel.PathFileEAInfo:
+					finfo = FileInfoPacker.unpackQueryEAInfo("", buf);
+					break;
+				case FileInfoLevel.PathFileNameInfo:
+					finfo = FileInfoPacker.unpackQueryNameInfo(buf, respBuf.isUnicode());
+					break;
+				case FileInfoLevel.PathFileAllInfo:
+					finfo = FileInfoPacker.unpackQueryAllInfo(buf, respBuf.isUnicode());
+					break;
+				case FileInfoLevel.PathFileAltNameInfo:
+					finfo = FileInfoPacker.unpackQueryNameInfo(buf, respBuf.isUnicode());
+					break;
+				case FileInfoLevel.PathFileStreamInfo:
+					finfo = FileInfoPacker.unpackQueryStreamInfo("", buf, respBuf.isUnicode());
+					break;
+				case FileInfoLevel.PathFileCompressionInfo:
+					finfo = FileInfoPacker.unpackQueryCompressionInfo("", buf);
+					break;
+			}
+		}
+
+		// Return the file information
+
+		return finfo;
+	}
+
+	/**
+	 * Get the disk volume information
+	 * 
+	 * @return VolumeInfo, or null
+	 * @exception java.io.IOException If an I/O error occurs
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	public final VolumeInfo getVolumeInformation()
+		throws java.io.IOException, SMBException {
+
+		// Check if the NT dialect has been negotiated, or LanMan
+
+		VolumeInfo volInfo = null;
+
+		if ( this.getDialect() != Dialect.NT) {
+
+			// Build the search request
+
+			m_pkt.setCommand(PacketType.Search);
+			m_pkt.setUserId(getUserId());
+			m_pkt.setTreeId(getTreeId());
+
+			// Initialize the search SMB packet
+
+			m_pkt.setFlags(getDefaultFlags());
+			m_pkt.setFlags2(getDefaultFlags2());
+
+			m_pkt.setParameterCount(2);
+			m_pkt.setParameter(0, 1); // number of directory entries to return
+			m_pkt.setParameter(1, FileAttribute.Volume);
+
+			// Pack the search string
+
+			m_pkt.resetBytePointer();
+			m_pkt.packByte(DataType.ASCII);
+			m_pkt.packString("", false);
+
+			// Append a null resume key, to indicate the start of a new search
+
+			m_pkt.packByte(DataType.VariableBlock);
+			m_pkt.packWord(0);
+
+			m_pkt.setByteCount();
+
+			// Send/receive the search SMB packet
+
+			m_pkt.ExchangeSMB(this, m_pkt, true);
+
+			// Unpack the volume label
+
+			m_pkt.resetBytePointer();
+			m_pkt.skipBytes(33); // data type byte + length word + offset to file name/volume label
+
+			String label = m_pkt.unpackString(m_pkt.isUnicode());
+
+			// Create the volume information object
+
+			volInfo = new VolumeInfo(label);
+		}
+		else {
+
+			// Create the transaction request
+
+			TransactBuffer reqBuf = new TransactBuffer(PacketType.Trans2QueryFileSys, null, 0, 2, 0);
+
+			// Pack the parameter block
+
+			DataBuffer paramBuf = reqBuf.getParameterBuffer();
+
+			paramBuf.putShort(FileInfoLevel.FSInfoQueryVolume);
+
+			// Perform the get file system information transaction
+
+			TransPacket tpkt = new TransPacket(m_pkt.getBuffer());
+			TransactBuffer respBuf = tpkt.doTransaction(this, reqBuf);
+
+			// Unpack the volume information
+
+			if ( respBuf != null && respBuf.hasDataBuffer()) {
+
+				// Get the data buffer
+
+				DataBuffer dataBuf = respBuf.getDataBuffer();
+
+				// Get the volume information
+
+				long createTime = dataBuf.getLong();
+				int serNo = dataBuf.getInt();
+
+				int nameLen = dataBuf.getInt();
+				if ( respBuf.isUnicode())
+					nameLen = nameLen / 2;
+				dataBuf.skipBytes(2);
+
+				String label = dataBuf.getString(nameLen, respBuf.isUnicode());
+
+				// Create the volume information
+
+				volInfo = new VolumeInfo(label, serNo, NTTime.toSMBDate(createTime));
+			}
+		}
+
+		// Return the volume information
+
+		return volInfo;
+	}
+
+	/**
+	 * Check if the specified file name is a directory.
+	 * 
+	 * @param dir Directory name string. If the directory name does not have a leading '\' the
+	 *            current working directory for this session will be preprended to the string.
+	 * @return true if the specified file name is a directory, else false.
+	 * @exception java.io.IOException If an I/O error occurs.
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	public final boolean isDirectory(String dir)
+		throws java.io.IOException, SMBException {
+
+		// Allocate an SMB packet for the check directory request
+
+		m_pkt.setFlags(getDefaultFlags());
+		m_pkt.setFlags2(getDefaultFlags2());
+
+		m_pkt.setCommand(PacketType.CheckDirectory);
+		m_pkt.setUserId(this.getUserId());
+		m_pkt.setTreeId(this.getTreeId());
+		m_pkt.setParameterCount(0);
+
+		// Build the remote directory tree relative path
+
+		String pathName = dir;
+		if ( pathName.startsWith("\\") == false)
+			pathName = PCShare.makePath(getWorkingDirectory(), dir);
+
+		// Pack the directory name
+
+		m_pkt.resetBytePointer();
+		m_pkt.packByte(DataType.ASCII);
+		m_pkt.packString(pathName, m_pkt.isUnicode());
+
+		m_pkt.setByteCount();
+
+		// Send/receive the SMB check directory packet
+
+		m_pkt.ExchangeSMB(this, m_pkt);
+
+		// Check if a valid response was received, indicates the path is a directory
+
+		return m_pkt.isValidResponse();
+	}
+
+	/**
+	 * Open a file on the remote file server.
+	 * 
+	 * @param fname Remote file name string.
+	 * @param flags File open option flags.
+	 * @return SMBFile for the opened file, else null.
+	 * @exception java.io.IOException If an I/O error occurs
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	public final SMBFile OpenFile(String fname, int flags)
+		throws java.io.IOException, SMBException {
+
+		// Check if the path is a valid file path
+
+		if ( isValidFilePath(fname) == false)
+			throw new SMBException(SMBStatus.NTErr, SMBStatus.NTInvalidParameter);
+
+		// Build the file name details
+
+		String fileName = fname;
+		if ( fileName.startsWith("\\") == false)
+			fileName = PCShare.makePath(getWorkingDirectory(), fname);
+
+		// Pre-NT dialect open file
+
+		if ( getDialect() != Dialect.NT || isUnicode() == false) {
+
+			// Initialize the SMB request to open an existing file
+
+			m_pkt.setCommand(PacketType.OpenAndX);
+			m_pkt.setFlags(getDefaultFlags());
+			m_pkt.setFlags2(getDefaultFlags2());
+
+			m_pkt.setUserId(this.getUserId());
+			m_pkt.setTreeId(this.getTreeId());
+
+			// Set the parameter words
+
+			m_pkt.setParameterCount(15);
+			m_pkt.setAndXCommand(0xFF); // no secondary command
+			m_pkt.setParameter(1, 0); // offset to next command
+			m_pkt.setParameter(2, 0x01); // return additional information
+			m_pkt.setParameter(3, flags);
+			m_pkt.setParameter(4, 0); // normal files only for now
+			m_pkt.setParameter(5, 0); // file attributes
+			m_pkt.setParameter(6, 0); // creation time
+			m_pkt.setParameter(7, 0); // creation date
+
+			// Default open mode is 'open if file exists'
+
+			int openMode = FileAction.OpenIfExists;
+
+			if ( AccessMode.getAccessMode(flags) == AccessMode.WriteOnly) {
+
+				// Truncate the file if it exists, create file if it does not exist
+
+				openMode = FileAction.CreateNotExist + FileAction.TruncateExisting;
+			}
+			else if ( AccessMode.getAccessMode(flags) == AccessMode.ReadWrite) {
+
+				// Open the file if it exists, create the file if it does not exist
+
+				openMode = FileAction.CreateNotExist + FileAction.OpenIfExists;
+			}
+
+			m_pkt.setParameter(8, openMode);
+			m_pkt.setParameter(9, 0); // default allocation on create/truncate (long)
+			m_pkt.setParameter(10, 0); // ... high word
+			m_pkt.setParameter(11, 0);
+			m_pkt.setParameter(12, 0);
+			m_pkt.setParameter(13, 0);
+			m_pkt.setParameter(14, 0);
+
+			// Pack the file name string
+
+			m_pkt.resetBytePointer();
+			m_pkt.packString(fileName, m_pkt.isUnicode());
+
+			m_pkt.setByteCount();
+
+			// Send/receive the SMB file open packet
+
+			m_pkt.ExchangeSMB(this, m_pkt, true);
+
+			// Extract the file information from the received SMB packet
+
+			int fid = m_pkt.getParameter(2);
+			int attr = m_pkt.getParameter(3);
+			int fsiz = (m_pkt.getParameter(7) << 16) + m_pkt.getParameter(6);
+
+			// Create a file information object
+
+			FileInfo finfo = new FileInfo(fname, fsiz, attr);
+
+			// Create an SMB file object
+
+			return new CIFSFile(this, finfo, fid);
+		}
+		else {
+
+			// Default open mode is 'open if file exists'
+
+			int openMode = FileAction.NTOpen;
+			int accessMode = AccessMode.NTRead;
+
+			if ( AccessMode.getAccessMode(flags) == AccessMode.WriteOnly) {
+
+				// Truncate the file if it exists, create file if it does not exist
+
+				openMode = FileAction.NTOverwriteIf;
+				accessMode = AccessMode.NTWrite;
+			}
+			else if ( AccessMode.getAccessMode(flags) == AccessMode.ReadWrite) {
+
+				// Open the file if it exists, create the file if it does not exist
+
+				openMode = FileAction.NTOpenIf;
+				accessMode = AccessMode.NTReadWrite;
+			}
+
+			// Open the remote file
+
+			return NTCreate(fileName, accessMode, FileAttribute.NTNormal, SharingMode.READWRITEDELETE, openMode, 0, WinNT.CreateFile);
+		}
+	}
+
+	/**
+	 * Rename a file, or set of files, on the remote file server.
+	 * 
+	 * @param curnam Current file name string, may contain wildcards. If the path does not start
+	 *            with a '\' the current working directory string will be preprended.
+	 * @param newnam New file name.
+	 * @param attr Search attributes, to determine which file(s) to rename.
+	 * @see org.alfresco.jlan.server.filesys.FileAttribute
+	 * @return true if the file rename request was successful, else false.
+	 * @exception java.io.IOException If an I/O error occurs.
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	public final boolean RenameFile(String curnam, String newnam, int attr)
+		throws java.io.IOException, SMBException {
+
+		// Create an SMB rename packet
+
+		m_pkt.setFlags(getDefaultFlags());
+		m_pkt.setFlags2(getDefaultFlags2());
+
+		m_pkt.setCommand(PacketType.RenameFile);
+		m_pkt.setUserId(this.getUserId());
+		m_pkt.setTreeId(this.getTreeId());
+
+		m_pkt.setParameterCount(1);
+		m_pkt.setParameter(0, attr);
+
+		// Add the current working directory path to the file names if they do not contain a path
+
+		String fromName = curnam;
+		if ( fromName.startsWith("\\") == false)
+			fromName = PCShare.makePath(getWorkingDirectory(), curnam);
+
+		String toName = newnam;
+		if ( toName.startsWith("\\") == false)
+			toName = PCShare.makePath(getWorkingDirectory(), newnam);
+
+		// Pack the current and new file names
+
+		m_pkt.resetBytePointer();
+
+		m_pkt.packByte(DataType.ASCII);
+		m_pkt.packString(fromName, m_pkt.isUnicode());
+
+		m_pkt.packByte(DataType.ASCII);
+		m_pkt.packString(toName, m_pkt.isUnicode());
+
+		m_pkt.setByteCount();
+
+		// Send/receive the SMB rename file(s) packet
+
+		m_pkt.ExchangeSMB(this, m_pkt, true);
+
+		// Check if we got a valid response
+
+		if ( m_pkt.isValidResponse())
+			return true;
+
+		// Invalid rename request
+
+		return false;
+	}
+
+	/**
+	 * Set file information for the specified file.
+	 * 
+	 * @param fname File name of the file to set information for.
+	 * @param finfo File information containing the new values.
+	 * @exception java.io.IOException If an I/O error occurs.
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	public final void setFileInformation(String fname, FileInfo finfo)
+		throws java.io.IOException, SMBException {
+
+		// Create an SMB set file information packet
+
+		m_pkt.setCommand(PacketType.SetFileAttributes);
+		m_pkt.setUserId(this.getUserId());
+		m_pkt.setTreeId(this.getTreeId());
+
+		m_pkt.setFlags(getDefaultFlags());
+		m_pkt.setFlags2(getDefaultFlags2());
+
+		// Set the call parameters
+
+		m_pkt.setParameterCount(8);
+
+		m_pkt.setParameter(0, finfo.getFileAttributes());
+		m_pkt.setParameter(1, finfo.getModifyDateTime().asSMBTime());
+		m_pkt.setParameter(2, finfo.getModifyDateTime().asSMBDate());
+
+		for (int i = 3; i < 8; i++)
+			m_pkt.setParameter(i, 0);
+
+		// Build the full path string
+
+		String fileName = fname;
+		if ( fname.startsWith("\\") == false)
+			fileName = PCShare.makePath(getWorkingDirectory(), fname);
+
+		// Pack the file name
+
+		m_pkt.resetBytePointer();
+		m_pkt.packByte(DataType.ASCII);
+		m_pkt.packString(fileName, m_pkt.isUnicode());
+
+		m_pkt.setByteCount();
+
+		// Send/receive the SMB set file information packet
+
+		m_pkt.ExchangeSMB(this, m_pkt, true);
+	}
+
+	/**
+	 * Set file information for the specified file, using the file id
+	 * 
+	 * @param file File to set information for.
+	 * @param finfo File information containing the new values.
+	 * @exception java.io.IOException If an I/O error occurs.
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	public final void setFileInformation(SMBFile file, FileInfo finfo)
+		throws java.io.IOException, SMBException {
+
+		// Create an SMB set file information packet
+
+		m_pkt.setCommand(PacketType.SetInformation2);
+		m_pkt.setUserId(this.getUserId());
+		m_pkt.setTreeId(this.getTreeId());
+
+		m_pkt.setFlags(getDefaultFlags());
+		m_pkt.setFlags2(getDefaultFlags2());
+
+		// Set the call parameters
+
+		m_pkt.setParameterCount(7);
+
+		m_pkt.setParameter(0, file.getFileId());
+
+		// Pack the new creation date/time, if available
+
+		if ( finfo.hasCreationDateTime()) {
+			m_pkt.setParameter(1, finfo.getCreationDateTime().asSMBTime());
+			m_pkt.setParameter(2, finfo.getCreationDateTime().asSMBDate());
+		}
+		else {
+			m_pkt.setParameter(1, 0);
+			m_pkt.setParameter(2, 0);
+		}
+
+		// Pack the new access date/time, if available
+
+		if ( finfo.hasAccessDateTime()) {
+			m_pkt.setParameter(3, finfo.getAccessDateTime().asSMBTime());
+			m_pkt.setParameter(4, finfo.getAccessDateTime().asSMBDate());
+		}
+		else {
+			m_pkt.setParameter(3, 0);
+			m_pkt.setParameter(4, 0);
+		}
+
+		// Pack the new modify date/time, if available
+
+		if ( finfo.hasModifyDateTime()) {
+			m_pkt.setParameter(5, finfo.getModifyDateTime().asSMBTime());
+			m_pkt.setParameter(6, finfo.getModifyDateTime().asSMBDate());
+		}
+		else {
+			m_pkt.setParameter(5, 0);
+			m_pkt.setParameter(6, 0);
+		}
+
+		// Clear the byte count
+
+		m_pkt.setByteCount(0);
+
+		// Send/receive the SMB set file information packet
+
+		m_pkt.ExchangeSMB(this, m_pkt, true);
+	}
+
+	/**
+	 * Set file attributes for the specified file, using the file name
+	 * 
+	 * @param fname File name of the file to set information for.
+	 * @param attrib File attributes mask
+	 * @see org.alfresco.jlan.server.filesys.FileAttribute
+	 * @exception java.io.IOException If an I/O error occurs.
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	public final void setFileAttributes(String fname, int attrib)
+		throws java.io.IOException, SMBException {
+
+		// Create an SMB set file information packet
+
+		m_pkt.setCommand(PacketType.SetFileAttributes);
+		m_pkt.setUserId(this.getUserId());
+		m_pkt.setTreeId(this.getTreeId());
+
+		m_pkt.setFlags(getDefaultFlags());
+		m_pkt.setFlags2(getDefaultFlags2());
+
+		// Set the call parameters
+
+		m_pkt.setParameterCount(8);
+
+		m_pkt.setParameter(0, attrib);
+		m_pkt.setParameter(1, 0);
+		m_pkt.setParameter(2, 0);
+
+		for (int i = 3; i < 8; i++)
+			m_pkt.setParameter(i, 0);
+
+		// Build the full path string
+
+		String fileName = fname;
+		if ( fname.startsWith("\\") == false)
+			fileName = PCShare.makePath(getWorkingDirectory(), fname);
+
+		// Pack the file name
+
+		m_pkt.resetBytePointer();
+		m_pkt.packByte(DataType.ASCII);
+		m_pkt.packString(fileName, m_pkt.isUnicode());
+
+		m_pkt.setByteCount();
+
+		// Send/receive the SMB set file information packet
+
+		m_pkt.ExchangeSMB(this, m_pkt, true);
+	}
+
+	/**
+	 * Start a search of the specified directory returning information for each file/directory
+	 * found.
+	 * 
+	 * @param dir Directory to start searching. If the directory string does not start with a '\'
+	 *            then the directory name is prepended with the current working directory.
+	 * @param attr Search attributes, to determine the types of files/directories returned. @see
+	 *            org.alfresco.jlan.server.filesys.FileAttribute
+	 * @param level Information level required
+	 * @return SMBSearchContext for this search, else null
+	 * @exception java.io.IOException If an I/O error occurs
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	public final SearchContext StartSearch(String dir, int attr, int level)
+		throws java.io.IOException, SMBException {
+
+		// Create a new SMB search context
+
+		TransSearchContext srch = new TransSearchContext(this);
+		if ( srch == null)
+			return null;
+
+		// Start the search and return the search context
+
+		srch.StartSearch(dir, attr, level);
+		return srch;
+	}
+
+	/**
+	 * Perform an NTCreateAndX SMB to create/open a file or directory
+	 * 
+	 * @param name File/directory name
+	 * @param access Desired access mode.
+	 * @see org.alfresco.jlan.server.filesys.AccessMode
+	 * @param attrib Required file attributes.
+	 * @see org.alfresco.jlan.server.filesys.FileAttribute
+	 * @param sharing Shared access mode
+	 * @param exists Action to take if file/directory exists.
+	 * @see org.alfresco.jlan.server.filesys.FileAction
+	 * @param initSize Initial file allocation size, in bytes
+	 * @param createOpt Create file options
+	 * @return CIFSFile
+	 * @exception IOException
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	public final CIFSFile NTCreate(String name, int access, int attrib, int sharing, int exists, long initSize, int createOpt)
+		throws IOException, SMBException {
+
+		// Call the main NTCreate method
+
+		return NTCreateInternal(name, 0, access, attrib, sharing, exists, initSize, createOpt, true);
+	}
+
+	/**
+	 * Perform an NTCreateAndX SMB to create/open a file with an oplock
+	 * 
+	 * @param name File/directory name
+	 * @param oplockFlags int
+	 * @param access Desired access mode.
+	 * @see org.alfresco.jlan.server.filesys.AccessMode
+	 * @param attrib Required file attributes.
+	 * @see org.alfresco.jlan.server.filesys.FileAttribute
+	 * @param sharing Shared access mode
+	 * @param exists Action to take if file/directory exists.
+	 * @see org.alfresco.jlan.server.filesys.FileAction
+	 * @param initSize Initial file allocation size, in bytes
+	 * @param createOpt Create file options
+	 * @return CIFSFile
+	 * @exception IOException
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	public final CIFSFile NTCreateWithOplock(String name, int oplockFlags, OplockInterface oplockIface,
+											 int access, int attrib, int sharing, int exists, long initSize, int createOpt)
+		throws IOException, SMBException {
+
+		// Call the main NTCreate method
+
+		CIFSFile cifsFile = NTCreateInternal(name, oplockFlags, access, attrib, sharing, exists, initSize, createOpt, true);
+		if ( cifsFile != null && cifsFile.getOplockType() != OpLock.TypeNone) {
+			
+			// Set the oplock interface
+			
+			cifsFile.setOplockInterface( oplockIface);
+			
+			// Add the file to the list of oplocked files, need to access the file to call
+			// the oplock interface if an oplock break is received asynchronously from the server
+			
+			if ( m_oplockFiles == null)
+				m_oplockFiles = new HashMap<Integer, CIFSFile>();
+			m_oplockFiles.put( new Integer( cifsFile.getFileId()), cifsFile);
+		}
+		
+		// Return the file
+		
+		return cifsFile;
+	}
+
+	/**
+	 * Perform an NT query security descriptor transaction for the specified file or directory
+	 * 
+	 * @param fid File identifier, via SMBFile.getFileId() of an open file.
+	 * @param flags Security descriptor elements to return (Owner, Group, SACL, DACL).
+	 * @see org.alfresco.jlan.smb.nt.SecurityDescriptor
+	 * @return SecurityDescriptor
+	 * @exception IOException
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	public final SecurityDescriptor NTQuerySecurityDescriptor(int fid, int flags)
+		throws IOException, SMBException {
+
+		// Check if we have negotiated NT dialect
+
+		if ( getDialect() != Dialect.NT)
+			throw new SMBException(SMBStatus.NetErr, SMBStatus.NETUnsupported);
+
+		// Create the request transaction buffer
+
+		TransactBuffer reqBuf = new TransactBuffer(PacketType.NTTransQuerySecurityDesc, null, 0, 8, 0);
+
+		// Pack the parameter block for the request
+
+		DataBuffer paramBuf = reqBuf.getParameterBuffer();
+
+		paramBuf.putShort(fid);
+		paramBuf.putShort(0);
+		paramBuf.putInt(flags);
+
+		// Perform the query security descriptor transaction
+
+		NTTransPacket ntPkt = new NTTransPacket();
+		TransactBuffer respBuf = ntPkt.doTransaction(this, reqBuf);
+
+		// Check if a security descriptor has been returned
+
+		SecurityDescriptor secDesc = null;
+
+		if ( respBuf != null && respBuf.hasDataBuffer()) {
+
+			// Get the returned data
+
+			DataBuffer dataBuf = respBuf.getDataBuffer();
+
+			try {
+				secDesc = new SecurityDescriptor();
+				secDesc.loadDescriptor(dataBuf.getBuffer(), dataBuf.getOffset());
+			}
+			catch (LoadException ex) {
+				secDesc = null;
+			}
+		}
+
+		// Return the security descriptor
+
+		return secDesc;
+	}
+
+	/**
+	 * Set the security descriptor for the specified file/directory
+	 * 
+	 * @param fid File identifier, via SMBFile.getFileId() of an open file.
+	 * @param secdesc Security descriptor
+	 * @param flags Fields to set (Owner, Group, SACL, DACL).
+	 * @see org.alfresco.jlan.smb.nt.SecurityDescriptor
+	 * @exception IOException If a network error occurs
+	 * @exception SMBException If an SMB level error occurs
+	 * @exception SaveException If the security descriptor cannot be stored
+	 */
+	public final void NTSetSecurityDescriptor(int fid, SecurityDescriptor secdesc, int flags)
+		throws IOException, SMBException, SaveException {
+
+		// Check if we have negotiated NT dialect
+
+		if ( getDialect() != Dialect.NT)
+			throw new SMBException(SMBStatus.NetErr, SMBStatus.NETUnsupported);
+
+		// Create the request transaction buffer
+
+		TransactBuffer reqBuf = new TransactBuffer(PacketType.NTTransSetSecurityDesc, null, 0, 8, 65000);
+
+		// Pack the parameter block
+
+		DataBuffer paramBuf = reqBuf.getParameterBuffer();
+
+		paramBuf.putShort(fid);
+		paramBuf.putShort(0);
+		paramBuf.putInt(flags);
+
+		// Pack the data block
+
+		DataBuffer dataBuf = reqBuf.getDataBuffer();
+		int len = secdesc.saveDescriptor(dataBuf.getBuffer(), 0);
+		dataBuf.setLength(len);
+
+		// Perform the set security descriptor transaction
+
+		NTTransPacket ntPkt = new NTTransPacket();
+		ntPkt.doTransaction(this, reqBuf);
+	}
+
+	/**
+	 * Add a change notification filter for the specified directory
+	 * 
+	 * @param fid File id, from SMBFile.getFileId() of an open directory. The directory should be
+	 *            opened using the NTCreate() method.
+	 * @param filter Directory watch filter flags. @see org.alfresco.jlan.client.nt.NotifyChange.
+	 * @param watchTree true to watch sub-directories, false to watch the specified directory only
+	 * @param handler DirectoryWatcher implementation. @see
+	 *            org.alfresco.jlan.client.smb.DirectoryWatcher
+	 * @param autoResub true to automatically resubmit the notification filter after an event is
+	 *            received
+	 * @return AsynchRequest
+	 * @exception IOException
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	public final AsynchRequest NTNotifyChange(int fid, int filter, boolean watchTree, DirectoryWatcher handler, boolean autoResub)
+		throws IOException, SMBException {
+
+		// Create an asynchronous request to hold the notify details
+
+		NotifyChangeAsynchRequest areq = new NotifyChangeAsynchRequest(-1, fid, filter, watchTree, handler);
+		areq.setAutoReset(autoResub);
+
+		// Call the main notify change method to set the notify request
+
+		return NTNotifyChange(areq);
+	}
+
+	/**
+	 * Add a change notification filter for the specified directory
+	 * 
+	 * @param areq AsynchRequest
+	 * @return AsynchRequest
+	 * @exception IOException
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	public final AsynchRequest NTNotifyChange(AsynchRequest areq)
+		throws IOException, SMBException {
+
+		// Check if we have negotiated NT dialect
+
+		if ( getDialect() != Dialect.NT)
+			throw new SMBException(SMBStatus.NetErr, SMBStatus.NETUnsupported);
+
+		// Make sure the request is a notify change request
+
+		if ( areq instanceof NotifyChangeAsynchRequest == false)
+			throw new IOException("Invalid asynchronous request class, " + areq.getClass().getName());
+
+		// Get the notify change asynchronous request
+
+		NotifyChangeAsynchRequest nreq = (NotifyChangeAsynchRequest) areq;
+
+		// Build the NT notify change transaction SMB packet
+
+		NTTransPacket tpkt = new NTTransPacket();
+
+		tpkt.setUserId(getUserId());
+		tpkt.setTreeId(getTreeId());
+
+		tpkt.setFlags(getDefaultFlags());
+		tpkt.setFlags2(getDefaultFlags2());
+
+		// Set the multiplex id to identify this notify request
+
+		int mid = getNextMultiplexId();
+		tpkt.setMultiplexId(mid);
+
+		// Save the request id and clear the completed status of the request
+
+		nreq.setId(mid);
+		nreq.setCompleted(false);
+
+		// Initialize the NT transaction packet
+
+		tpkt.InitializeNTTransact(PacketType.NTTransNotifyChange, null, 0, null, 0, 4, 32, 0);
+
+		tpkt.resetSetupPointer();
+		tpkt.packInt(nreq.getFilter());
+		tpkt.packWord(nreq.getFileId());
+		tpkt.packByte(nreq.hasWatchTree() ? 1 : 0);
+		tpkt.packByte(0);
+
+		tpkt.setByteCount(0);
+
+		// Send the notify change transaction
+		//
+		// Note: There is no response until the notification triggers
+
+		tpkt.SendSMB(this);
+
+		// Add the request to the pending list and return the request
+
+		addAsynchronousRequest(nreq);
+		return nreq;
+	}
+
+	/**
+	 * Cancel an outstanding request. Used to cancel change notifications.
+	 * 
+	 * @param req AsynchRequest
+	 * @exception IOException
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	public final void NTCancel(AsynchRequest areq)
+		throws IOException, SMBException {
+
+		// Check if we have negotiated NT dialect
+
+		if ( getDialect() != Dialect.NT)
+			throw new SMBException(SMBStatus.NetErr, SMBStatus.NETUnsupported);
+
+		// Check if the request has auto-resubmit enabled, if so then disable the resubmit
+
+		if ( areq.hasAutoReset())
+			areq.setAutoReset(false);
+
+		// Check if the request has already completed, if so then there is no need to cancel it
+
+		if ( areq.hasCompleted())
+			return;
+
+		// Check if there is any data available for this network session, the request we are about
+		// to
+		// cancel may have just completed
+
+		if ( getSession().hasData()) {
+
+			// Clear out the recieve buffer
+
+			pingServer();
+
+			// Check if the request has now completed
+
+			if ( areq.hasCompleted())
+				return;
+		}
+
+		// Build the NTCancel SMB packet
+
+		m_pkt.setFlags(getDefaultFlags());
+		m_pkt.setFlags2(getDefaultFlags2());
+
+		m_pkt.setCommand(PacketType.NTCancel);
+		m_pkt.setUserId(getUserId());
+		m_pkt.setTreeId(getTreeId());
+		m_pkt.setMultiplexId(areq.getId());
+
+		m_pkt.setParameterCount(0);
+		m_pkt.setByteCount(0);
+
+		// Send/receive the NT cancel request
+
+		m_pkt.ExchangeSMB(this, m_pkt, false);
+
+		// Check the return status
+
+		boolean isValid = m_pkt.isValidResponse();
+
+		if ( isValid == true)
+			return;
+		else if ( m_pkt.isLongErrorCode() && m_pkt.getLongErrorCode() == SMBStatus.NTCancelled)
+			return;
+		else {
+
+			// Throw the SMB exception
+
+			if ( m_pkt.hasLongErrorCode())
+				throw new SMBException(SMBStatus.NTErr, m_pkt.getLongErrorCode());
+			else
+				throw new SMBException(m_pkt.getErrorClass(), m_pkt.getErrorCode());
+		}
+	}
+
+	/**
+	 * NT I/O control
+	 * 
+	 * @param ctrlCode int
+	 * @param fid int
+	 * @param fsctl boolean
+	 * @param data byte[]
+	 * @param dlen int
+	 * @param filter int
+	 * @return DataBuffer
+	 * @exception IOException
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	public final DataBuffer NTIOCtl(int ctrlCode, int fid, boolean fsctl, byte[] data, int dlen, int filter)
+		throws IOException, SMBException {
+
+		// Check if we have negotiated NT dialect
+
+		if ( getDialect() != Dialect.NT)
+			throw new SMBException(SMBStatus.NetErr, SMBStatus.NETUnsupported);
+
+		// Create the request transaction buffer
+
+		TransactBuffer reqBuf = new TransactBuffer(PacketType.NTTransIOCtl, 8, 0, data, 0, dlen);
+
+		// Pack the setup block
+
+		DataBuffer setupBuf = reqBuf.getSetupBuffer();
+
+		setupBuf.putInt(ctrlCode);
+		setupBuf.putShort(fid);
+		setupBuf.putByte(fsctl ? 1 : 0);
+		setupBuf.putByte(filter);
+
+		// Perform the I/O control transaction
+
+		NTTransPacket ntPkt = new NTTransPacket();
+		TransactBuffer respBuf = ntPkt.doTransaction(this, reqBuf);
+
+		// Check if there is any return data
+
+		if ( respBuf != null)
+			return respBuf.getDataBuffer();
+		return null;
+	}
+
+	/**
+	 * Get file information for the specified open file/directory, returning the requested
+	 * information level
+	 * 
+	 * @param fid File id for the file or directory, from SMBFile.getFileId().
+	 * @param level Information level. @see org.alfresco.jlan.smb.FileInfoLevel
+	 * @return FileInfo
+	 * @exception IOException
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	public final FileInfo NTGetFileInformation(int fid, int level)
+		throws IOException, SMBException {
+
+		// Create the request transaction buffer
+
+		TransactBuffer reqBuf = new TransactBuffer(PacketType.Trans2QueryFile, null, 0, 4, 0);
+
+		// Pack the parameter block
+
+		DataBuffer paramBuf = reqBuf.getParameterBuffer();
+
+		paramBuf.putShort(fid);
+		paramBuf.putShort(level);
+
+		// Perform the get file information transaction
+
+		TransPacket tpkt = new TransPacket(m_pkt.getBuffer());
+		TransactBuffer respBuf = tpkt.doTransaction(this, reqBuf);
+
+		// Unpack the received file information data
+
+		FileInfo finfo = null;
+
+		if ( respBuf != null && respBuf.hasDataBuffer()) {
+
+			// Unpack the file information
+
+			DataBuffer buf = respBuf.getDataBuffer();
+
+			switch (level) {
+				case FileInfoLevel.PathStandard:
+					finfo = FileInfoPacker.unpackFileInfoStandard("", buf, false);
+					break;
+				case FileInfoLevel.PathQueryEASize:
+					finfo = FileInfoPacker.unpackFileInfoStandard("", buf, true);
+					break;
+				case FileInfoLevel.PathAllEAs:
+					break;
+				case FileInfoLevel.PathFileBasicInfo:
+					finfo = FileInfoPacker.unpackQueryBasicInfo("", buf);
+					break;
+				case FileInfoLevel.PathFileStandardInfo:
+					finfo = FileInfoPacker.unpackQueryStandardInfo("", buf);
+					break;
+				case FileInfoLevel.PathFileEAInfo:
+					finfo = FileInfoPacker.unpackQueryEAInfo("", buf);
+					break;
+				case FileInfoLevel.PathFileNameInfo:
+					finfo = FileInfoPacker.unpackQueryNameInfo(buf, respBuf.isUnicode());
+					break;
+				case FileInfoLevel.PathFileAllInfo:
+					finfo = FileInfoPacker.unpackQueryAllInfo(buf, respBuf.isUnicode());
+					break;
+				case FileInfoLevel.PathFileAltNameInfo:
+					finfo = FileInfoPacker.unpackQueryNameInfo(buf, respBuf.isUnicode());
+					break;
+				case FileInfoLevel.PathFileStreamInfo:
+					finfo = FileInfoPacker.unpackQueryStreamInfo("", buf, respBuf.isUnicode());
+					break;
+				case FileInfoLevel.PathFileCompressionInfo:
+					finfo = FileInfoPacker.unpackQueryCompressionInfo("", buf);
+					break;
+			}
+		}
+
+		// If the file information is valid, set the file id so the file information can be used
+		// to set information
+
+		if ( finfo != null)
+			finfo.setFileId(fid);
+
+		// Return the file information
+
+		return finfo;
+	}
+
+	/**
+	 * Get file information for the specified open file/directory, returning the requested
+	 * information level
+	 * 
+	 * @param fid File id for the file or directory, from SMBFile.getFileId().
+	 * @param level Information level. @see org.alfresco.jlan.smb.FileInfoLevel
+	 * @return TransactBuffer
+	 * @exception IOException
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	public final TransactBuffer NTGetFileInformationRaw(int fid, int level)
+		throws IOException, SMBException {
+
+		// Create the request transaction buffer
+
+		TransactBuffer reqBuf = new TransactBuffer(PacketType.Trans2QueryFile, null, 0, 4, 0);
+
+		// Pack the parameter block
+
+		DataBuffer paramBuf = reqBuf.getParameterBuffer();
+
+		paramBuf.putShort(fid);
+		paramBuf.putShort(level);
+
+		// Perform the get file information transaction
+
+		TransPacket tpkt = new TransPacket(m_pkt.getBuffer());
+		return tpkt.doTransaction(this, reqBuf);
+	}
+
+	/**
+	 * Set file information that allows setting different information levels
+	 * 
+	 * @param finfo FileInfo
+	 * @param level Information level. @see org.alfresco.jlan.smb.FileInfoLevel
+	 * @exception IOException
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	public final void NTSetFileInformation(FileInfo finfo, int level)
+		throws IOException, SMBException {
+
+		// Create the request transaction buffer
+
+		TransactBuffer reqBuf = new TransactBuffer(PacketType.Trans2SetFile, null, 0, 6, 65000);
+
+		// Pack the parameter block
+
+		DataBuffer paramBuf = reqBuf.getParameterBuffer();
+
+		paramBuf.putShort(finfo.getFileId());
+		paramBuf.putShort(level);
+		paramBuf.putShort(0);
+
+		// Pack the file information into the data buffer
+
+		DataBuffer dataBuf = reqBuf.getDataBuffer();
+
+		switch (level) {
+			case FileInfoLevel.SetStandard:
+				FileInfoPacker.packFileInfoStandard(finfo, dataBuf, false);
+				break;
+			case FileInfoLevel.SetQueryEASize:
+				FileInfoPacker.packFileInfoStandard(finfo, dataBuf, true);
+				break;
+			case FileInfoLevel.SetBasicInfo:
+				FileInfoPacker.packFileBasicInfo(finfo, dataBuf);
+				break;
+			case FileInfoLevel.SetDispositionInfo:
+				break;
+			case FileInfoLevel.SetAllocationInfo:
+				break;
+			case FileInfoLevel.SetEndOfFileInfo:
+				break;
+		}
+		;
+
+		// Perform the get file information transaction
+
+		TransPacket tpkt = new TransPacket(m_pkt.getBuffer());
+		tpkt.doTransaction(this, reqBuf);
+	}
+
+	/**
+	 * Set the delete on close flag for an open file
+	 * 
+	 * @param fid File id for the file or directory, from SMBFile.getFileId().
+	 * @param delFlag true to delete the file on close, or false to clear a previous delete on close
+	 *            request
+	 * @exception IOException
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	public final void NTSetDeleteOnClose(int fid, boolean delFlag)
+		throws IOException, SMBException {
+
+		// Create the data block for the set file disposition info level (0x102)
+
+		byte[] dblock = new byte[4];
+		dblock[0] = delFlag == true ? (byte) 1 : (byte) 0;
+
+		// Set the delete on close setting for the open file
+
+		NTSetFileInformationRaw(fid, dblock, 2, 0x102);
+	}
+
+	/**
+	 * Set the end of file position for the open file
+	 * 
+	 * @param fid File id for the file or directory, from SMBFile.getFileId().
+	 * @param pos New end of file position
+	 * @exception IOException
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	public final void NTSetEndOfFile(int fid, long pos)
+		throws IOException, SMBException {
+
+		// Create the data block for the set end of file info level (0x104)
+
+		byte[] dblock = new byte[8];
+		DataPacker.putIntelLong(pos, dblock, 0);
+
+		// Set the end of file position for the open file
+
+		NTSetFileInformationRaw(fid, dblock, 8, 0x104);
+	}
+
+	/**
+	 * Set the file allocation size for the open file
+	 * 
+	 * @param fid File id for the file or directory, from SMBFile.getFileId().
+	 * @param alloc New file allocation size
+	 * @exception IOException
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	public final void NTSetFileAllocation(int fid, long alloc)
+		throws IOException, SMBException {
+
+		// Create the data block for the set file allocation info level (0x103)
+
+		byte[] dblock = new byte[8];
+		DataPacker.putIntelLong(alloc, dblock, 0);
+
+		// Set the file allocation for the open file
+
+		NTSetFileInformationRaw(fid, dblock, 8, 0x103);
+	}
+
+	/**
+	 * Set file information that allows setting different information levels
+	 * 
+	 * @param fid File id for the file or directory, from SMBFile.getFileId().
+	 * @param data Raw file information data block.
+	 * @param dlen Raw data block length.
+	 * @param level File information level. @see org.alfresco.jlan.smb.FileInfoLevel
+	 * @exception IOException
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	private final void NTSetFileInformationRaw(int fid, byte[] data, int dlen, int level)
+		throws IOException, SMBException {
+
+		// Create the request transaction buffer
+
+		TransactBuffer reqBuf = new TransactBuffer(PacketType.Trans2SetFile, 0, 6, data, 0, dlen);
+
+		// Pack the parameter block
+
+		DataBuffer paramBuf = reqBuf.getParameterBuffer();
+
+		paramBuf.putShort(fid);
+		paramBuf.putShort(level);
+		paramBuf.putShort(0);
+
+		// Perform the get file information transaction
+
+		TransPacket tpkt = new TransPacket(m_pkt.getBuffer());
+		tpkt.doTransaction(this, reqBuf);
+	}
+
+	/**
+	 * Get the device information
+	 * 
+	 * @return DeviceInfo @see org.alfresco.jlan.client.info.DeviceInfo
+	 * @exception IOException
+	 * @exception SMBException
+	 */
+	public final DeviceInfo NTGetDeviceInfo()
+		throws IOException, SMBException {
+
+		// Create the request transaction buffer
+
+		TransactBuffer reqBuf = new TransactBuffer(PacketType.Trans2QueryFileSys, null, 0, 2, 0);
+
+		// Pack the parameter block
+
+		DataBuffer paramBuf = reqBuf.getParameterBuffer();
+
+		paramBuf.putShort(FileInfoLevel.FSInfoQueryDevice);
+
+		// Perform the get file information transaction
+
+		TransPacket tpkt = new TransPacket(m_pkt.getBuffer());
+		TransactBuffer respBuf = tpkt.doTransaction(this, reqBuf);
+
+		// Unpack the received device information data
+
+		DeviceInfo devInfo = null;
+
+		if ( respBuf != null && respBuf.hasDataBuffer()) {
+
+			// Unpack the device information
+
+			DataBuffer buf = respBuf.getDataBuffer();
+
+			int typ = buf.getInt();
+			int chr = buf.getInt();
+
+			// Return the device information
+
+			devInfo = new DeviceInfo(typ, chr);
+		}
+
+		// Return the device information
+
+		return devInfo;
+	}
+
+	/**
+	 * Get the device attributes information
+	 * 
+	 * @return DeviceAttributesInfo
+	 * @exception IOException
+	 * @exception SMBException
+	 */
+	public final DeviceAttributesInfo NTGetDeviceAttributes()
+		throws IOException, SMBException {
+
+		// Create the request transaction buffer
+
+		TransactBuffer reqBuf = new TransactBuffer(PacketType.Trans2QueryFileSys, null, 0, 2, 0);
+
+		// Pack the parameter block
+
+		DataBuffer paramBuf = reqBuf.getParameterBuffer();
+
+		paramBuf.putShort(FileInfoLevel.FSInfoQueryAttribute);
+
+		// Perform the get file information transaction
+
+		TransPacket tpkt = new TransPacket(m_pkt.getBuffer());
+		TransactBuffer respBuf = tpkt.doTransaction(this, reqBuf);
+
+		// Unpack the received device attribute information data
+
+		DeviceAttributesInfo attrInfo = null;
+
+		if ( respBuf != null && respBuf.hasDataBuffer()) {
+
+			// Unpack the device attribute information
+
+			DataBuffer buf = respBuf.getDataBuffer();
+
+			int attr = buf.getInt();
+			int maxLen = buf.getInt();
+			int lblLen = buf.getInt();
+
+			if ( respBuf.isUnicode())
+				lblLen = lblLen / 2;
+
+			String label = buf.getString(lblLen, respBuf.isUnicode());
+
+			// Return the device attributes
+
+			attrInfo = new DeviceAttributesInfo(attr, maxLen, label);
+		}
+
+		// Return the device attribute information
+
+		return attrInfo;
+	}
+
+	/**
+	 * Get file information for the specified file.
+	 * 
+	 * @param fname File name of the file to return information for.
+	 * @param level Information level required. @see org.alfresco.jlan.smb.FileInfoLevel
+	 * @return TransactBuffer
+	 * @exception java.io.IOException If an I/O error occurs.
+	 * @exception java.io.FileNotFoundException If the remote file does not exist.
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	public final TransactBuffer getFileInformationRaw(String fname, int level)
+		throws java.io.IOException, java.io.FileNotFoundException, SMBException {
+
+		// Build the file name/path string
+
+		String pathName = fname;
+		if ( pathName.startsWith("\\") == false)
+			pathName = PCShare.makePath(getWorkingDirectory(), fname);
+
+		// Create the request transaction buffer
+
+		TransactBuffer reqBuf = new TransactBuffer(PacketType.Trans2QueryPath, null, 0, 512, 0);
+
+		// Pack the parameter block
+
+		DataBuffer paramBuf = reqBuf.getParameterBuffer();
+
+		paramBuf.putShort(level);
+		paramBuf.putInt(0);
+		paramBuf.putString(pathName, isUnicode());
+
+		// Perform the get file information transaction
+
+		TransPacket tpkt = new TransPacket(m_pkt.getBuffer());
+		return tpkt.doTransaction(this, reqBuf);
+	}
+
+	/**
+	 * Return the details for a symlink file/folder
+	 * 
+	 * @param linkPath String
+	 * @return SymLink
+	 * @exception Exception
+	 */
+	public final SymLink getSymLinkDetails(String linkPath)
+		throws Exception {
+
+		// Open the symlink file
+
+		CIFSFile linkFile = NTCreateInternal(linkPath, 0, AccessMode.NTRead + AccessMode.NTReadControl + AccessMode.NTReadAttrib
+				+ AccessMode.NTReadEA, FileAttribute.NTNormal, SharingMode.READWRITE, FileAction.NTOpen, 0,
+				WinNT.CreateReparsePoint, false);
+
+		SymLink symLink = null;
+		Exception retError = null;
+
+		try {
+
+			// Make sure the file is a reparse point
+
+			if ( linkFile.isReparsePoint()) {
+
+				// Get the symlink details
+
+				int ioctlCode = NTIOCtl.makeControlCode(NTIOCtl.DeviceFileSystem, NTIOCtl.FsCtlGetReparsePoint,
+						NTIOCtl.MethodBuffered, NTIOCtl.AccessAny);
+				DataBuffer linkBuf = NTIOCtl(ioctlCode, linkFile.getFileId(), true, null, 0, 0);
+
+				// Parse the returned structure
+
+				symLink = new SymLink(linkBuf);
+			}
+			else {
+
+				// Return an exception
+
+				retError = new IOException("Not a reparse point, " + linkPath);
+			}
+		}
+		catch (Exception ex) {
+
+			// Save the error
+
+			retError = ex;
+		}
+		finally {
+
+			// Close the link file
+
+			try {
+				linkFile.Close();
+			}
+			catch (Exception ex) {
+			}
+		}
+
+		// Check if there is an error to return
+
+		if ( retError != null)
+			throw retError;
+
+		// Return the symlink details
+
+		return symLink;
+	}
+
+	/**
+	 * Process incoming data checking for asynchronous response packets from the server
+	 * 
+	 * @param waitTime Receive timeout in milliseconds, zero for no timeout or -1 to not wait for
+	 *            data
+	 * @exception IOException
+	 * @exception SMBException
+	 */
+	public final void checkForAsynchReceive(int waitTime)
+		throws IOException, SMBException {
+
+		// Check if there is any data in the socket receive buffer, if the caller does not want
+		// to wait for a packet then return immediately
+
+		if ( waitTime == -1 && getSession().hasData() == false) {
+
+			// Check if we need to send an echo packet to the server to keep the SMB session alive
+
+			if ( (m_pkt.getLastPacketSendTime() + SessionKeepAlive) < System.currentTimeMillis())
+				pingServer();
+			return;
+		}
+
+		// Wait for an asynchronous response from the server
+
+		m_pkt.ReceiveAsynchSMB(this, waitTime == -1 ? 0 : waitTime);
+
+		// Check if we need to send an echo packet to the server to keep the SMB session alive.
+		//
+		// The asynchronous receive will usually result in the request being reset on the server, if
+		// not then we may need to send an echo request.
+
+		if ( (m_pkt.getLastPacketSendTime() + SessionKeepAlive) < System.currentTimeMillis())
+			pingServer();
+	}
+
+	
+	/**
+	 * Refresh the file information for an open file
+	 * 
+	 * @param smbFile SMBFile
+	 */
+	public void refreshFileInformation( SMBFile smbFile)
+		throws IOException, SMBException {
+		
+	}
+	
+	/**
+	 * Process an asynchronous response packet
+	 * 
+	 * @param pkt SMBPacket
+	 */
+	protected void processAsynchResponse(SMBPacket pkt) {
+
+		// Check for a locking request from the server, an oplock break
+		
+		if ( pkt.isRequest() == true && pkt.getCommand() == PacketType.LockingAndX) {
+			
+			// Unpack the file id and flags
+			
+			int fileId = pkt.getParameter( 2);
+			int flags  = pkt.getParameter( 3);
+			
+			// Check for an oplock break
+			
+			if ( m_oplockFiles != null && ( flags & LockingAndX.OplockBreak) != 0) {
+
+				try {
+					
+					// Find the oplocked file
+					
+					CIFSFile cifsFile = m_oplockFiles.get( new Integer( fileId));
+					int breakToOpLock = OpLock.TypeNone;
+					
+					if ( cifsFile != null) {
+						
+						// Check if the file has an oplock callback interface
+						
+						if ( cifsFile.getOplockInterface() != null) {
+
+							// Call the oplock interface
+							
+							breakToOpLock = cifsFile.getOplockInterface().oplockBreak( cifsFile);
+						}
+						else {
+							
+							// Flush any pending data on the file 
+							
+							cifsFile.Flush();
+						}
+					}
+					
+					// Check if an oplock break response should be sent
+					
+					if ( cifsFile.getOplockInterface().sendAutomaticBreakResponse() == true) {
+
+						// Build an oplock break response
+						
+						SMBPacket respPkt = new SMBPacket( 128);
+						
+						respPkt.setCommand(PacketType.LockingAndX);
+						respPkt.setUserId(this.getUserId());
+						respPkt.setTreeId(this.getTreeId());
+		
+						respPkt.setFlags(getDefaultFlags() + SMBPacket.FLG_RESPONSE);
+						respPkt.setFlags2(getDefaultFlags2());
+		
+						respPkt.setParameterCount(8);
+						respPkt.setAndXCommand( PacketType.NoChainedCommand);
+						respPkt.setParameter(1, 0);							// AndX offset
+						respPkt.setParameter(2, fileId);
+						
+						// Break the oplock, or break to a level II shared oplock
+						
+						if ( breakToOpLock == OpLock.TypeLevelII)
+							respPkt.setParameter(3, LockingAndX.OplockBreak + LockingAndX.Level2OpLock);
+						else
+							respPkt.setParameter(3, LockingAndX.OplockBreak);
+							
+						respPkt.setParameterLong(4, 0);						// timeout
+						respPkt.setParameter(6, 0);							// number of unlocks
+						respPkt.setParameter(7, 0);							// number of locks
+						
+						respPkt.setByteCount( 0);
+						
+						// Send the oplock break to the server
+						//
+						// Note: The response flag must be set, and we do not expect a response from the server
+						
+						respPkt.SendSMB( this);
+						
+						// Set the new oplock type on the file
+						
+						cifsFile.setOplockType( breakToOpLock);
+						cifsFile.setOplockInterface( null);
+					}
+				}
+				catch (Exception ex) {
+						
+				}
+			}
+		}
+		else {
+			
+			// Check if there are any pending asynchronous requests queued
+	
+			if ( m_asynchRequests == null || m_asynchRequests.size() == 0)
+				return;
+	
+			// Find the matching asynchronous request and remove from the pending list
+	
+			AsynchRequest areq = removeAsynchronousRequest(pkt.getMultiplexId());
+			if ( areq == null)
+				return;
+	
+			// Mark the asynchronous request as completed
+	
+			areq.setCompleted(true);
+	
+			// Pass the packet to the asynchronous request for processing
+	
+			areq.processResponse(this, pkt);
+	
+			// Check if the request should be automatically resubmitted
+	
+			if ( areq.hasAutoReset()) {
+	
+				// Resubmit the request
+	
+				if ( areq.resubmitRequest(this, null) == true)
+					addAsynchronousRequest(areq);
+			}
+		}
+	}
+
+	/**
+	 * Add an asynchronous request to the list of pending requests
+	 * 
+	 * @param req AsynchRequest
+	 */
+	protected final void addAsynchronousRequest(AsynchRequest req) {
+
+		// Check if the asynchronous request list has been allocated
+
+		if ( m_asynchRequests == null)
+			m_asynchRequests = new ArrayList<AsynchRequest>();
+
+		// Add the request to the list
+
+		m_asynchRequests.add(req);
+	}
+
+	/**
+	 * Remove an asynchronous request from the pending list
+	 * 
+	 * @param id int
+	 * @return AsynchRequest
+	 */
+	protected final AsynchRequest removeAsynchronousRequest(int id) {
+
+		// Check if the list is valid
+
+		if ( m_asynchRequests == null)
+			return null;
+
+		// Find the request
+
+		AsynchRequest areq = null;
+		int idx = 0;
+
+		while (idx < m_asynchRequests.size() && areq == null) {
+
+			// Get the current request and check if it is the required request
+
+			AsynchRequest curReq = m_asynchRequests.get(idx);
+			if ( curReq.getId() == id)
+				areq = curReq;
+			else
+				idx++;
+		}
+
+		// Remove the request from the list
+
+		if ( areq != null)
+			m_asynchRequests.remove(areq);
+
+		// Return the removed request
+
+		return areq;
+	}
+
+	/**
+	 * Remove an asynchronous request from the pending list
+	 * 
+	 * @param req AsynchRequest
+	 * @return AsynchRequest
+	 */
+	protected final AsynchRequest removeAsynchronousRequest(AsynchRequest req) {
+
+		// Check if the list is valid
+
+		if ( m_asynchRequests == null)
+			return null;
+
+		// Remove the request from the list
+
+		m_asynchRequests.remove(req);
+		return req;
+	}
+
+	/**
+	 * Perform an NTCreateAndX SMB to create/open a file or directory
+	 * 
+	 * @param name File/directory name
+	 * @param createFlags int
+	 * @param access Desired access mode.
+	 * @see org.alfresco.jlan.server.filesys.AccessMode
+	 * @param attrib Required file attributes.
+	 * @see org.alfresco.jlan.server.filesys.FileAttribute
+	 * @param sharing Shared access mode
+	 * @param exists Action to take if file/directory exists.
+	 * @see org.alfresco.jlan.server.filesys.FileAction
+	 * @param initSize Initial file allocation size, in bytes
+	 * @param createOpt Create file options
+	 * @param throwErr Throw errors from the CIFS packet exchange
+	 * @return CIFSFile
+	 * @exception IOException
+	 * @exception SMBException If an SMB level error occurs
+	 */
+	protected final CIFSFile NTCreateInternal(String name, int createFlags, int access, int attrib, int sharing, int exists, long initSize,
+			int createOpt, boolean throwErr)
+		throws IOException, SMBException {
+
+		// Check if we have negotiated NT dialect
+
+		if ( getDialect() != Dialect.NT)
+			throw new SMBException(SMBStatus.NetErr, SMBStatus.NETUnsupported);
+
+		// Build the NTCreateAndX SMB packet
+
+		m_pkt.setFlags(getDefaultFlags());
+		m_pkt.setFlags2(getDefaultFlags2());
+
+		m_pkt.setCommand(PacketType.NTCreateAndX);
+		m_pkt.setUserId(getUserId());
+		m_pkt.setTreeId(getTreeId());
+
+		m_pkt.setParameterCount(24);
+		m_pkt.resetParameterPointer();
+
+		m_pkt.packByte(0xFF); // no chained command
+		m_pkt.packByte(0); // reserved
+		m_pkt.packWord(0); // AndX offset
+		m_pkt.packByte(0); // reserved
+
+		m_pkt.packWord((name.length() * 2) + 2); // name length in bytes, inc null
+		m_pkt.packInt(createFlags); // oplocks/extended response
+		m_pkt.packInt(0); // root FID
+		m_pkt.packInt(access); // desired access mode
+		m_pkt.packLong(initSize); // allocation size
+		m_pkt.packInt(attrib); // file attributes
+		m_pkt.packInt(sharing); // share access mode
+		m_pkt.packInt(exists); // action to take if file exists
+		m_pkt.packInt(createOpt); // file create options
+		m_pkt.packInt(2); // impersonation level, 0=anonymous, 2=impersonation
+		m_pkt.packByte(0); // security flags
+
+		m_pkt.resetBytePointer();
+		m_pkt.packString(name, m_pkt.isUnicode());
+
+		m_pkt.setByteCount();
+
+		// Send/receive the NT create andX request
+
+		m_pkt.ExchangeSMB(this, m_pkt, throwErr);
+
+		// Unpack the file/directory details
+
+		m_pkt.resetParameterPointer();
+		m_pkt.skipBytes(4);
+
+		int oplockTyp = m_pkt.unpackByte();
+		int fid = m_pkt.unpackWord();
+		int createAction = m_pkt.unpackInt();
+
+		long createTime = m_pkt.unpackLong();
+		long lastAccessTime = m_pkt.unpackLong();
+		long lastWriteTime = m_pkt.unpackLong();
+		long changeTime = m_pkt.unpackLong();
+
+		int attr = m_pkt.unpackInt();
+
+		long allocSize = m_pkt.unpackLong();
+		long eofOffset = m_pkt.unpackLong();
+
+		int devType = m_pkt.unpackWord();
+
+		// Create the file information
+
+		FileInfo finfo = new FileInfo(name, eofOffset, attr);
+		finfo.setFileId(fid);
+
+		// Convert the granted oplock type to internal type
+		
+		if ( oplockTyp == WinNT.GrantedOplockBatch)
+			oplockTyp = OpLock.TypeBatch;
+		else if ( oplockTyp == WinNT.GrantedOplockExclusive)
+			oplockTyp = OpLock.TypeExclusive;
+		else if ( oplockTyp == WinNT.GrantedOplockLevelII)
+			oplockTyp = OpLock.TypeLevelII;
+		else
+			oplockTyp = OpLock.TypeNone;
+		
+		// Create the file object
+
+		return new CIFSFile(this, finfo, fid, oplockTyp);
+	}
+	
+	/**
+	 * File closed, remove from the oplocked file list
+	 * 
+	 * @param cifsFile CIFSFile
+	 */
+	protected final void fileClosed( CIFSFile cifsFile) {
+		
+		// Check if there are any oplocked files
+		
+		if ( m_oplockFiles == null || m_oplockFiles.size() == 0)
+			return;
+		
+		// Remove the file from the oplocked list
+		
+		m_oplockFiles.remove( new Integer( cifsFile.getFileId()));
+	}
+}

Some files were not shown because too many files changed in this diff