import java.io.IOException;
import java.util.Hashtable;

import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.CommunicationException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.Attributes;
import javax.naming.directory.Attribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.SearchResult;
import javax.naming.directory.SearchControls;
import javax.naming.ldap.StartTlsRequest;
import javax.naming.ldap.StartTlsResponse;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.InitialLdapContext;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLHandshakeException;


public class Ox {
	enum Security { PLAIN, TLS, SSL, SSL2 };
	enum Auth { ANONYMOUS, SIMPLE, SASL, KERBEROS, EXTERNAL };
	enum Search { DIRECT, BASIC, SEARCH };

	public static final String HOST = "10.200.17.61";
	public static final String LDAP_BASE = "dc=phahn,dc=dev";
	public static final String LDAP_USER = "cn=users";
	public static final String USER_RDN = "uid=Administrator," + LDAP_USER;
	public static final String BIND_DN = USER_RDN + "," + LDAP_BASE;
	public static final String BIND_PWD = "univention";

	public static Security security = Security.SSL;
	public static Auth auth = Auth.SIMPLE;
	public static Search search = Search.SEARCH;

	protected Hashtable<String, Object> env = new Hashtable<String, Object>();
	protected DirContext ctx;

	public static void main(String[] args) {
		parseArgs(args);
		try {
			Ox ox = new Ox();
			switch (search) {
				case DIRECT: ox.direct(); break;
				case BASIC: ox.basic(); break;
				case SEARCH: ox.search(); break;
			}
		} catch (Exception ex) {
			System.err.println(ex);
			System.exit(1);
		}
	}

	public static void parseArgs(String[] args) {
		for (String arg: args) {
			if ("plain".equals(arg)) security = Security.PLAIN;
			else if ("tls".equals(arg)) security = Security.TLS;
			else if ("ssl".equals(arg)) security = Security.SSL;
			else if ("ssl2".equals(arg)) security = Security.SSL2;
			else if ("anonymous".equals(arg)) auth = Auth.ANONYMOUS;
			else if ("simple".equals(arg)) auth = Auth.SIMPLE;
			else if ("sasl".equals(arg)) auth = Auth.SASL;
			else if ("kerberos".equals(arg)) auth = Auth.KERBEROS;
			else if ("external".equals(arg)) auth = Auth.EXTERNAL;
			else if ("direct".equals(arg)) search = Search.DIRECT;
			else if ("basic".equals(arg)) search = Search.BASIC;
			else if ("search".equals(arg)) search = Search.SEARCH;
			else System.err.println("Unknown argument: " + arg);
		}
	}

	public Ox() throws NamingException, IOException {
		env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
		switch (security) {
			case PLAIN:
			case TLS:
				env.put(Context.PROVIDER_URL, "ldap://" + HOST + ":7389/" + LDAP_BASE);
				break;
			case SSL:
				env.put(Context.PROVIDER_URL, "ldap://" + HOST + ":7636/" + LDAP_BASE);
				env.put(Context.SECURITY_PROTOCOL, "ssl");
				break;
			case SSL2:
				env.put(Context.PROVIDER_URL, "ldaps://" + HOST + ":7636/" + LDAP_BASE);
				break;
		}
		/*
		 * /usr/lib/jvm/java-1.6.0-openjdk/jre/lib/security/java.security
		 * /usr/lib/jvm/java-1.6.0-openjdk/jre/lib/security/jssecacerts
		 * /usr/lib/jvm/java-1.6.0-openjdk/jre/lib/security/cacerts -> /etc/ssl/certs/java/cacerts
		 * keytool -list -keystore /etc/ssl/certs/java/cacerts -storepass changeit
		 * keytool -importcert -noprompt \
		 *  -alias "$(openssl x509 -noout -in /etc/univention/ssl/ucsCA/CAcert.pem -subject -nameopt multiline,-esc_msb|sed -ne 's,^\s*commonName\s*=\s*,,;T;s,\s\+$,,g;s,\s\+,_,g;p')" \
		 *  -file /etc/univention/ssl/ucsCA/CAcert.pem \
		 *  -keystore /etc/ssl/certs/java/cacerts -storepass changeit
		*/

		switch (security) {
			case TLS:
				LdapContext ctx = new InitialLdapContext(env, null);
				StartTlsResponse tls = (StartTlsResponse)ctx.extendedOperation(new StartTlsRequest());
				tls.setHostnameVerifier(new Verifier());
				SSLSession sess = tls.negotiate();
				this.ctx = ctx;
		}

		switch (auth) {
			case ANONYMOUS:
				put(Context.SECURITY_AUTHENTICATION, "none");
				break;
			case SIMPLE:
				put(Context.SECURITY_AUTHENTICATION, "simple");
				put(Context.SECURITY_PRINCIPAL, BIND_DN);
				put(Context.SECURITY_CREDENTIALS, BIND_PWD);
				break;
			case SASL:
				put(Context.SECURITY_AUTHENTICATION, "DIGEST-MD5");
				put(Context.SECURITY_PRINCIPAL, "dn:" + BIND_DN);
				put(Context.SECURITY_CREDENTIALS, BIND_PWD);
				//env.put("javax.security.sasl.qop", "auth-int");
				break;
			case KERBEROS:
				this.kerberos();
				put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
				break;
			case EXTERNAL:
				put(Context.SECURITY_AUTHENTICATION, "EXTERNAL");
				/* java -Djavax.net.ssl.keyStore=MyKeystoreFile -Djavax.net.ssl.keyStorePassword=mysecret Ox */
				break;
		}

		switch (security) {
			case TLS: break;
			default:
				try {
					this.ctx = new InitialDirContext(env);
				} catch (CommunicationException ce) {
					ce.printStackTrace(System.err);
					throw ce;
				}
				break;
		}
	}

	protected void put(String key, Object value) throws NamingException {
		if (this.ctx != null)
			this.ctx.addToEnvironment(key, value);
		else
			this.env.put(key, value);
	}

	protected void kerberos() throws NamingException {
		/* ox_jaas.conf:
Ox {
  com.sun.security.auth.module.Krb5LoginModule required client=TRUE;
};
		*/

		/* krb5.policy:
permission java.net.SocketPermission "billy.knut.univention.de", "connect"; // ldap
permission java.net.SocketPermission "billy.knut.univention.de", "connect"; // kerberos

permission javax.security.auth.AuthPermission "createLoginContext.Ox";
permission javax.security.auth.AuthPermission "doAs";

permission javax.security.auth.kerberos.ServicePermission "krbtgt/KNUT.UNIVENTION.DE@KNUT.UNIVENTION.DE", "initiate";
permission javax.security.auth.kerberos.ServicePermission "ldap/billy.knut.univention.de@KNUT.UNIVENTION.DE", "initiate";
		*/

		/*
java \
	-Djava.security.auth.login.config=ox_jaas.conf \
	-Djava.security.krb5.conf=krb5.conf \
	-Djava.security.manager \
	-Djava.security.policy=krb5.policy
	Ox
		*/

		/*
		LoginContext lc = null;
		try {
			lc = new LoginContext(Ox.class.getName(), new SampleCallbackHandler());
			lc.login()
		} catch (LoginException le) {
			System.err.println(le);
			System.exit(1);
		}

		Subject.doAs(lc.getSubject(), new JndiAction(args));

		*/
	}

	protected void direct() throws NamingException {
		Attributes attrs = ctx.getAttributes(USER_RDN);
		printAttrs(attrs);
	}

	protected void basic() throws NamingException {
		Attributes matchAttrs = new BasicAttributes(true);
		matchAttrs.put(new BasicAttribute("uid", "Administrator"));
		NamingEnumeration answer = ctx.search(LDAP_USER, matchAttrs);
		printAnswer(answer);
	}

	protected void search() throws NamingException {
		String filter = "(uid=Administrator)";

		String[] attrIDs = {"uid"};
		SearchControls ctls = new SearchControls();
		ctls.setReturningAttributes(attrIDs);
		//ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
		//ctls.setCountLimit(1);
		//ctls.setTimeLimit(1000);

		NamingEnumeration answer = ctx.search(LDAP_USER, filter, ctls);
		printAnswer(answer);
	}

	protected void printAnswer(NamingEnumeration answer) throws NamingException {
		while (answer.hasMore()) {
			SearchResult sr = (SearchResult)answer.next();
			System.out.println(sr.getName());
			Attributes attrs = sr.getAttributes();
			printAttrs(attrs);
		}
	}

	protected void printAttrs(Attributes attrs) throws NamingException {
		for (NamingEnumeration ae = attrs.getAll(); ae.hasMore();) {
			Attribute attr = (Attribute)ae.next();
			System.out.println("  attribute: " + attr.getID());
			/* Print each value */
			for (NamingEnumeration e = attr.getAll(); e.hasMore();
					System.out.println("    value: " + e.next()));
		}
	}

	protected class Verifier implements HostnameVerifier {
		public boolean verify(String hostname, SSLSession session) {
			return true;
		}
	}
}
