//Warning: This code has been marked up for HTML

/**************************************************************************
*  Novell Software Developer Kit
*
*  Copyright (C) 2002-2003 Novell, Inc. All Rights Reserved.
*
*  THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND TREATIES.
*  USE AND REDISTRIBUTION OF THIS WORK IS SUBJECT TO THE LICENSE AGREEMENT
*  ACCOMPANYING THE SOFTWARE DEVELOPER KIT (SDK) THAT CONTAINS THIS WORK.
*  PURSUANT TO THE SDK LICENSE AGREEMENT, NOVELL HEREBY GRANTS TO DEVELOPER A
*  ROYALTY-FREE, NON-EXCLUSIVE LICENSE TO INCLUDE NOVELL'S SAMPLE CODE IN ITS
*  PRODUCT. NOVELL GRANTS DEVELOPER WORLDWIDE DISTRIBUTION RIGHTS TO MARKET,
*  DISTRIBUTE, OR SELL NOVELL'S SAMPLE CODE AS A COMPONENT OF DEVELOPER'S
*  PRODUCTS. NOVELL SHALL HAVE NO OBLIGATIONS TO DEVELOPER OR DEVELOPER'S
*  CUSTOMERS WITH RESPECT TO THIS CODE.
*
* $name:         CheckBind.java
* $description:  Sometimes a user can not bind to a directory although he
*                is using a correct password. This is because that there
*                are a number of bind restrictions to prevent the user from
*                binding to the directory.
*
*                CheckBind.java verifies user's pass word and checks user's
*                'LoginDisabled', 'loginExpirationTime', 'passwordExpira-
*                tionTime', 'loginAllowedTimeMap', and 'lockedByIntruder'
*                attributes to determine if the user can bind to the directory
*                at the current time.
*
*                CheckBind.java does not check 'networkAddressRestriction' and
*                'loginMaximumSimultaneous' attributes. The settings of
*                those attributes can still prevent the user from binding
*                to the directory.
*
*                In order to access all those attributes, the login dn should
*                have admin or admin equivalent rights.
******************************************************************************/
import java.util.Hashtable;
import java.util.Date;
import java.util.TimeZone;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.Attribute;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;

public class CheckBind
{
    public static final int LENGTH     = 42;
    public static final int BITS       = 336;
    public static final int BITSOFFSET = 14;

    public static void main( String[] args )
    {
    if (args.length != 5) {
        usage();
    }

    String hostURL  = args[0];
    String loginDN  = args[1];
    String password = args[2];
    String userDN   = args[3];
    String userPWD  = args[4];

    try {

        /* Setup environment properties */
        Hashtable env = new Hashtable(5, 0.75f);
        env.put(Context.INITIAL_CONTEXT_FACTORY,
                                      "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, hostURL);
        env.put(Context.SECURITY_PRINCIPAL, loginDN );
        env.put(Context.SECURITY_CREDENTIALS, password );
       // inform the LDAP provider that the value of 'loginAllowedTimeMap'

       // attribute is to be returned as byte array

        env.put("java.naming.ldap.attributes.binary","loginAllowedTimeMap");

       // create the initial directory context

        DirContext ctx = new InitialDirContext(env);

        System.out.println();
        System.out.println("    User DN: " + userDN );
        System.out.println("    Checking bind restrictions...\n");

        if ( GetBindInfo( ctx, userDN, userPWD ) )
        System.out.println("\n\n    User can login now.\n");
        else
        System.out.println("\n\n    User can not login now.\n");

       // close the context

        ctx.close();
    }
    catch (NamingException e) {
        System.err.println("CheckBind example failed.");
        e.printStackTrace();
    }
    finally {
        System.exit(0);
    }
    }

   // GetBindInfo() returns 'true' if user passed the test and

   // can login currently. Otherwise 'false' is returned and the

   // user can not login right now.

    public static boolean GetBindInfo( DirContext ctx,
               String userDN, String userPWD) {
    byte    byteValue[] = new byte [0];
    Date    date = new Date();
    boolean cr = true;
    String  attrName, attrValue, login = null, loginExpTime = null;
    String  pwdExpTime = null, locked = null;
    String  locale = date.toString();

    try {
       // check user's password

       System.out.print("\n        user's password: ");
       // do compare in JNDI:

       //     1. search scope: SearchControls.OBJECT_SCOPE

       //     2. return no attributes

       //     3. set filter to be name-value pair

        SearchControls ctls = new SearchControls();
        ctls.setSearchScope( SearchControls.OBJECT_SCOPE );
        ctls.setReturningAttributes( new String[0] );
        NamingEnumeration sre = ctx.search( userDN, "userPassword="
                                        + userPWD, ctls );

        if ( sre != null && sre.hasMoreElements())
        System.out.println(" OK");
        else
        System.out.println(" password is incorrect");

       // get userDN's bind-related attributes

       // return those attributes only

        String returnAttrs[] = {"LoginDisabled",
                   "loginExpirationTime",
                   "passwordExpirationTime",
                   "loginAllowedTimeMap",
                   "lockedByIntruder",
                   "title" };
        Attributes attrs = ctx.getAttributes( userDN, returnAttrs );
       // get Enumeration of the attributes

        NamingEnumeration enu = attrs.getAll();

       // save the attributes values

        while ( (enu != null) && enu.hasMore() ) {
        Attribute attr = (Attribute)enu.next();
        attrName = attr.getID();
        NamingEnumeration attrValues = attr.getAll();

        while ( attrValues.hasMore()) {
            if ( attrName.equalsIgnoreCase( "loginAllowedTimeMap" ))
             byteValue = (byte[])attrValues.next();

            else {
            attrValue = (String)attrValues.next();
            if (attrName.equalsIgnoreCase( "LoginDisabled" ))
                login = attrValue;
            else if (attrName.equalsIgnoreCase(
                             "loginExpirationTime"))
                loginExpTime = attrValue;
            else if (attrName.equalsIgnoreCase(
                              "passwordExpirationTime"))
                pwdExpTime = attrValue;
            else if (attrName.equalsIgnoreCase("lockedByIntruder"))
                locked = attrValue;
            }
        }
        }

       // check 'Logindisabled'

        System.out.print("\n        'Logindisabled': ");
        if ( (login != null)  && (login.length() != 0) ) {
        if ( login.equalsIgnoreCase( "FALSE" ) )
            System.out.println( " OK." );
        else {
            System.out.println( " account is disabled." );
            cr = false;
        }
        }
        else
        System.out.println(" not initialized.");

       // check 'LoninExpirationTime'

        System.out.print("\n        'loginExpirationTime': ");
        if ( (loginExpTime != null)  && (loginExpTime.length() != 0) ) {
        //theDate = timeStringToDate( loginExpTime );

         if ( CompareTime(loginExpTime) )
             System.out.println( " OK." );
         else {
             System.out.println(" Exceeded expiration time." );
             System.out.println("            LoninExpirationTime: "
                        + loginExpTime + "    (UTC)");
             PrintLocalTime(loginExpTime);
             System.out.println("            Current Locale Time: "
                                   + locale);
             cr = false;
         }
        }
        else
        System.out.println(" not initialized.");

       // check 'passwordExpirationTime'

        System.out.print("\n        'passwordExpirationTime': ");
        if ( (pwdExpTime != null)  && (pwdExpTime.length() != 0) ) {
        if ( CompareTime( pwdExpTime) )
            System.out.println( " OK." );
        else {
            System.out.println(" Exceeded expiration time.");
            System.out.println("            passwordExpirationTime: "
                           + pwdExpTime + "    (UTC)");
            PrintLocalTime( pwdExpTime );
            System.out.println("            Current   Locale  Time: "
                                   + locale);
            cr = false;
        }
        }
        else
        System.out.println(" not initialized.");

       // check 'LoginAllowedTimeMap'

        System.out.print("\n        'LoginAllowedTimeMap': ");
        if ( (byteValue.length != 0) && (byteValue.length == LENGTH)) {
        if ( getTimeRestriction( byteValue, locale) )
            System.out.println( " Current time is restricted." );
        else {
            System.out.println( " OK." );
            cr = false;
        }
        }
        else
        System.out.println(" not initialized.");

       // Check 'lockedByIntruder'

        System.out.print("\n        'lockedByIntruder': ");
        if ( (locked != null)  && (locked.length() != 0) ) {
        if ( locked.equalsIgnoreCase( "FALSE" ) )
            System.out.println( " OK." );
        else {
            System.out.println( " lockedout.");
            cr = false;
        }
        }
        else
        System.out.println(" not initialized.");
    }
    catch( NamingException e ) {
        System.out.println( "\n    Error: " + e.toString() );
        System.exit(1);
    }

    return cr;
    }

   // PrintLocalTime() truns UTCTime into local

   // time and then prints it out in text format

    public static void PrintLocalTime( String UTCTime )
    {
    Date date = null;
   // setup x.208 generalized time formatter

    DateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss'Z'");
   // all time in UTC - must set formatter

    TimeZone tz = TimeZone.getTimeZone("UTC");
    formatter.setTimeZone(tz);

    try {
       // parse into Date - converted to locale time zone

        date = formatter.parse( UTCTime );
    }
    catch(ParseException pe) {
        System.out.println( "\n    Error: " + pe.toString() );
    }

    System.out.print("                                    ");
    System.out.println(date);
    }

   // CompareTime() parses UTCTime into locale Date, and then

   // compare it with sytem Date. It returns true if UTCTime

   // is after system time. Otherwise false is returned.

    public static boolean CompareTime( String UTCTime )
    {
    Date date = null, currentDate = new Date();
   // setup x.208 generalized time formatter

    DateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss'Z'");
   //DateFormat fm = DateFormat.getDateInstance(Locale.GMT);

   // all time in UTC - must set formatter

    TimeZone tz = TimeZone.getTimeZone("UTC");
    formatter.setTimeZone(tz);

    try {
       // parse into Date - converted to locale time zone

        date = formatter.parse( UTCTime );
    }
    catch(ParseException pe) {
        System.out.println( "\n   Error: " + pe.toString() );
    }

    return date.after(currentDate);
    }

   // getTimeRestriction() returns 'true' if there is a timerestriction

   // for the current 30-minute period. Otherwise 'false' is returned

    public static boolean getTimeRestriction( byte[] byteValues,
                          String localITime )
    {
    int i, index = 0;

   // byteValues has 42 bytes ( e.g. 336 bites) to flag 336

   // thirty-minutes time intervals in a week. the bytes and

   // the bits are not in a ready-to-use order. some

   // transformations need to be done:

   //     1. reverse bits order in all the bytes of byteValues.

   //     2. check total 336 bits one by one in byteValues from

   //        left to right from index 0 to 41 and set flags'

   //        corresponding value to true if the bit value in

   //        byteValues is '1'

   //     3. shift the first 14 elements to the end of flags

   // Then flags contains 336 flags for each thirty-minute interval

   // in a week. The 336 intervals are in the order of

   // Sun 12:00AM-12:30AM,  Sun 12:30AM-1:00Am, ...,

   // Sat 11:00PM-11:30PM, Sat 11:30PM-12:00AM

    boolean flags[] = new boolean[BITS], temp1[] = new boolean [BITSOFFSET];
   // reverse the bits order and set flags

    for ( i = 0; i < LENGTH; i++ ) {
        if ( (byteValues[i] & 0x01) != 0 )
        flags[ i * 8 + 0 ]= true;
        if ( (byteValues[i] & 0x02) != 0 )
        flags[ i * 8 + 1 ]= true;
        if ( (byteValues[i] & 0x04) != 0 )
        flags[ i * 8 + 2 ]= true;
        if ( (byteValues[i] & 0x08) != 0 )
        flags[ i * 8 + 3 ]= true;
        if ( (byteValues[i] & 0x10) != 0 )
        flags[ i * 8 + 4 ]= true;
        if ( (byteValues[i] & 0x20) != 0 )
        flags[ i * 8 + 5 ]= true;
        if ( (byteValues[i] & 0x40) != 0 )
        flags[ i * 8 + 6 ]= true;
        if ( (byteValues[i] & 0x80) != 0 )
        flags[ i * 8 + 7 ]= true;
    }
   // shift the first 14 elements to the end of flags

    for ( i = 0; i < BITSOFFSET; i++ )
        temp1[i] = flags[i];
    for ( i = BITSOFFSET; i < BITS; i++ )
        flags[i-BITSOFFSET] = flags[i];
    for ( i = 0; i < BITSOFFSET; i++ )
        flags[BITS-BITSOFFSET+i] = temp1[i];

   // get day, hour, and minute from localITime which is

   // in the format of "Tue Jul 24 12:34:56 MDT 2001"

    String weekDay = localITime.substring( 0, 3 );
    String clock   = localITime.substring(
        localITime.indexOf((int)':') - 2,
        localITime.indexOf((int)':') + 3 );
    int hour   = Integer.parseInt( clock.substring( 0, 2 ));
    int minute = Integer.parseInt( clock.substring( 3 ));

   // calculate index

    int clockIndex = hour * 2 + minute / 30;
    if (      weekDay.equalsIgnoreCase( "Sun" ))
        index = 0 * 48 + clockIndex;
    else if ( weekDay.equalsIgnoreCase( "Mon" ))
        index = 1 * 48 + clockIndex;
    else if ( weekDay.equalsIgnoreCase( "Tue" ))
        index = 2 * 48 + clockIndex;
    else if ( weekDay.equalsIgnoreCase( "Wed" ))
        index = 3 * 48 + clockIndex;
    else if ( weekDay.equalsIgnoreCase( "Thu" ))
        index = 4 * 48 + clockIndex;
    else if ( weekDay.equalsIgnoreCase( "Fri" ))
        index = 5 * 48 + clockIndex;
    else if ( weekDay.equalsIgnoreCase( "Sat" ))
        index = 6 * 48 + clockIndex;

    return !flags[index];
    }

    public static void usage() {
    System.err.println("\n Usage:   java CheckBind <host URL> <login dn> "
        + "<password> <user dn>\n         <user password>\n\n Example: java "
        + "CheckBind ldap://Acme.com:389 \"cn=Admin,o=Acme\" secret\n"
        + "         \"cn=Jsmith,ou=Sales,o=Acme\" userPWD");
        System.exit(1);
    }
}