Browse Source

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

Daniel Lazar 10 năm trước cách đây
mục cha
commit
d4a371c9b4
100 tập tin đã thay đổi với 16508 bổ sung1 xóa
  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()));
+	}
+}

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác