/* * 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.smb.server.nio.win32; import java.io.IOException; import java.util.Enumeration; import java.util.Iterator; import java.util.Vector; import org.alfresco.jlan.debug.Debug; import org.alfresco.jlan.netbios.NetBIOSName; import org.alfresco.jlan.netbios.win32.NetBIOSSelectionKey; import org.alfresco.jlan.netbios.win32.NetBIOSSelector; import org.alfresco.jlan.netbios.win32.NetBIOSSocket; import org.alfresco.jlan.server.SessionHandlerList; import org.alfresco.jlan.server.config.InvalidConfigurationException; import org.alfresco.jlan.smb.server.CIFSConfigSection; import org.alfresco.jlan.smb.server.CifsConnectionsHandler; import org.alfresco.jlan.smb.server.PacketHandler; import org.alfresco.jlan.smb.server.SMBServer; import org.alfresco.jlan.smb.server.SMBSrvSession; import org.alfresco.jlan.smb.server.nio.RequestHandler; import org.alfresco.jlan.smb.server.nio.RequestHandlerListener; /** * Asynchronous Winsock NIO Connections Handler Class * * <p>Initializes the configured CIFS session handlers and listens for incoming requests using a single thread. * * @author gkspencer */ public class AsyncWinsockCifsConnectionsHandler implements CifsConnectionsHandler, RequestHandlerListener, Runnable { // Constants // // Number of session socket channels each request handler thread monitors // // Note: Windows has an OS limit of 64 events public static final int SessionSocketsPerHandler = 64; // File server and workstation NetBIOS names to listen for incoming connections on private NetBIOSName m_srvNbName; private NetBIOSName m_wksNbName; // File server LANA private int m_srvLANA; // List of session handlers that are waiting for incoming requests private SessionHandlerList m_handlerList; // Selector used to monitor incoming connections private NetBIOSSelector m_nbSelector; // Session request handler(s) // // Each handler processes the socket read events for a number of session socket channels private Vector<AsyncWinsockCIFSRequestHandler> m_requestHandlers; // SMB server private SMBServer m_server; // Connection handler thread private Thread m_thread; // Shutdown request flag private boolean m_shutdown; // Session id private int m_sessId; // Client socket timeout, in milliseconds private int m_clientSocketTimeout; // Idle session reper thread private IdleSessionReaper m_idleSessReaper; // Debug output private boolean m_debug; /** * Idle Session Reaper Thread Class * * <p>Check for sessions that have no recent I/O requests. The session timeout is configurable. */ protected class IdleSessionReaper implements Runnable { // Reaper wakeup interval private long m_wakeup; // Reaper thread private Thread m_reaperThread; // Shutdown request flag private boolean m_shutdown = false; /** * Class constructor * * @param intvl long */ public IdleSessionReaper(long intvl) { m_wakeup = intvl; // Create a thread for the reaper, and start the thread m_reaperThread = new Thread( this); m_reaperThread.setDaemon( true); m_reaperThread.setName( "CIFS_IdleSessionReaper_Winsock"); m_reaperThread.start(); } /** * Shutdown the connection reaper */ public final void shutdownRequest() { m_shutdown = true; m_reaperThread.interrupt(); } /** * Connection reaper thread */ public void run() { // Loop forever, or until shutdown while ( m_shutdown == false) { // Sleep for a while try { Thread.sleep(m_wakeup); } catch(InterruptedException ex) { } // Check if there is a shutdown pending if ( m_shutdown == true) break; // Check for idle sessions in the active CIFS request handlers Enumeration<AsyncWinsockCIFSRequestHandler> enumHandlers = m_requestHandlers.elements(); while ( enumHandlers.hasMoreElements()) { // Get the current request handler and check for idle session AsyncWinsockCIFSRequestHandler curHandler = enumHandlers.nextElement(); if ( curHandler != null) { // Check for idle sessions int idleCnt = curHandler.checkForIdleSessions(); // DEBUG if ( idleCnt > 0 && Debug.EnableInfo && hasDebug()) Debug.println( "[SMB] Idle session check, removed " + idleCnt + " sessions for " + curHandler.getName()); } } } } }; /** * Class constructor */ public AsyncWinsockCifsConnectionsHandler() { m_handlerList = new SessionHandlerList(); } /** * Check if debug output is enabled * * @return boolean */ public final boolean hasDebug() { return m_debug; } /** * Return the count of active session handlers * * @return int */ public int numberOfSessionHandlers() { return m_handlerList.numberOfHandlers(); } /** * Initialize the connections handler * * @param srv SMBServer * @param config CIFSConfigSection * @exception InvalidConfigurationException */ public final void initializeHandler( SMBServer srv, CIFSConfigSection config) throws InvalidConfigurationException { // Save the server the handler is associated with m_server = srv; // Check if socket debug output is enabled if ( (config.getSessionDebugFlags() & SMBSrvSession.DBG_SOCKET) != 0) m_debug = true; // Create the native SMB/port 445 session handler, if enabled if ( config.hasWin32NetBIOS()) { // Get the Win32 NetBIOS file server name String srvName = null; if ( srv.getCIFSConfiguration().getWin32ServerName() != null) srvName = srv.getCIFSConfiguration().getWin32ServerName(); else srvName = srv.getCIFSConfiguration().getServerName(); // Create the local NetBIOS names to listen for incoming connections on m_srvNbName = new NetBIOSName( srvName, NetBIOSName.FileServer, false); m_wksNbName = new NetBIOSName( srvName, NetBIOSName.WorkStation, false); // Create the session handlers to listen for incoming requests try { // Get the NetBIOS LANA to use, or -1 if the first available should be used int lana = srv.getCIFSConfiguration().getWin32LANA(); // Create the session listener for the file server name AsyncWinsockNetBIOSSessionHandler sessHandler = new AsyncWinsockNetBIOSSessionHandler( lana, m_srvNbName, srv); sessHandler.initializeSessionHandler( m_server); m_handlerList.addHandler( sessHandler); // Get the server LANA m_srvLANA = sessHandler.getLANA(); // Create the session listener for the workstation name sessHandler = new AsyncWinsockNetBIOSSessionHandler( lana, m_wksNbName, srv); sessHandler.initializeSessionHandler( m_server); m_handlerList.addHandler( sessHandler); } catch (IOException ex) { // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println( "[SMB] Error initializing session handler, " + ex.getMessage()); throw new InvalidConfigurationException( ex.getMessage()); } } // Check if any session handlers were created if ( m_handlerList.numberOfHandlers() == 0) throw new InvalidConfigurationException( "No CIFS session handlers enabled"); // Set the client socket timeout m_clientSocketTimeout = config.getSocketTimeout(); // Create the session request handler list and add the first handler m_requestHandlers = new Vector<AsyncWinsockCIFSRequestHandler>(); AsyncWinsockCIFSRequestHandler reqHandler = new AsyncWinsockCIFSRequestHandler( m_srvNbName, m_srvLANA, m_server.getThreadPool(), SessionSocketsPerHandler, m_clientSocketTimeout); reqHandler.setListener( this); reqHandler.setDebug( hasDebug()); m_requestHandlers.add( reqHandler); } /** * Start the connection handler thread */ public final void startHandler() { // Start the connection handler in its own thread m_thread = new Thread( this); m_thread.setName( "WinsockCIFSConnectionsHandler"); m_thread.setDaemon( false); m_thread.start(); // Start the idle session reaper thread, if session timeouts are enabled if ( m_clientSocketTimeout > 0) m_idleSessReaper = new IdleSessionReaper( m_clientSocketTimeout / 2); } /** * Stop the connections handler */ public final void stopHandler() { // Check if the thread is running if ( m_thread != null) { // Set the shutdown flag m_shutdown = true; // Stop the idle session reaper thread, if enabled if ( m_idleSessReaper != null) m_idleSessReaper.shutdownRequest(); // Close the first session handler socket to wakeup the listener thread if ( m_handlerList.numberOfHandlers() > 0) { // Get the first handler and close the listening socket AsyncWinsockNetBIOSSessionHandler sessHandler = (AsyncWinsockNetBIOSSessionHandler) m_handlerList.getHandlerAt( 0); NetBIOSSocket srvSock = sessHandler.getSocket(); if ( srvSock != null) { try { srvSock.closeSocket(); } catch (Exception ex) { Debug.println( ex); } } } } } /** * Run the connections handler in a seperate thread */ public void run() { // Clear the shutdown flag, may have been restarted m_shutdown = false; // Initialize the socket selector try { // Create the selector m_nbSelector = new NetBIOSSelector(); // Register the server sockets with the selector for ( int idx = 0; idx < m_handlerList.numberOfHandlers(); idx++) { // Get the current Winsock NetBIOS server socket and register with the selector for socket accept events AsyncWinsockNetBIOSSessionHandler curHandler = (AsyncWinsockNetBIOSSessionHandler) m_handlerList.getHandlerAt( idx); // Get the NetBIOSSocket and register with the selector NetBIOSSocket srvSocket = curHandler.getSocket(); srvSocket.configureBlocking( false); srvSocket.register( m_nbSelector, NetBIOSSelectionKey.OP_ACCEPT, curHandler); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println( "[SMB] Listening for connections on " + curHandler); } } catch ( IOException ex) { // DEBUG if ( Debug.EnableInfo && hasDebug()) { Debug.println( "[SMB] Error opening/registering Selector"); Debug.println( ex); } m_shutdown = true; } // Loop until shutdown while ( m_shutdown != true) { // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println( "[SMB] Waiting for new connection ..."); // Wait until there are some connections int connCnt = 0; try { connCnt = m_nbSelector.select(); } catch ( IOException ex) { // DEBUG if ( Debug.EnableError && hasDebug()) { Debug.println( "[SMB] Error waiting for connection"); Debug.println(ex); } } // Check if there are any connection events to process if ( connCnt == 0) continue; // Iterate the selected keys Iterator<NetBIOSSelectionKey> keysIter = m_nbSelector.selectedKeys().iterator(); while ( keysIter.hasNext()) { // Get the current selection key and check if there is an incoming connection NetBIOSSelectionKey selKey = keysIter.next(); if ( selKey.isAcceptable()) { try { // Get the listening server socket, accept the new client connection AsyncWinsockNetBIOSSessionHandler channelHandler = (AsyncWinsockNetBIOSSessionHandler) selKey.attachment(); NetBIOSSocket clientSock = channelHandler.getSocket().accept(); // Check if the connection is to the file server name if ( channelHandler.getNetBIOSName().getType() == NetBIOSName.FileServer) { // Create a packet handler for the new connection PacketHandler pktHandler = channelHandler.createPacketHandler( clientSock); // Create the new session SMBSrvSession sess = SMBSrvSession.createSession( pktHandler, m_server, ++m_sessId); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println( "[SMB] Connection from " + clientSock.getName() + ", handler=" + channelHandler + ", sess=" + sess.getUniqueId()); // Add the new session to a request handler thread queueSessionToHandler( sess); } } catch ( IOException ex) { // DEBUG if ( Debug.EnableError && hasDebug()) { Debug.println( "[SMB] Failed to accept connection"); Debug.println( ex); } } } // Remove the key from the selected list keysIter.remove(); } } // Close the session handlers for ( int idx = 0; idx < m_handlerList.numberOfHandlers(); idx++) { // Close the current session handler AsyncWinsockNetBIOSSessionHandler sessHandler = (AsyncWinsockNetBIOSSessionHandler) m_handlerList.getHandlerAt( idx); sessHandler.closeSessionHandler( null); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println( "[SMB] Closed session handler " + sessHandler); } // Close the request handlers while ( m_requestHandlers.size() > 0) { // Close the current request handler AsyncWinsockCIFSRequestHandler reqHandler = m_requestHandlers.remove( 0); reqHandler.closeHandler(); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println( "[SMB] Closed request handler, " + reqHandler.getName()); } // Close the selector if ( m_nbSelector != null) { try { m_nbSelector.close(); } catch (Exception ex) { // DEBUG if ( Debug.EnableError && hasDebug()) Debug.println( "[SMB] Error closing socket selector, " + ex.getMessage()); } } // Clear the active thread before exiting m_thread = null; } /** * Queue a new session to a request handler * * @param sess SMBSrvSession */ private final void queueSessionToHandler( SMBSrvSession sess) { // Check if the current handler has room for a new session AsyncWinsockCIFSRequestHandler reqHandler = m_requestHandlers.firstElement(); if ( reqHandler == null || reqHandler.hasFreeSessionSlot() == false) { // Create a new session request handler and add to the head of the list reqHandler = new AsyncWinsockCIFSRequestHandler( m_srvNbName, m_srvLANA, m_server.getThreadPool(), SessionSocketsPerHandler, m_clientSocketTimeout); reqHandler.setListener( this); reqHandler.setDebug( hasDebug()); m_requestHandlers.add( 0, reqHandler); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println( "[SMB] Added new CIFS request handler, " + reqHandler); } // Queue the new session to the current request handler reqHandler.queueSessionToHandler( sess); } /** * Enable/disable debug output * * @param ena boolean */ public final void setDebug( boolean ena) { m_debug = ena; } /** * Request handler has no sessions to listen for events for * * @param reqHandler RequestHandler */ public void requestHandlerEmpty(RequestHandler reqHandler) { synchronized ( m_handlerList) { // Check if the request handler is the current head of the handler list, if not then we can close // this request handler if ( m_requestHandlers.get( 0).getName().equals( reqHandler.getName()) == false) { // Remove the handler from the request handler list m_requestHandlers.remove( reqHandler); // Close the request handler reqHandler.closeHandler(); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println( "[SMB] Removed empty request handler, " + reqHandler.getName()); } } } }