gt: 			if (authPlugin == null) {
context: /*
   * ChoobSecurityManager.java
   *
   * Created on June 25, 2005, 3:28 PM
   */
  
  package uk.co.uwcs.choob.modules;
  
  import java.lang.reflect.Constructor;
  import java.lang.reflect.InvocationTargetException;
  import java.security.AccessControlContext;
  import java.security.AccessController;
  import java.security.AllPermission;
  import java.security.Permission;
  import java.security.PermissionCollection;
  import java.security.Permissions;
  import java.security.PrivilegedAction;
  import java.security.ProtectionDomain;
  import java.sql.Connection;
  import java.sql.PreparedStatement;
  import java.sql.ResultSet;
  import java.sql.SQLException;
  import java.sql.Statement;
  import java.util.ArrayList;
  import java.util.Enumeration;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Map;
  import java.util.PropertyPermission;
  
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import uk.co.uwcs.choob.support.ChoobAuthError;
  import uk.co.uwcs.choob.support.ChoobError;
  import uk.co.uwcs.choob.support.ChoobEventExpired;
  import uk.co.uwcs.choob.support.ChoobException;
  import uk.co.uwcs.choob.support.ChoobFakeProtectionDomain;
  import uk.co.uwcs.choob.support.ChoobGeneralAuthError;
  import uk.co.uwcs.choob.support.ChoobNoSuchCallException;
  import uk.co.uwcs.choob.support.ChoobNoSuchPluginException;
  import uk.co.uwcs.choob.support.ChoobPermission;
  import uk.co.uwcs.choob.support.ChoobPluginAuthError;
  import uk.co.uwcs.choob.support.ChoobProtectionDomain;
  import uk.co.uwcs.choob.support.ChoobSpecialStackPermission;
  import uk.co.uwcs.choob.support.ChoobUserAuthError;
  import uk.co.uwcs.choob.support.DbConnectionBroker;
  import uk.co.uwcs.choob.support.UserNode;
  import uk.co.uwcs.choob.support.events.IRCEvent;
  import uk.co.uwcs.choob.support.events.MessageEvent;
  import uk.co.uwcs.choob.support.events.UserEvent;
  
  /**
   * Security manager for plugins, access control to anything requiring/checking
   * for a permission.
   *
   * Also manages user, groups etc.
   *
   * @author	bucko
   */
  public final class SecurityModule extends SecurityManager // For getClassContext(). Heh.
  {
  	private static final Logger logger = LoggerFactory.getLogger(SecurityModule.class);
  
  	private final DbConnectionBroker dbBroker;
  	private final Map<Integer,PermissionCollection> nodeMap;
  	private final Map<Integer,List<Integer>> nodeTree;
  	private final ArrayList<Map<String, Integer>> nodeIDCache;
  	private final Modules mods;
  	private final int anonID;
  
  	/**
  	 * Creates a new instance of SecurityModule
  	 * @param dbBroker Database connection pool/broker.
  	 */
  	SecurityModule(final DbConnectionBroker dbBroker, final Modules mods)
  	{
  		this.dbBroker = dbBroker;
  		this.mods = mods;
  
  		this.nodeDbLock = new Object();
  
  		this.nodeMap = new HashMap<Integer,PermissionCollection>();
  		this.nodeTree = new HashMap<Integer,List<Integer>>();
  
  		this.nodeIDCache = new ArrayList<Map<String, Integer>>();
  		for (int i = 0; i < 4; i++) {
  			nodeIDCache.add(new HashMap<String, Integer>());
  		}
  
  		this.anonID = getNodeIDFromNodeName("anonymous", 3);
  	}
  
  	/* =====================
  	 * SecurityManager stuff
  	 * =====================
  	 */
  
  	/**
  	 * Returns an AccessControlContext which implies all permissions, but
  	 * retains the plugin stack so that getPluginNames() will work.
  	 */
  	public AccessControlContext getPluginContext( )
  	{
  		return new AccessControlContext(new ProtectionDomain[] {
  			getContextProtectionDomain()
  		});
  	}
  
  	public ProtectionDomain getContextProtectionDomain()
  	{
  		return new ChoobFakeProtectionDomain(getPluginNames());
  	}
  
  	public ProtectionDomain getProtectionDomain(final String pluginName)
  	{
  		return new ChoobProtectionDomain(this, pluginName);
  	}
  
  	public List<String> getPluginNames()
  	{
  		return getPluginNames(null);
  	}
  
  	public List<String> getPluginNames(final String debugKey)
  	{
  		final List<String> pluginStack = new ArrayList<String>();
  		// XXX HAX XXX HAX XXX HAX XXX HAX XXX
  		// ^^ If this doesn't persuade you that this is a hack, nothing will...
  		final ChoobSpecialStackPermission perm = new ChoobSpecialStackPermission(pluginStack);
  		AccessController.checkPermission(perm);
  		perm.patch();
  		return pluginStack;
  	}
  
  	/**
  	 * Gets the name of the nth plugin in the stack (0th = plugin #1).
  	 * @param skip
  	 * @return null if there is no plugin that far back. Otherwise the plugin
  	 *         name.
  	 */
  	public String getPluginName(final int skip) {
  		final List<String> names = getPluginNames();
  		if (skip >= names.size()) {
  			return null;
  		}
  		return names.get(skip);
  	}
  
  	/**
  	 * Force plugin permissions to be reloaded at some later point.
  	 */
  	private void invalidateNodePermissions(final int nodeID)
  	{
  		synchronized(nodeMap) {
  			nodeMap.remove(nodeID);
  		}
  	}
  
  	/**
  	 * Force plugin tree to be reloaded at some later point.
  	 */
  	private void invalidateNodeTree(final int nodeID)
  	{
  		synchronized(nodeMap) {
  			nodeTree.remove(nodeID);
  		}
  	}
  
  	private PermissionCollection getNodePermissions(final int nodeID)
  	{
  		PermissionCollection perms;
  		synchronized(nodeMap)
  		{
  			perms = nodeMap.get(nodeID);
  			if (perms == null)
  			{
  				updateNodePermissions(nodeID);
  				perms = nodeMap.get(nodeID);
  			}
  		}
  		return perms;
  	}
  
  	/**
  	 * Update permissions set for the given node ID. Code viciously hacked out
  	 * of ChoobSecurityManager.
  	 */
  	private void updateNodePermissions(final int nodeID) {
  		Connection dbConnection = null;
  
  		final Permissions permissions = new Permissions();
  
  		PreparedStatement permissionsSmt = null;
  		try {
  			dbConnection=dbBroker.getConnection();
  			permissionsSmt = dbConnection.prepareStatement("SELECT Type, Permission, Action FROM UserNodePermissions WHERE UserNodePermissions.NodeID = ?");
  
  			permissionsSmt.setInt(1, nodeID);
  
  			final ResultSet permissionsResults = permissionsSmt.executeQuery();
  
  			if ( permissionsResults.first() ) {
  				do
  				{
  					final String className = permissionsResults.getString(1);
  					final String permissionName = permissionsResults.getString(2);
  					final String actions = permissionsResults.getString(3);
  
  					Class <?> clas;
  					try
  					{
  						clas = Class.forName( className );
  					}
  					catch (final ClassNotFoundException e)
  					{
  						logger.warn("Permission class not found: " + className);
  						continue; // XXX I guess this is OK?
  					}
  
  					// Perhaps more strict checking here?
  					// TODO - is this check enough to be secure?
  					if (!Permission.class.isAssignableFrom(clas))
  					{
  						logger.warn("Class " + className + " is not a Permission!");
  						continue; // XXX
  					}
  
  					Constructor<?> con;
  					try
  					{
  						con = clas.getDeclaredConstructor(String.class, String.class);
  					}
  					catch (final NoSuchMethodException e)
  					{
  						System.err.println("Permission class had no valid constructor: " + className);
  						continue; // XXX I guess this is OK?
  					}
  
  					Permission perm;
  					try
  					{
  						perm = (Permission)con.newInstance(permissionName, actions);
  					}
  					catch (final IllegalAccessException e)
  					{
  						System.err.println("Permission class constructor for " + className + " failed: " + e.getMessage());
  						e.printStackTrace();
  						continue; // XXX
  					}
  					catch (final InstantiationException e)
  					{
  						System.err.println("Permission class constructor for " + className + " failed: " + e.getMessage());
  						e.printStackTrace();
  						continue; // XXX
  					}
  					catch (final InvocationTargetException e)
  					{
  						System.err.println("Permission class constructor for " + className + " failed: " + e.getMessage());
  						e.printStackTrace();
  						continue; // XXX
  					}
  
  					permissions.add(perm);
  				} while ( permissionsResults.next() );
  			}
  		}
  		catch ( final SQLException e )
  		{
  			System.err.println("Could not load DB permissions for user node " + nodeID + " (probably now incomplete permissions): " + e.getMessage());
  			e.printStackTrace();
  		}
  		finally
  		{
  			try
  			{
  				dbCleanupSel(permissionsSmt, dbConnection);
  			}
  			catch (final ChoobError e) {}
  		}
  
  		synchronized(nodeMap) {
  			nodeMap.put(nodeID, permissions);
  		}
  	}
  
  	/**
  	 * Check if the numbered user node has a permission
  	 * @param permission
  	 * @param userNode
  	 */
  	private boolean hasPerm(final Permission permission, final int userNode)
  	{
  		return hasPerm(permission, userNode, false);
  	}
  
  	private boolean hasPerm(final Permission permission, final int userNode, final boolean includeThis)
  	{
  		final Iterator<Integer> allNodes = getAllNodes(userNode, includeThis);
  
  		if ( ! allNodes.hasNext() )
  		{
  			System.out.println("User node " + userNode + " has no subnodes!");
  			return false;
  		}
  
  		int nodeID;
  		while( allNodes.hasNext() )
  		{
  			nodeID = allNodes.next();
  			final PermissionCollection perms = getNodePermissions( nodeID );
  			// Be careful to avoid invalid groups and stuff.
  			if (perms != null && perms.implies(permission))
  				return true;
  		}
  
  		return false;
  	}
  
  	/**
  	 * Get all nodes linked to the passed node.
  	 */
  	private Iterator<Integer> getAllNodes(final int nodeID, final boolean addThis)
  	{
  		synchronized(nodeTree)
  		{
  			Connection dbCon;
  			try
  			{
  				dbCon=dbBroker.getConnection();
  			}
  			catch (final SQLException e)
  			{
  				e.printStackTrace();
  				System.err.println("Couldn't get a connection for getAllNodes()");
  				return new ArrayList<Integer>().iterator(); // XXX
  			}
  			final List <Integer>list = new ArrayList<Integer>();
  			if (addThis)
  				list.add(nodeID);
  			try
  			{
  				getAllNodesRecursive(dbCon, list, nodeID, 0);
  				if (anonID != -1)
  				{
  					list.add(anonID);
  					getAllNodesRecursive(dbCon, list, anonID, 0);
  				}
  			}
  			finally
  			{
  				dbBroker.freeConnection(dbCon);
  			}
  			return list.iterator();
  		}
  	}
  
  	private void getAllNodesRecursive(final Connection dbConn, final List<Integer> list, final int nodeID, final int recurseDepth)
  	{
  		if (recurseDepth >= 5)
  		{
  			logger.warn("Ack! Recursion depth exceeded when trying to process user node " + nodeID);
  			return;
  		}
  
  		List<Integer> things = nodeTree.get(nodeID);
  		if (things == null)
  		{
  			things = new ArrayList<Integer>();
  			nodeTree.put(nodeID, things);
  			PreparedStatement stat = null;
  			try
  			{
  				stat = dbConn.prepareStatement("SELECT GroupID FROM GroupMembers WHERE MemberID = ?");
  				stat.setInt(1, nodeID);
  
  				final ResultSet results = stat.executeQuery();
  
  				if ( results.first() )
  				{
  					do
  					{
  						final int newNode = results.getInt(1);
  						if ( !list.contains( newNode ) )
  						{
  							list.add( newNode );
  						}
  						things.add( newNode );
  					} while ( results.next() );
  				}
  			}
  			catch (final SQLException e)
  			{
  				sqlErr("getting user nodes", e);
  			}
  			finally
  			{
  				try
  				{
  					if (stat != null)
  						stat.close();
  				}
  				catch (final SQLException e)
  				{
  					sqlErr("cleaning up user node statment", e);
  				}
  			}
  		}
  
  		for(final int newNode: things)
  		{
  			if (!list.contains(newNode))
  				list.add(newNode);
  			getAllNodesRecursive(dbConn, list, newNode, recurseDepth + 1);
  		}
  	}
  
  	/**
  	 * Get the node ID that corresponds to a node
  	 */
  	private int getNodeIDFromNode(final UserNode node)
  	{
  		return getNodeIDFromNodeName(node.getName(), node.getType());
  	}
  
  	/**
  	 * Get the node ID that corresponds to a node name
  	 */
  	private int getNodeIDFromUserName(final UserEvent userEvent)
  	{
  		if (userEvent instanceof IRCEvent)
  			checkEvent((IRCEvent)userEvent);
  		return getNodeIDFromNodeName(userEvent.getNick(), 0);
  	}
  
  	/**
  	 * Get the node ID that corresponds to a plugin
  	 */
  	private int getNodeIDFromPluginName(final String pluginName)
  	{
  		return getNodeIDFromNodeName(pluginName, 2);
  	}
  
  	/**
  	 * Get the node ID that corresponds to a node name
  	 */
  	private int getNodeIDFromNodeName(final String nodeName, final int nodeType)
  	{
  		// Check the cache.
  		final Integer id = nodeIDCache.get(nodeType).get(nodeName.toLowerCase());
  		if (id != null) {
  			return id.intValue();
  		}
  
  		Connection dbConn = null;
  		PreparedStatement stat = null;
  		try
  		{
  			dbConn = dbBroker.getConnection();
  			stat = dbConn.prepareStatement("SELECT NodeID FROM UserNodes WHERE NodeName = ? AND NodeClass = ?");
  			stat.setString(1, nodeName);
  			stat.setInt(2, nodeType);
  			final ResultSet results = stat.executeQuery();
  			if ( results.next() )
  			{
  				final int idGot = results.getInt(1);
  				nodeIDCache.get(nodeType).put(nodeName.toLowerCase(), idGot);
  				return idGot;
  			}
  			logger.warn("Ack! Node name " + nodeName + "(" + nodeType + ") not found!");
  		}
  		catch (final SQLException e)
  		{
  			sqlErr("getting a node ID", e);
  		}
  		finally
  		{
  			dbCleanupSel(stat, dbConn);
  		}
  		return -1;
  	}
  
  	/**
  	 * Get the node ID that corresponds to a node name
  	 */
  	private UserNode getNodeFromNodeID(final int nodeID)
  	{
  		Connection dbConn = null;
  		PreparedStatement stat = null;
  		try
  		{
  			dbConn = dbBroker.getConnection();
  			stat = dbConn.prepareStatement("SELECT NodeName, NodeClass FROM UserNodes WHERE NodeID = ?");
  			stat.setInt(1, nodeID);
  			final ResultSet results = stat.executeQuery();
  			if ( results.first() )
  			{
  				return new UserNode(results.getString(1), results.getInt(2));
  			}
  			logger.warn("Ack! Node " + nodeID + " not found!");
  		}
  		catch (final SQLException e)
  		{
  			logger.warn("Ack! SQL exception when getting node from node ID " + nodeID + ": " + e);
  		}
  		finally
  		{
  			dbCleanupSel(stat, dbConn);
  		}
  		return null;
  	}
  
  	/**
  	 * Get the last insert ID
  	 */
  	private int getLastInsertID(final Connection dbConn) throws SQLException
  	{
  		PreparedStatement stat = null;
  		try
  		{
  			stat = dbConn.prepareStatement("SELECT LAST_INSERT_ID()");
  			final ResultSet results = stat.executeQuery();
  			if ( results.first() )
  				return results.getInt(1);
  			throw new SQLException("Ack! LAST_INSERT_ID() returned no results!");
  		}
  		finally
  		{
  			if (stat != null)
  				stat.close();
  		}
  	}
  
  
  	/* ================================
  	 * PLUGIN PERMISSION CHECK ROUTINES
  	 * ================================
  	 */
  
  	public String renderPermission(final Permission permission)
  	{
  		if (permission instanceof AllPermission)
  			return "ALL";
  
  		String output;
  		final String className = permission.getClass().getSimpleName();
  		if (className.endsWith("Permission"))
  			output = className.substring(0, className.length() - 10);
  		else
  			output = className;
  
  		final String name = permission.getName();
  		final String actions = permission.getActions();
  		if (name != null)
  		{
  			output += " with name \"" + name + "\"";
  			if (actions != null)
  				output += " and actions \"" + actions + "\"";
  		}
  		else if (actions != null)
  			output += " and actions \"" + actions + "\"";
  
  		return output;
  	}
  
  	/**
  	 * Check if the given nickname has some form of authentication token
  	 * @param userEvent The event to validate and check the permission on.
  	 * @throws ChoobAuthError If the nick has no authentication
  	 */
  	public void checkAuth(final UserEvent userEvent) throws ChoobAuthError {
  		if (!hasAuth(userEvent.getNick())) {
  			throw new ChoobGeneralAuthError();
  		}
  	}
  
  	/**
  	 * Check if the given nickname has some form of authentication token.
  	 * @param nick The nickname to validate.
  	 * @throws ChoobAuthError If the nick has no authentication.
  	 */
  	public void checkAuth(final String nick) throws ChoobAuthError {
  		if (!hasAuth(nick)) {
  			throw new ChoobGeneralAuthError();
  		}
  	}
  
  	/**
  	 * Check if the given nickname has some form of authentication.
  	 * @param userEvent The event to validate and check the permission on
  	 * @return Whether the nick is authorised.
  	 */
  	public boolean hasAuth(final UserEvent userEvent) {
  		if (userEvent instanceof IRCEvent) {
  			checkEvent((IRCEvent)userEvent);
  		}
  		return hasAuth(userEvent.getNick());
  	}
  
  	/**
  	 * Check if the given nickname has some form of authentication.
  	 * @param nick The nickname to validate.
  	 * @return Whether the nick is authorised.
  	 */
  	public boolean hasAuth(final String nick) {
  		try {
  
  			// Attempt to confirm which authentication module we are using
  			String authPlugin = (String)mods.plugin.callAPI("AuthSelector", "GetAuthMethod");
  			if (authPlugin == null) {
  				authPlugin = "unknown";
  			}
  			authPlugin.toLowerCase();
  			if (authPlugin.equals("nickserv") || authPlugin.equals("unknown")) {
  				if (hasNS(nick)) {
  					return true;
  				}
  			}
  
  			if (authPlugin.equals("quakenet") || authPlugin.equals("unknown")){
  				if (hasQ(nick)) {
  					return true;
  				}
  			}
  
  			// Unsupported setting - should not occur.
  			return false;
  		} catch (final ChoobNoSuchPluginException e) {
  			// No options module, attempt all forms of auth until one works
  			if (hasNS(nick)) {
  				return true;
  			}
  			if (hasQ(nick)) {
  				return true;
  			}
  
  			// No successful auth
  			return false;
  		} catch (final ChoobException e) {
  			// Oh. Bugger
  			System.err.println("Authentication broken:");
  			e.printStackTrace(System.err);
  			return false;
  		}
  	}
  
  	/**
  	 * Check if the given nickname is authed with NickServ (if it is loaded).
  	 * @param nick The nickname to validate.
  	 * @return Whethe the nick is authorised
  	 */
  	private boolean hasNS(final String nick) {
  		try
  		{
  
  			//return (Boolean)mods.plugin.callAPI("NickServ", "Check", nickName, false);
  			return (Boolean)mods.plugin.callAPI("NickServ", "Check", nick);
  		}
  		catch (final ChoobNoSuchPluginException e)
  		{
  			// XXX Should this throw an exception?:
  			//if (!allowNoNS)
  			//	throw new ChoobAuthError("The NickServ plugin is not loaded! Holy mother of God save us all!");
  			return true;
  		}
  		catch (final ChoobException e)
  		{
  			// OMFG!
  			System.err.println("Error calling NickServ check! Details:");
  			e.printStackTrace();
  			return false;
  		}
  	}
  
  	/**
  	 * Check if the nickname given is authed with Q (if QuakenetAuth is loaded)
  	 * @param nick The nick to check.
  	 * @return Auth status.
  	 */
  	private boolean hasQ(final String nick) {
  		try {
  
  			// Get the account name being used by the current nick
  			final String account = (String)mods.plugin.callAPI("QuakenetAuth", "Account", nick);
  			if (account == null) {
  				return false;
  			}
  
  			// User has a current Q auth.
  			return true;
  		} catch (final ChoobNoSuchPluginException e) {
  			// XXX Ohnoes, no quakenet stuffs!
  			return false;
  		} catch (final ChoobException e) {
  			// Aieeeeee!
  			System.err.println("Error getting QuakenetAuth account! Details:");
  			e.printStackTrace(System.err);
  			return false;
  		}
  	}
  	/**
  	 * Check if the given nickName has permission and is authed
  	 * @param permission The permission to check.
  	 * @param userEvent The event to validate and check the permission on.
  	 * @throws ChoobAuthError If the nick is not authorised.
  	 */
  	public void checkNickPerm(final Permission permission, final UserEvent userEvent) throws ChoobAuthError	{
  		checkAuth(userEvent);
  
  		if (!hasNickPerm(permission, userEvent))
  			throw new ChoobUserAuthError(permission);
  	}
  
  	/**
  	 * Check if the given nickName has permission and is authed
  	 * @param permission The permission to check.
  	 * @param userEvent The event to validate and check the permission on.
  	 * @return Whether the nick is authorised.
  	 */
  	public boolean hasNickPerm(final Permission permission, final UserEvent userEvent) {
  		// XXX: Check for synthetic userEvent here.
  
  		if (!hasAuth(userEvent)) {
  			return false;
  		}
  		String authPlugin = "unknown";
  		try {
  			authPlugin = (String)mods.plugin.callAPI("AuthSelector", "GetAuthMethod");
  