package org.samba.test.ldap;

import java.util.*;
import java.util.logging.*;

import javax.naming.*;
import javax.naming.directory.*;
import javax.naming.ldap.*;

public class FastBindTest
{
	public static final String DEFAULT_INITIAL_CONTEXT_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory";
	public static final String DEFAULT_SECURITY_AUTHENTICATION = "simple";	// ”none", "simple", "strong"; DIGEST-MD5, GSSAPI
	public static final String DEFAULT_REFERRAL = "ignore";	// "follow" "ignore" "throw"
	public static final String DEFAULT_LDAP_URL = "ldap://127.0.0.1:389";
	public static final String LDAP_SERVER_FAST_BIND_OID =  "1.2.840.113556.1.4.1781";	//http://msdn.microsoft.com/en-us/library/aa366981.aspx
	static Control[] fastBindConnectionControls =
		new Control[]
		{
			new FastBindConnectionControl (),
		};

	static class FastBindConnectionControl implements Control
	{
		public byte[] getEncodedValue ()
		{
			return null;
		}
	  	public String getID ()
	  	{
			return LDAP_SERVER_FAST_BIND_OID;
		}
	 	public boolean isCritical ()
	 	{
			return true;
		}
	}

	Hashtable env = new Hashtable ();
	LdapContext ctx = null;

	protected FastBindTest ()
	{
	}
	public FastBindTest (String sInitialContextFactory, String sSecurityAuthentication, String sReferral, String sLDAPServerURL, boolean bSSL, String sKeyStore, String sKeyStorePassword)
	{
		if (sInitialContextFactory!=null && !sInitialContextFactory.isEmpty ())
			env.put (Context.INITIAL_CONTEXT_FACTORY, sInitialContextFactory);
		if (sLDAPServerURL!=null && !sLDAPServerURL.isEmpty ())
			env.put (Context.PROVIDER_URL, sLDAPServerURL);
		if (sSecurityAuthentication!=null && !sSecurityAuthentication.isEmpty ())
			env.put (Context.SECURITY_AUTHENTICATION, sSecurityAuthentication);
		if (sReferral!=null && !sReferral.isEmpty ())
			env.put (Context.REFERRAL, sReferral);
		if (bSSL)
		{
			env.put (Context.SECURITY_PROTOCOL, "ssl");
			if (sKeyStore!=null && !sKeyStore.isEmpty ())
				System.setProperty ("javax.net.ssl.trustStore", sKeyStore);
			if (sKeyStorePassword!=null && !sKeyStorePassword.isEmpty ())
				System.setProperty ("javax.net.ssl.trustStorePassword", sKeyStorePassword);
		}
	}
	public void Close ()
	{
		try
		{
			if (ctx != null)
				ctx.close ();
		}
		catch (NamingException e)
		{
			e.printStackTrace ();
		}
	}
	
	public boolean Authenticate (String u, String p, Control[] controls) throws NamingException
	{
		boolean bPassed = false;
		try
		{
			if (ctx == null)
			{
				env.put (Context.SECURITY_PRINCIPAL, u);
				env.put (Context.SECURITY_CREDENTIALS, p);
				ctx = new InitialLdapContext (env, controls);	// if no exception throws, authentication is passed
			}
			else
			{
				ctx.addToEnvironment (Context.SECURITY_PRINCIPAL, u);
				ctx.addToEnvironment (Context.SECURITY_CREDENTIALS, p);
				ctx.reconnect (controls);
			}
			bPassed = true;
		}
		catch (NamingException e)
		{
			e.printStackTrace ();
		}
		return bPassed;
	}
	
	public boolean Authenticate (String u, String p) throws NamingException
	{
		return Authenticate (u, p, null);
	}

	static void PrintUsage ()
	{
		System.out.println ("java FastBindTest [options] -u account@DOMAIN.TLD -p password");
		System.out.println ("    -u account@DOMAIN.TLD");
		System.out.println ("        Specify LDAP username/account");
		System.out.println ("    -p password");
		System.out.println ("        Specify LDAP password");
		System.out.println ("    -url LDAPURL");
		System.out.println ("        Specify LDAP server URL, default is ldap://127.0.0.1:389");
		System.out.println ("    -n  Do Not use fast bind connection control / Normal");
		System.out.println ("    -ssl");
		System.out.println ("    -tls");
		System.out.println ("        use TLS/SSL connection");
		System.out.println ("    -ks KeyStoreFile>");
		System.out.println ("    -keystore KeyStoreFile");
		System.out.println ("        Specify the keystore file which contains trusted certification(s) for TLS/SSL connection");
		System.out.println ("    -ksp KeyStorePassword");
		System.out.println ("    -keystorepassword KeyStorePassword");
		System.out.println ("        Specify the password of the keystore, default is 'changeit' (java default)");
		System.out.println ("    -icf <FACTORY>");
		System.out.println ("        Specify initial context factory, default is com.sun.jndi.ldap.LdapCtxFactory");
		System.out.println ("    -sa <SECURITY>");
		System.out.println ("        Specify security authentication: none|simple|strong, default is simple");
		System.out.println ("    -referral <REFERRAL>");
		System.out.println ("        Specify referral: ignore|follow|throw, default is ignore");
		System.out.println ();
		System.out.println ("Sample");
		System.out.println ("  java org.samba.test.ldap.FastBindTest -url ldap://localhost:389 administrator@samba.org XXXXXXXX");
		System.out.println ("  java org.samba.test.ldap.FastBindTest -url ldap://localhost:389 administrator@samba.org XXXXXXXX -n");
		System.out.println ("  java org.samba.test.ldap.FastBindTest -url ldaps://localhost:636 administrator@samba.org XXXXXXXX -tls -ks samba-cert.jks -ksp changeit");
	}
	static boolean isArgumentValueMissing (String[]args, int index)
	{
		return index+1==args.length || args[index+1].startsWith ("-") || args[index+1].startsWith ("/");
	}
	public static void main (String[] args) throws NamingException
	{
		if (args.length < 4)
		{
			PrintUsage ();
			return;
		}

		String initialContextFactory = DEFAULT_INITIAL_CONTEXT_FACTORY;
		String securityAuthentication = DEFAULT_SECURITY_AUTHENTICATION;
		String referral = DEFAULT_REFERRAL;
		String url = "ldap://127.0.0.1:389";
		String account = null;
		String password = null;
		boolean usingTLS = false;
		String keyStore = null;
		String keyStorePassword = null;
		boolean notUsingFastBind = false;

		for (int i=0; i<args.length; i++)
		{
			String arg = args[i];
			if (arg.startsWith ("-") || arg.startsWith ("/"))
			{
				String o = arg.substring (1);
				if (o.equalsIgnoreCase ("n"))
					notUsingFastBind = true;
				else if (o.equalsIgnoreCase ("tls") || o.equalsIgnoreCase ("ssl"))
					usingTLS = true;
				else if (o.equalsIgnoreCase ("ks") || o.equalsIgnoreCase ("keystore"))
				{
					if (i+1==args.length)
					{
						System.err.println ("Error: Need specify key store file");
						return;
					}
					keyStore = args[++i]; 
				}
				else if (o.equalsIgnoreCase ("ksp") || o.equalsIgnoreCase ("keystorepassword"))
				{
					if (isArgumentValueMissing(args, i))
					{
						System.err.println ("Error: Need specify password of key store file");
						return;
					}
					keyStorePassword = args[++i]; 
				}
				else if (o.equalsIgnoreCase ("url"))
				{
					if (isArgumentValueMissing(args, i))
					{
						System.err.println ("Error: Need specify URL of LDAP server, such as ldap://192.168.1.2:389");
						return;
					}
					url = args[++i];
				}
				else if (o.equalsIgnoreCase ("u"))
				{
					if (isArgumentValueMissing(args, i))
					{
						System.err.println ("Error: Need specify account, such as 'Administrator@DOMAIN.TLD' or 'DOMAIN\\Administrator'");
						return;
					}
					account = args[++i];
				}
				else if (o.equalsIgnoreCase ("p"))
				{
					if (isArgumentValueMissing(args, i))
					{
						System.err.println ("Error: Need specify password of account");
						return;
					}
					password = args[++i];
				}
				else if (o.equalsIgnoreCase ("icf") || o.equalsIgnoreCase ("InitialContextFactory"))
				{
					if (isArgumentValueMissing(args, i))
					{
						System.err.println ("Error: Need specify InitialContextFactory");
						return;
					}
					securityAuthentication = args[++i];
				}
				else if (o.equalsIgnoreCase ("sa") || o.equalsIgnoreCase ("SecurityAuthentication"))
				{
					if (isArgumentValueMissing(args, i))
					{
						System.err.println ("Error: Need specify security authentication");
						return;
					}
					securityAuthentication = args[++i];
				}
				else if (o.equalsIgnoreCase ("referral"))
				{
					if (isArgumentValueMissing(args, i))
					{
						System.err.println ("Error: Need specify referral");
						return;
					}
					referral = args[++i];
				}
				else
				{
					System.err.println ("Warning: Unknown option: " + arg);
				}
			}
			else
			{
			}
		}

		if (account==null || password==null)
		{
			System.err.println ("Error: At least, you need specify the account and password");
			return;
		}
		if (usingTLS && keyStore==null)
		{
			System.err.println ("Error: A certification key store file is needed when using TLS/SSL");
			return;
		}

		FastBindTest ldap = new FastBindTest (initialContextFactory, securityAuthentication, referral, url, usingTLS, keyStore, keyStorePassword);
		boolean authenticated = false;
		if (notUsingFastBind)
			authenticated = ldap.Authenticate (account, password);
		else
			authenticated = ldap.Authenticate (account, password, fastBindConnectionControls);

		if (authenticated)
			System.out.println ("Authentication OK");
		else
		{
			System.out.println ("Authentication failed");
			ldap.Close ();
			return;
		}

		ldap.Close ();
	}
}
