/*
* Copyright (C) 2006-2008 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.jlan.server.auth.passthru;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import org.alfresco.jlan.debug.Debug;
import org.alfresco.jlan.netbios.NetBIOSName;
import org.alfresco.jlan.netbios.NetBIOSNameList;
import org.alfresco.jlan.netbios.NetBIOSSession;
import org.alfresco.jlan.smb.PCShare;
import org.alfresco.jlan.util.IPAddress;
/**
* Passthru Servers Class
*
* <p>Contains a list of one or more servers that are used for passthru authentication. The status of the
* servers is tracked so that offline servers are not used but periodically monitored so that they can be
* returned to the online list of servers.
*
* <p>The server list may be initialized from a list of server names or addresses, or by specifying a domain
* name in which case the primary and backup domain controllers will be used.
*
* @author GKSpencer
*
*/
public class PassthruServers
{
// Default timeout for setting up a new session
private static final int DefaultConnectTimeout = 5000; // 5 seconds
// Default interval to check if offline servers
private static final long DefaultOfflineCheckInterval = 5 * 60000; // 5 minutes
// List of online and offline authentication servers
private List<PassthruServerDetails> m_onlineList;
private List<PassthruServerDetails> m_offlineList;
// Timeout value when opening a session to an authentication server, in milliseconds
private int m_tmo = DefaultConnectTimeout;
// Domain name, if using domain controllers
private String m_domain;
// Offline server check interval
private long m_offlineCheckInterval = DefaultOfflineCheckInterval;
// Offline server checker thread
PassthruOfflineChecker m_offlineChecker;
// Debug output enable
private boolean m_debug;
// Null domain uses any available server option
private boolean m_nullDomainUseAnyServer;
/**
* Inner class used to periodically check offline servers to see if they are back online
*/
class PassthruOfflineChecker extends Thread
{
// Thread shutdown request flag
private boolean m_ishutdown;
/**
* Default constructor
*/
PassthruOfflineChecker()
{
setDaemon(true);
setName("PassthruOfflineChecker");
start();
}
/**
* Main thread code
*/
public void run()
{
// Loop until shutdown
m_ishutdown = false;
while ( m_ishutdown == false)
{
// Sleep for a while
try
{
sleep( m_offlineCheckInterval);
}
catch ( InterruptedException ex)
{
}
// Check if shutdown has been requested
if( m_ishutdown == true)
continue;
// Check if there are any offline servers to check
if ( getOfflineServerCount() > 0)
{
// Enumerate the offline server list
int idx = 0;
PassthruServerDetails offlineServer = null;
PCShare authShare = new PCShare("", "IPC$", "", "");
AuthenticateSession authSess = null;
while ( idx < getOfflineServerCount())
{
// Get an offline server from the list
offlineServer = (PassthruServerDetails) m_offlineList.get(idx);
if ( offlineServer != null)
{
try
{
// Set the target host name
authShare.setNodeName(offlineServer.getAddress().getHostAddress());
// Try and connect to the authentication server
authSess = AuthSessionFactory.OpenAuthenticateSession( authShare, getConnectionTimeout());
// Close the session
try
{
authSess.CloseSession();
}
catch ( Exception ex)
{
}
// Authentication server is online, move it to the online list
serverOnline(offlineServer);
}
catch ( Exception ex)
{
// Debug
if ( hasDebug())
Debug.println("Passthru offline check failed for " + offlineServer.getName());
}
// Check if the server is now online
if ( offlineServer.isOnline() == false)
idx++;
}
}
}
}
// Debug
if( hasDebug())
Debug.println("Passthru offline checker thread closed");
}
/**
* Shutdown the checker thread
*/
public final void shutdownRequest()
{
m_ishutdown = true;
this.interrupt();
}
/**
* Wakeup the offline checker thread to process the offline server list
*/
public final void processOfflineServers()
{
this.interrupt();
}
}
/**
* Default constructor
*/
public PassthruServers() {
commonInit();
}
/**
* Class constructor
*
* @param checkInterval In seconds
*/
public PassthruServers(int checkInterval) {
// Set the offline checker wakeup interval
m_offlineCheckInterval = ((long) checkInterval) * 1000L;
// Common initialization
commonInit();
}
/**
* Common constructor
*/
private void commonInit() {
// Create the server lists
m_onlineList = new ArrayList<PassthruServerDetails>();
m_offlineList = new ArrayList<PassthruServerDetails>();
// Create and start the offline server checker thread
m_offlineChecker = new PassthruOfflineChecker();
}
/**
* Return the count of online authentication servers
*
* @return int
*/
public final int getOnlineServerCount()
{
return m_onlineList.size();
}
/**
* Return the count of offline authentication servers
*
* @return int
*/
public final int getOfflineServerCount()
{
return m_offlineList.size();
}
/**
* Return the total count of online and offline servers
*
* @return int
*/
public final int getTotalServerCount()
{
return m_onlineList.size() + m_offlineList.size();
}
/**
* Determine if there are online servers
*
* @return boolean
*/
public final boolean hasOnlineServers()
{
return m_onlineList.size() > 0 ? true : false;
}
/**
* Return the connection timeout, in milliseconds
*
* @return int
*/
public final int getConnectionTimeout()
{
return m_tmo;
}
/**
* Determine if the authentication servers are domain controllers
*
* @return boolean
*/
public final boolean isDomainAuthentication()
{
return m_domain != null ? true : false;
}
/**
* Return the domain name
*
* @return String
*/
public final String getDomain()
{
return m_domain;
}
/**
* Check if debug output is enabled
*
* @return boolean
*/
public final boolean hasDebug() {
return m_debug;
}
/**
* Check if a null domain should use any available passthru server
*
* @return boolean
*/
public final boolean getNullDomainUseAnyServer()
{
return m_nullDomainUseAnyServer;
}
/**
* Open a new session to an authentication server
*
* @return AuthenticateSession
*/
public final AuthenticateSession openSession()
{
return openSession( false, null);
}
/**
* Open a new session to an authentication server
*
* @param useExtSec boolean
* @param clientDomain String
* @return AuthenticateSession
*/
public final AuthenticateSession openSession(boolean useExtSec, String clientDomain)
{
// Get the details of an authentication server to connect to
PassthruServerDetails passthruServer = null;
if ( clientDomain != null)
passthruServer = getAuthenticationServer( clientDomain);
else
passthruServer = getAuthenticationServer();
if ( passthruServer == null)
return null;
// Debug
if ( hasDebug())
Debug.println("Open authenticate session to " + passthruServer + ( clientDomain != null ? " (routed for client domain " + clientDomain + ")" : ""));
// Open a new authentication session to the server
PCShare authShare = new PCShare(passthruServer.getAddress().getHostAddress(), "IPC$", "", "");
AuthenticateSession authSess = null;
while ( authSess == null && passthruServer != null && hasOnlineServers()) {
try
{
// Open a session to the current authentication server
authSess = AuthSessionFactory.OpenAuthenticateSession( authShare, getConnectionTimeout());
// Update the passthru statistics
passthruServer.incrementAuthenticationCount();
}
catch ( Exception ex)
{
// Debug
if ( hasDebug())
Debug.println("Failed to connect to " + passthruServer + " : " + ex.getMessage());
// Failed to connect to the current authentication server, mark the server as offline
serverOffline(passthruServer);
}
// Check if we have a valid session
if ( authSess == null)
{
// Try another authentication server
passthruServer = getAuthenticationServer();
// Debug
if( hasDebug())
Debug.println("Trying authentication server " + passthruServer);
}
}
// Return the authentication session
return authSess;
}
/**
* Return the details of an online server to use for authentication
*
* @return PassthruServerDetails
*/
protected PassthruServerDetails getAuthenticationServer()
{
// Check if any available passthru server or a passthru server that does not have a domain name set
// should be used
PassthruServerDetails passthruServer = null;
if ( getNullDomainUseAnyServer())
{
// Use the first available passthru server
synchronized ( m_onlineList)
{
// Rotate the head of the list and return the new head of list server details
if ( m_onlineList.size() > 1)
m_onlineList.add(m_onlineList.remove(0));
if ( m_onlineList.size() > 0)
passthruServer = (PassthruServerDetails) m_onlineList.get(0);
}
}
else
{
// Search for an online passthru server that does not have a domain name set
synchronized ( m_onlineList)
{
int idx = 0;
while ( idx < m_onlineList.size() && passthruServer == null)
{
// Get the current passthru server details
PassthruServerDetails curServer = m_onlineList.get( idx);
if ( curServer.getDomain() == null || curServer.getDomain().length() == 0)
{
// Use this passthru server
passthruServer = curServer;
// Move to the back of the list
m_onlineList.add( m_onlineList.remove( idx));
}
// Update the server index
idx++;
}
}
}
// Return the selected passthru server, or null if not available
return passthruServer;
}
/**
* Return the details of an online server to use for authentication of the specified client
* domain
*
* @param clientDomain String
* @return PassthruServerDetails
*/
protected PassthruServerDetails getAuthenticationServer( String clientDomain)
{
// Rotate the head of the list and return the new head of list server details
PassthruServerDetails passthruServer = null;
synchronized ( m_onlineList)
{
int idx = 0;
while ( idx < m_onlineList.size() && passthruServer == null)
{
// Get the current passthru server details
PassthruServerDetails curServer = m_onlineList.get( idx);
if ( curServer.getDomain() != null && curServer.getDomain().equals( clientDomain))
{
// Use this passthru server
passthruServer = curServer;
// Move to the back of the list
m_onlineList.add( m_onlineList.remove( idx));
}
// Update the server index
idx++;
}
}
return passthruServer;
}
/**
* Move a server from the list of online servers to the offline list
*
* @param server PassthruServerDetails
*/
protected final void serverOffline(PassthruServerDetails server)
{
// Set the server status
server.setOnline(false);
// Remove the server from the online list
synchronized( m_onlineList)
{
m_onlineList.remove(server);
}
// Add it to the offline list
synchronized( m_offlineList)
{
m_offlineList.add( server);
}
// Debug
if ( hasDebug())
Debug.println("Passthru server offline, " + server);
}
/**
* Move a server from the list of offline servers to the online list
*
* @param server PassthruServerDetails
*/
protected final void serverOnline(PassthruServerDetails server)
{
// Set the server status
server.setOnline(true);
// Remove the server from the offline list
synchronized( m_offlineList)
{
m_offlineList.remove(server);
}
// Add it to the online list
synchronized( m_onlineList)
{
m_onlineList.add( server);
}
// Debug
if ( hasDebug())
Debug.println("Passthru server online, " + server);
}
/**
* Set the session connect timeout value, in milliseconds
*
* @param tmo int
*/
public final void setConnectionTimeout(int tmo)
{
m_tmo = tmo;
}
/**
* Set the offline check interval, in seconds
*
* @param interval long
*/
public final void setOfflineCheckInterval(long interval)
{
m_offlineCheckInterval = interval * 1000L;
// Wakeup the offline checked thread to pickup the new interval
m_offlineChecker.processOfflineServers();
}
/**
* Set the list of servers to use for passthru authentication using a comma delimeted list
* of server names/addresses
*
* @param servers String
*/
public final void setServerList(String servers)
{
// Split the server list into seperate name/address tokens
StringTokenizer tokens = new StringTokenizer(servers, ",");
while ( tokens.hasMoreTokens())
{
// Get the current server name/address
String srvName = tokens.nextToken().trim();
// Check if the server address also contains a domain name
String domain = null;
int pos = srvName.indexOf( '\\');
if ( pos != -1)
{
domain = srvName.substring(0, pos);
srvName = srvName.substring( pos + 1);
}
// If a name a has been specified convert it to an address, if an address has been specified
// then convert to a name.
InetAddress srvAddr = null;
if ( IPAddress.isNumericAddress(srvName))
{
// Get the server name
try
{
// Get the host address and name
srvAddr = InetAddress.getByName(srvName);
srvName = srvAddr.getHostName();
}
catch ( UnknownHostException ex)
{
// Debug
if ( hasDebug())
Debug.println("Passthru failed to find name/address for " + srvName);
}
}
else
{
// Get the server address
try
{
srvAddr = InetAddress.getByName(srvName);
}
catch ( UnknownHostException ex)
{
// Debug
if ( hasDebug())
Debug.println("Passthru failed to find address for " + srvName);
}
}
// Create the passthru server details and add to the list of offline servers
if ( srvName != null && srvAddr != null)
{
// Create the passthru server details
PassthruServerDetails passthruServer = new PassthruServerDetails(srvName, domain, srvAddr, false);
m_offlineList.add( passthruServer);
// Debug
if ( hasDebug())
Debug.println("Added passthru server " + passthruServer);
}
}
// Wakeup the server checker thread to check each of the servers just added and move servers that are
// accessible to the online list
m_offlineChecker.processOfflineServers();
}
/**
* Set the domain to use for passthru authentication
*
* @param domain String
* @exception IOException
*/
public final void setDomain(String domain)
throws IOException
{
// DEBUG
if (hasDebug())
Debug.println("Passthru finding domain controller for " + domain + " ...");
// Find a domain controller or the browse master
NetBIOSName nbName = null;
try
{
// Find a domain controller
nbName = NetBIOSSession.FindName(domain, NetBIOSName.DomainControllers, getConnectionTimeout());
// DEBUG
if (hasDebug())
Debug.println(" Found " + nbName.numberOfAddresses() + " domain controller(s)");
}
catch (IOException ex)
{
}
// If we did not find a domain controller look for the browse master
if ( nbName == null) {
try
{
// Try and find the browse master for the workgroup
nbName = NetBIOSSession.FindName( domain, NetBIOSName.MasterBrowser, getConnectionTimeout());
// DEBUG
if (hasDebug())
Debug.println(" Found browse master at " + nbName.getIPAddressString(0));
}
catch (IOException ex)
{
throw new IOException("Failed to find domain controller or browse master for " + domain);
}
}
// Add the passthru server(s)
//
// Try and convert the address to a name for each domain controller
for ( int i = 0; i < nbName.numberOfAddresses(); i++)
{
// Get the domain controller name
InetAddress dcAddr = null;
String dcName = null;
try
{
// Get the current domain controller address
dcAddr = InetAddress.getByName(nbName.getIPAddressString(i));
// Get the list of NetBIOS names for the domain controller
NetBIOSNameList nameList = NetBIOSSession.FindNamesForAddress(dcAddr.getHostAddress());
NetBIOSName dcNBName = nameList.findName(NetBIOSName.FileServer, false);
if ( dcNBName != null)
dcName = dcNBName.getName();
}
catch (UnknownHostException ex)
{
// Debug
if ( hasDebug())
Debug.println("Invalid address for server " + nbName.getIPAddressString(i));
}
catch (Exception ex)
{
// Failed to get domain controller name, use the address
dcName = dcAddr.getHostAddress();
// Debug
if ( hasDebug())
Debug.println("Failed to get NetBIOS name for server " + dcAddr);
}
// Create a passthru server entry for the domain controller
if ( dcAddr != null)
{
// Create the passthru authentication server record
PassthruServerDetails passthruServer = new PassthruServerDetails(dcName, domain, dcAddr, false);
m_offlineList.add(passthruServer);
// Debug
if ( hasDebug())
Debug.println("Added passthru server " + passthruServer);
}
}
// Wakeup the server checker thread to check each of the servers just added and move servers that are
// accessible to the online list
m_offlineChecker.processOfflineServers();
}
/**
* Set the null domain to use any available server option
*
* @param nullDomain boolean
*/
public final void setNullDomainUseAnyServer( boolean nullDomain)
{
m_nullDomainUseAnyServer = nullDomain;
}
/**
* Enable/disbale debug output
*
* @param dbg boolean
*/
public final void setDebug( boolean dbg) {
m_debug = dbg;
}
/**
* Shutdown passthru authentication
*/
public final void shutdown()
{
// Shutdown the offline server checker thread
m_offlineChecker.shutdownRequest();
// Clear the online and offline server lists
m_onlineList.clear();
m_offlineList.clear();
}
/**
* Return the passthru server details as a string
*
* @return String
*/
public String toString()
{
StringBuilder str = new StringBuilder();
str.append("[");
if ( isDomainAuthentication())
{
str.append("Domain:");
str.append(getDomain());
}
else
str.append("Servers:");
str.append(",Online=");
str.append(getOnlineServerCount());
str.append(",Offline=");
str.append(getOfflineServerCount());
str.append(",nullDomain=");
str.append( getNullDomainUseAnyServer() ? "On" : "Off");
str.append("]");
return str.toString();
}
}