package com.wedgedpig.catalina.realm;

import java.io.*;
import java.security.*;
import java.util.*;

import javax.security.auth.callback.*;
import javax.security.auth.login.*;

import com.tagish.auth.win32.*;
import org.apache.catalina.realm.*;

/**
 * Realm implementation to authenticate users against an NT domain, and populate
 * authenticated Principals with Tomcat roles, based on their NT user group membership
 * @author Rory Douglas
 * @version 1.0
 */
public class NTRealm extends RealmBase {
  /**
   * Descriptive information about this Realm implementation.
   */
  protected static final String name = "NTRealm";

  /**
   * Callback handler for NT authentication
   */
  protected CallbackHandler ntcb = new NTRealmCallbackHandler();

  /**
   * The NT username
   */
  private String username = null;

  /**
   * The NT password
   */
  private char[] password = null;

  /**
   * The NT domain
   */
  private String domain = null;

  /**
   * Authenticates an NT user against an NT domain. A username or a DOMAIN\\username string
   * must be supplied. If no DOMAIN is supplied, you must set the default domain
   * using the domain attribute of the Realm element in server.xml. If no default domain
   * is specified and the given login name does not contain a domain, authentication will
   * fail. A succesfully authenticated user is assigned roles based on the NT user groups
   * to which they belong.
   * @param useranddomain Either "username" to authenticate against default domain, or
   * "DOMAIN\\username"
   * @param credentials the NT password
   * @return a <code>TomcatNTPrincipal</code> if authentication is successful, or <code>null</code> otherwise
   */
  public Principal authenticate(String useranddomain, String credentials) {
    if (useranddomain == null)
      return null;

    try {
      LoginContext lc = new LoginContext("com.wedgedpig.catalina.realm.NTRealm",ntcb);

      int dindex = useranddomain.indexOf("\\");

      if(dindex < 0) {      // no DOMAIN supplied
        if(domain == null)  // domain attribute not specified in server.xml
          return null;
        username = useranddomain;
      } else {
        domain = useranddomain.substring(0,dindex);
        int uindex = domain.length()+1;

        if(useranddomain.length()<= uindex) // only DOMAIN\ specified, weirdness...
          return null;

        username = useranddomain.substring(uindex);
      }

      password = credentials.toCharArray();
      credentials = null;

      lc.login();

      Set p = lc.getSubject().getPrincipals();
      Iterator it = p.iterator();
      Principal userPrincipal = null;
      NTPrincipal tempP = null;
      NTPrincipal authUser = null;
      List l = new ArrayList();

      while(it.hasNext()) {
        userPrincipal = (Principal)it.next();
        if(userPrincipal instanceof NTPrincipal) {
          tempP = (NTPrincipal)userPrincipal;
          if(tempP.getType() == NTPrincipal.GROUP)
            l.add(tempP.getName());
          else if(tempP.getType() == NTPrincipal.USER)
            authUser = tempP;
        }
      }

      if (authUser != null)
        return new TomcatNTPrincipal(authUser,this,l);

    } catch (LoginException le) {
      log("NTRealm Error",le);
    }
    return null;
  }

  /**
   * Determines whether a specific Principal can act in a given role
   * @param principal The Principal to check, must be instance of <code>TomcatNTPrincipal</code>
   * @param role The role to check
   * @return <code>true</code> if the Principal is not <code>null</code>, is an instance
   * of TomcatNTPrincpal, was authenticated against this Realm instance AND can has the
   * given role. <code>false</code> otherwise.
   */
  public boolean hasRole(Principal principal, String role) {
    if ((principal == null) || (role == null) ||
        !(principal instanceof TomcatNTPrincipal))
        return (false);

    TomcatNTPrincipal ntp = (TomcatNTPrincipal) principal;
    if (!(ntp.getRealm() == this))
        return (false);
    boolean result = ntp.hasRole(role);
    if (debug >= 2) {
        String name = principal.getName();
        if (result)
            log(sm.getString("realmBase.hasRoleSuccess", name, role));
        else
            log(sm.getString("realmBase.hasRoleFailure", name, role));
    }
    return (result);
  }

  /**
   * Don't think you want this feature really
   * @param username The name of a user
   * @return <code>null</code>
   */
  protected String getPassword(String username) {
    return null;
  }

  /**
   * Should return Principal associated with given username, but doesn't
   * @param username The name of a user
   * @return <code>null</code>
   */
  protected Principal getPrincipal(String username) {
    return null;
  }

  /**
   * A short descriptive name of this Realm
   * @return
   */
  protected String getName() {
    return this.name;
  }

  /**
   * Set the default NT Domain to authenticate against. Specfiy domain attribute of
   * &lt;Realm&gt; element to set default domain.
   * @param domain
   */
  public void setDomain(String domain) {
    this.domain = domain;
  }

  /**
   * Dependent on the behaviour of the Tagish LoginModule
   */
  private class NTRealmCallbackHandler implements CallbackHandler {
    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
      for (int i = 0; i < callbacks.length; i++) {
        if (callbacks[i] instanceof TextOutputCallback) {
          TextOutputCallback toc = (TextOutputCallback)callbacks[i];
          System.out.println(toc.getMessage());
        } else if (callbacks[i] instanceof NameCallback) {
          NameCallback ncb = (NameCallback)callbacks[i];
          ncb.setName(username);
        } else if (callbacks[i] instanceof PasswordCallback) {
          PasswordCallback pcb = (PasswordCallback)callbacks[i];
          pcb.setPassword(password);
          Arrays.fill(password,'*');
        } else if (callbacks[i] instanceof TextInputCallback) {
          TextInputCallback ticb = (TextInputCallback)callbacks[i];
          ticb.setText(domain);
        } else {
            throw new UnsupportedCallbackException
             (callbacks[i], "Unrecognized Callback");
        }
      }
    }
  }
}