# JCIFS Exceptions and NtlmAuthenticator

This document describes how jCIFS communicates server errors to callers and how authentication related exceptions may be handled differently from regular exceptions. How jCIFS can be used to authenticate arbitrary user credentials is not discussed although it is in the FAQ.

## SmbException

There are hundreds of error codes that may be returned by a CIFS server. It would be unacceptable to have an Exception class for each so the jCIFS client represents all of these using the SmbException class. The NT STATUS code for an SmbException may be retrieved using it's getNtStatus() method.

All of the methods of SmbFile that generate network IO can potentially throw an SmbException. Under normal operation it is not necessary to catch and interrogate each exception however this mechanism provides the developer with an opportunity to make an application more sophisticated and robust in the event of an error. Consider the following example:

    while( retry-- ) {
        try {
            ...
            d = f.lastModified();
            ...
        } catch( SmbException se ) {
            if( se.getNtStatus() == SmbException.NT_STATUS_DEVICE_NOT_READY ) {
                // The tape drive is not ready yet, let's wait a little while
                Thread.sleep( retryPeriod );
            } else {
                throw se;
            }
        }

In this example (some kind of backup utility that accesses a shared tape drive perhaps) the developer knows that accessing this device will return an NT_STATUS_DEVICE_NOT_READY status code if the tape has not finished rewinding. The SmbException is caught and examined for this condition so that the program may sleep for a predefined amount of time while waiting for the drive to become ready. Otherwise exceptions with status codes other than NT_STATUS_DEVICE_NOT_READY are re-thrown. Adding this kind of support to your application usually requires some experimentation to determine what types of error codes will be returned by a server and in general the technique is not portable.

The SmbException class has text messages associated with the more common status codes. The SmbExcption for NT_STATUS_DEVICE_NOT_READY will generate the familiar "The device is not ready" message you get when trying to access a CDROM drive without a disk in it. Of course not all of the various error codes have associated text messages and the ones that do are in English at the moment.

Note: Eventually there may be a java.util.ResourceBundle to manage common text messages in various languages.

## SmbAuthException

One rather common not-so-exceptional set of exceptions are the authentication related exceptions. For example, if an ordinary user attempts to read a file which may only be accessed by a user with "Backup Operator" permissions, the server will return a status code of NT_STATUS_ACCESS_DENIED (at least NT with NTFS will). Similarly if the user is restricted to accessing a server during a prescribed time period, the server may return an NT_STATUS_INVALID_LOGON_HOURS status code. To catch these exceptions (perhaps to ask the user for alternative credentials), using only the mechanism described above, a switch block like the following would be necessary:

    switch( se.getNtStatus() ) {
        case SmbException.NT_STATUS_OK:
            break;
        case SmbException.NT_STATUS_ACCESS_DENIED:
        case SmbException.NT_STATUS_WRONG_PASSWORD:
        case SmbException.NT_STATUS_LOGON_FAILURE:
        case SmbException.NT_STATUS_ACCOUNT_RESTRICTION:
        case SmbException.NT_STATUS_INVALID_LOGON_HOURS:
        case SmbException.NT_STATUS_INVALID_WORKSTATION:
        case SmbException.NT_STATUS_PASSWORD_EXPIRED:
        case SmbException.NT_STATUS_ACCOUNT_DISABLED:
        case SmbException.NT_STATUS_ACCOUNT_LOCKED_OUT:
            // get new authentication credentials and try again
    }

This is cumbersome. Fortunately, jCIFS provides a mechanism to do the equivalent of the above internally and throw an SmbAuthException instead of the more generic SmbException. SmbAuthException extends SmbException but provides no additional functionality other than being a different class to catch:

    } catch( SmbAuthException sae ) {
        // handle authentication related issue here
    } catch( SmbException se ) {
        // any special SMB related exception handling
    }

This is useful by itself but there's an addition facility for handing these exceptions.

## NtlmAuthenticator and NtlmAuthenticator.setDefault( NtlmAuthenticator )

Like java.net.Authenticator the NtlmAuthenticator class can be extended by an application and registered with the NtlmAuthenticator.setDefault(jcifs.smb.NtlmAuthenticator) method to add sophisticated authentication handing to an application. When an SmbAuthException occurs the NtlmAuthenticator's getNtlmPasswordAuthentication() implementation will be called. The NtlmPasswordAuthentication object returned will be used to resubmit the request. The developer need not worry about retrying the operation. The authenticator will be called repeatedly until null is returned. Only one NtlmAuthenticator may be registered.

The SmbShell example illustrates the use of NtlmAuthenticator:

    public class SmbShell extends NtlmAuthenticator {

        protected NtlmPasswordAuthentication getNtlmPasswordAuthentication() {
            System.out.println( getRequestingException().getMessage() +
                        " for " + getRequestingURL() );
            System.out.print( "username: " );
            try {
                int i;
                String username = readLine();
                String domain = null, password;

                if(( i = username.indexOf( '\\' )) != -1 ) {
                    domain = username.substring( 0, i );
                    username = username.substring( i + 1 );
                }
                System.out.print( "password: " );
                password = readLine();
                if( password.length() == 0 ) {
                    return null;
                }
                return new NtlmPasswordAuthentication( domain, username, password );
            } catch( Exception e ) {
            }
            return null;
        }

        public SmbShell( String start ) {
            this.start = start;
            NtlmAuthenticator.setDefault( this );
        }

When the SmbShell user tries to access a resource for which they do not have adequate permissions the SmbAuthException will be trapped and the above getNtlmPasswordAuthentication method will be called upon to supply alternative credentials. The commandline dialog of this might look like:

    $ java -Djcifs.netbios.wins=192.168.1.15 SmbShell smb://myworkgroup/
    myworkgroup/> cd storage0605/c$/
    The user account has expired for smb://storage0605/c$/
    username: mydomain\miallen
    password: fretos
    c$/>