/* * 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; import java.io.FileNotFoundException; import java.io.IOException; import java.net.InetAddress; import java.net.NetworkInterface; import java.util.Enumeration; import java.util.Random; import java.util.Vector; import org.alfresco.jlan.debug.Debug; import org.alfresco.jlan.server.ServerListener; import org.alfresco.jlan.server.SrvSessionList; import org.alfresco.jlan.server.Version; import org.alfresco.jlan.server.auth.ICifsAuthenticator; import org.alfresco.jlan.server.config.ConfigId; import org.alfresco.jlan.server.config.ConfigurationListener; import org.alfresco.jlan.server.config.CoreServerConfigSection; import org.alfresco.jlan.server.config.InvalidConfigurationException; import org.alfresco.jlan.server.config.ServerConfiguration; import org.alfresco.jlan.server.core.DeviceContext; import org.alfresco.jlan.server.core.InvalidDeviceInterfaceException; import org.alfresco.jlan.server.core.ShareType; import org.alfresco.jlan.server.core.SharedDevice; import org.alfresco.jlan.server.filesys.DiskInterface; import org.alfresco.jlan.server.filesys.NetworkFileServer; import org.alfresco.jlan.server.thread.ThreadRequestPool; import org.alfresco.jlan.smb.Dialect; import org.alfresco.jlan.smb.DialectSelector; import org.alfresco.jlan.smb.ServerType; import org.alfresco.jlan.smb.dcerpc.UUID; import org.alfresco.jlan.smb.server.nio.NIOCifsConnectionsHandler; import org.alfresco.jlan.smb.server.nio.win32.AsyncWinsockCifsConnectionsHandler; import org.alfresco.jlan.smb.server.win32.Win32NetBIOSLanaMonitor; /** * CIFS Server Class * * @author gkspencer */ public class SMBServer extends NetworkFileServer implements Runnable, ConfigurationListener { // Constants // // Server version private static final String ServerVersion = Version.SMBServerVersion; // CIFS server custom server events public static final int CIFSNetBIOSNamesAdded = ServerListener.ServerCustomEvent; // Configuration sections private CIFSConfigSection m_cifsConfig; private CoreServerConfigSection m_coreConfig; // Server thread private Thread m_srvThread; // Session connections handler private CifsConnectionsHandler m_connectionsHandler; // Active session list private SrvSessionList m_sessions; // Server type flags, used when announcing the host private int m_srvType = ServerType.WorkStation + ServerType.Server; // Server GUID private UUID m_serverGUID; // CIFS packet pool private CIFSPacketPool m_packetPool; /** * Create an SMB server using the specified configuration. * * @param cfg ServerConfiguration */ public SMBServer(ServerConfiguration cfg) throws IOException { super("CIFS", cfg); // Call the common constructor CommonConstructor(); } /** * Add a new session to the server * * @param sess SMBSrvSession */ public final void addSession(SMBSrvSession sess) { // Add the session to the session list m_sessions.addSession(sess); // Propagate the debug settings to the new session if ( Debug.EnableInfo && hasDebug()) { // Enable session debugging, output to the same stream as the server sess.setDebug(getCIFSConfiguration().getSessionDebugFlags()); } } /** * Check if the disk share is read-only. * * @param shr SharedDevice */ protected final void checkReadOnly(SharedDevice shr) { // For disk devices check if the shared device is read-only, this should also check if the // shared device path actually exists. if ( shr.getType() == ShareType.DISK) { // Check if the disk device is read-only try { // Get the device interface for the shared device DiskInterface disk = (DiskInterface) shr.getInterface(); if ( disk.isReadOnly(null, shr.getContext())) { // The disk is read-only, mark the share as read-only int attr = shr.getAttributes(); if ( (attr & SharedDevice.ReadOnly) == 0) attr += SharedDevice.ReadOnly; shr.setAttributes(attr); // Debug if ( Debug.EnableInfo && hasDebug()) Debug.println("[SMB] Add Share " + shr.toString() + " : isReadOnly"); } } catch (InvalidDeviceInterfaceException ex) { // Shared device interface error if ( Debug.EnableInfo && hasDebug()) Debug.println("[SMB] Add Share " + shr.toString() + " : " + ex.toString()); } catch (FileNotFoundException ex) { // Shared disk device local path does not exist if ( Debug.EnableInfo && hasDebug()) Debug.println("[SMB] Add Share " + shr.toString() + " : " + ex.toString()); } catch (IOException ex) { // Shared disk device access error if ( Debug.EnableInfo && hasDebug()) Debug.println("[SMB] Add Share " + shr.toString() + " : " + ex.toString()); } } } /** * Common constructor code. */ private void CommonConstructor() throws IOException { // Get the CIFS server configuration m_cifsConfig = (CIFSConfigSection) getConfiguration().getConfigSection(CIFSConfigSection.SectionName); if ( m_cifsConfig != null) { // Add the SMB server as a configuration change listener of the server configuration getConfiguration().addListener(this); // Check if debug output is enabled if ( getCIFSConfiguration().getSessionDebugFlags() != 0) setDebug(true); // Set the server version setVersion(ServerVersion); // Create the active session list m_sessions = new SrvSessionList(); // Get the core server configuration m_coreConfig = (CoreServerConfigSection) getConfiguration().getConfigSection( CoreServerConfigSection.SectionName); if ( m_coreConfig != null) { // Create the CIFS packet pool using the global memory pool m_packetPool = new CIFSPacketPool( m_coreConfig.getMemoryPool()); // Check if packet pool debugging is enabled if (( m_cifsConfig.getSessionDebugFlags() & SMBSrvSession.DBG_PKTPOOL) != 0) m_packetPool.setDebug( true); } } else setEnabled(false); } /** * Delete temporary shares created by the share mapper for the specified session * * @param sess SMBSrvSession */ public final void deleteTemporaryShares(SMBSrvSession sess) { // Delete temporary shares via the share mapper getShareMapper().deleteShares(sess); } /** * Return the CIFS server configuration * * @return CIFSConfigSection */ public final CIFSConfigSection getCIFSConfiguration() { return m_cifsConfig; } /** * Return the server comment. * * @return java.lang.String */ public final String getComment() { return getCIFSConfiguration().getComment(); } /** * Return the CIFS server name * * @return String */ public final String getServerName() { return getCIFSConfiguration().getServerName(); } /** * Return the server type flags. * * @return int */ public final int getServerType() { return m_srvType; } /** * Return the per session debug flag settings. */ public final int getSessionDebug() { return getCIFSConfiguration().getSessionDebugFlags(); } /** * Return the list of SMB dialects that this server supports. * * @return DialectSelector */ public final DialectSelector getSMBDialects() { return getCIFSConfiguration().getEnabledDialects(); } /** * Return the CIFS authenticator * * @return CifsAuthenticator */ public final ICifsAuthenticator getCifsAuthenticator() { return getCIFSConfiguration().getAuthenticator(); } /** * Return the active session list * * @return SrvSessionList */ public final SrvSessionList getSessions() { return m_sessions; } /** * Return the CIFS packet pool * * @return CIFSPacketPool */ public final CIFSPacketPool getPacketPool() { return m_packetPool; } /** * Return the thread pool * * @return ThreadRequestPool */ public final ThreadRequestPool getThreadPool() { return m_coreConfig.getThreadPool(); } /** * Start the SMB server. */ public void run() { // Fire a server startup event fireServerEvent(ServerListener.ServerStartup); // Indicate that the server is active setActive(true); // Check if we are running under Windows boolean isWindows = isWindowsNTOnwards(); // Generate a GUID for the server based on the server name Random r = new Random(); m_serverGUID = new UUID(r.nextLong(), r.nextLong()); // Debug if ( Debug.EnableInfo && hasDebug()) { // Dump the server name/version and Java runtime details Debug.println("[SMB] CIFS Server " + getServerName() + " starting"); Debug.print("[SMB] Version " + isVersion()); Debug.print(", Java VM " + System.getProperty("java.vm.version")); Debug.println(", OS " + System.getProperty("os.name") + ", version " + System.getProperty("os.version")); // Check for server alias names if ( getCIFSConfiguration().hasAliasNames()) Debug.println("[SMB] Server alias(es) : " + getCIFSConfiguration().getAliasNames()); // Output the authenticator details if ( getCifsAuthenticator() != null) Debug.println("[SMB] Using authenticator " + getCifsAuthenticator().toString()); // Display the timezone offset/name if ( getGlobalConfiguration().getTimeZone() != null) Debug.println("[SMB] Server timezone " + getGlobalConfiguration().getTimeZone() + ", offset from UTC = " + getGlobalConfiguration().getTimeZoneOffset() / 60 + "hrs"); else Debug.println("[SMB] Server timezone offset = " + getGlobalConfiguration().getTimeZoneOffset() / 60 + "hrs"); // Dump the available dialect list Debug.println("[SMB] Dialects enabled = " + getSMBDialects()); // Dump the share list Debug.println("[SMB] Shares:"); Enumeration<SharedDevice> enm = getFullShareList(getCIFSConfiguration().getServerName(), null).enumerateShares(); while (enm.hasMoreElements()) { SharedDevice share = enm.nextElement(); Debug.println("[SMB] " + share.toString() + " " + (share.getContext() != null ? share.getContext().toString() : "")); } } // Create a server socket to listen for incoming session requests try { // Add the IPC$ named pipe shared device AdminSharedDevice admShare = new AdminSharedDevice(); getFilesystemConfiguration().addShare(admShare); // Clear the server shutdown flag setShutdown(false); // Get the list of IP addresses the server is bound to getServerIPAddresses(); // Check if the NT SMB dialect is enabled, if so then update the server flags to // indicate that this is an NT server if ( getCIFSConfiguration().getEnabledDialects().hasDialect(Dialect.NT) == true) { // Enable the NT server flag getCIFSConfiguration().setServerType(getServerType() + ServerType.NTServer); // Debug if ( Debug.EnableInfo && hasDebug()) Debug.println("[SMB] Added NTServer flag to host announcement"); } // Create the CIFS connections handler // // Note: The older thread per session/socket handler is used for Win32 NetBIOS connections if ( getCIFSConfiguration().hasDisableNIOCode() || getCIFSConfiguration().hasWin32NetBIOS()) { // Use the older threaded connections handler (thread per session model) m_connectionsHandler = new ThreadedCifsConnectionsHandler(); } else { // Check if the Java socket or JNI based connections handler should be used if ( getCIFSConfiguration().hasTcpipSMB() || getCIFSConfiguration().hasNetBIOSSMB()) { // Use the NIO based native SMB/NetBIOS SMB connections handler m_connectionsHandler = new NIOCifsConnectionsHandler(); } else { // Use the JNI based Winsock NetBIOS connections handler m_connectionsHandler = new AsyncWinsockCifsConnectionsHandler(); } } // Initialize the connections handler m_connectionsHandler.initializeHandler( this, getCIFSConfiguration()); m_connectionsHandler.startHandler(); // Check if there are any session handlers installed, if not then close the server if ( m_connectionsHandler.numberOfSessionHandlers() > 0 || getCIFSConfiguration().hasWin32NetBIOS()) { // Fire a server active event fireServerEvent(ServerListener.ServerActive); // Wait for incoming connection requests while (hasShutdown() == false) { // Sleep for a while try { Thread.sleep(3000L); } catch (InterruptedException ex) { } } } else if ( Debug.EnableError && hasDebug()) { // DEBUG Debug.println("[SMB] No valid session handlers, server closing"); } } catch (Exception ex) { // Do not report an error if the server has shutdown, closing the server socket // causes an exception to be thrown. if ( hasShutdown() == false) { Debug.println("[SMB] Server error : " + ex.toString(), Debug.Error); Debug.println(ex); // Store the error, fire a server error event setException(ex); fireServerEvent(ServerListener.ServerError); } } // Debug if ( Debug.EnableInfo && hasDebug()) Debug.println("[SMB] SMB Server shutting down ..."); // Close the host announcer and session handlers m_connectionsHandler.stopHandler(); // Shutdown the Win32 NetBIOS LANA monitor, if enabled if ( isWindows && Win32NetBIOSLanaMonitor.getLanaMonitor() != null) { // Shutdown the LANA monitor Win32NetBIOSLanaMonitor.getLanaMonitor().shutdownRequest(); } // Indicate that the server is not active setActive(false); fireServerEvent(ServerListener.ServerShutdown); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println("[SMB] Packet pool at shutdown: " + getPacketPool()); } /** * Notify the server that a session has been closed. * * @param sess SMBSrvSession */ protected final void sessionClosed(SMBSrvSession sess) { // Remove the session from the active session list m_sessions.removeSession(sess); // DEBUG if ( hasDebug()) { Debug.println("[SMB] Closed session " + sess.getSessionId() + ", sessions=" + m_sessions.numberOfSessions()); if ( m_sessions.numberOfSessions() > 0 && m_sessions.numberOfSessions() <= 10) { Enumeration<Integer> sessIds = m_sessions.enumerate(); Debug.print(" Active sessions ["); while ( sessIds.hasMoreElements()) { SMBSrvSession curSess = (SMBSrvSession) m_sessions.findSession( sessIds.nextElement()); Debug.print("" + curSess.getSessionId() + "=" + curSess.getRemoteAddress().getHostAddress() + ","); } Debug.println("]"); } } // Notify session listeners that a session has been closed fireSessionClosedEvent(sess); } /** * Notify the server that a user has logged on. * * @param sess SMBSrvSession */ protected final void sessionLoggedOn(SMBSrvSession sess) { // Notify session listeners that a user has logged on. fireSessionLoggedOnEvent(sess); } /** * Notify the server that a session has been closed. * * @param sess SMBSrvSession */ protected final void sessionOpened(SMBSrvSession sess) { // Notify session listeners that a session has been closed fireSessionOpenEvent(sess); } /** * Shutdown the SMB server * * @param immediate boolean */ public final void shutdownServer(boolean immediate) { // Indicate that the server is closing setShutdown(true); try { // Wakeup the main CIFS server thread m_srvThread.interrupt(); } catch (Exception ex) { } // Close the active sessions Enumeration<Integer> enm = m_sessions.enumerate(); while (enm.hasMoreElements()) { // Get the session id and associated session Integer sessId = enm.nextElement(); SMBSrvSession sess = (SMBSrvSession) m_sessions.findSession(sessId); // Inform listeners that the session has been closed fireSessionClosedEvent(sess); // Close the session sess.closeSession(); } // Wait for the main server thread to close if ( m_srvThread != null) { try { m_srvThread.join(3000); } catch (Exception ex) { } } // Fire a shutdown notification event fireServerEvent(ServerListener.ServerShutdown); } /** * Start the SMB server in a seperate thread */ public void startServer() { // Create a seperate thread to run the SMB server m_srvThread = new Thread(this); m_srvThread.setName("CIFS Server"); m_srvThread.start(); } /** * Validate configuration changes that are relevant to the SMB server * * @param id int * @param config ServerConfiguration * @param newVal Object * @return int * @throws InvalidConfigurationException */ public int configurationChanged(int id, ServerConfiguration config, Object newVal) throws InvalidConfigurationException { int sts = ConfigurationListener.StsIgnored; try { // Check if the configuration change affects the SMB server switch (id) { // Server enable/disable case ConfigId.ServerSMBEnable: // Check if the server is active Boolean enaSMB = (Boolean) newVal; if ( isActive() && enaSMB.booleanValue() == false) { // Shutdown the server shutdownServer(false); } else if ( isActive() == false && enaSMB.booleanValue() == true) { // Start the server startServer(); } // Indicate that the setting was accepted sts = ConfigurationListener.StsAccepted; break; // Changes that can be accepted without restart case ConfigId.SMBComment: case ConfigId.SMBDialects: case ConfigId.SMBTCPPort: case ConfigId.SMBMacExtEnable: case ConfigId.SMBDebugEnable: case ConfigId.ServerTimezone: case ConfigId.ServerTZOffset: case ConfigId.ShareList: case ConfigId.ShareMapper: case ConfigId.SecurityAuthenticator: case ConfigId.UsersList: case ConfigId.DebugDevice: sts = ConfigurationListener.StsAccepted; break; // Changes that affect new sessions only case ConfigId.SMBSessionDebug: sts = ConfigurationListener.StsNewSessionsOnly; if ( newVal instanceof Integer) { Integer dbgVal = (Integer) newVal; setDebug( dbgVal.intValue() != 0 ? true : false); } break; // Changes that require a restart case ConfigId.SMBHostName: case ConfigId.SMBAliasNames: case ConfigId.SMBDomain: case ConfigId.SMBBroadcastMask: case ConfigId.SMBAnnceEnable: case ConfigId.SMBAnnceInterval: case ConfigId.SMBAnnceDebug: case ConfigId.SMBTCPEnable: case ConfigId.SMBBindAddress: sts = ConfigurationListener.StsRestartRequired; break; } } catch (Exception ex) { throw new InvalidConfigurationException("SMB Server configuration error", ex); } // Return the status return sts; } /** * Determine if we are running under Windows NT onwards * * @return boolean */ private final boolean isWindowsNTOnwards() { // Get the operating system name property String osName = System.getProperty("os.name"); if ( osName.startsWith("Windows")) { if ( osName.endsWith("95") || osName.endsWith("98") || osName.endsWith("ME")) { // Windows 95-ME return false; } // Looks like Windows NT onwards return true; } // Not Windows return false; } /** * Get the list of local IP addresses * */ private final void getServerIPAddresses() { try { // Get the local IP address list Enumeration<NetworkInterface> enm = NetworkInterface.getNetworkInterfaces(); Vector<InetAddress> addrList = new Vector<InetAddress>(); while (enm.hasMoreElements()) { // Get the current network interface NetworkInterface ni = enm.nextElement(); // Get the address list for the current interface Enumeration<InetAddress> addrs = ni.getInetAddresses(); while (addrs.hasMoreElements()) addrList.add(addrs.nextElement()); } // Convert the vector of addresses to an array if ( addrList.size() > 0) { // Convert the address vector to an array InetAddress[] inetAddrs = new InetAddress[addrList.size()]; // Copy the address details to the array for (int i = 0; i < addrList.size(); i++) inetAddrs[i] = addrList.elementAt(i); // Set the server IP address list setServerAddresses(inetAddrs); } } catch (Exception ex) { // DEBUG if ( Debug.EnableError && hasDebug()) Debug.println("[SMB] Error getting local IP addresses, " + ex.toString()); } } /** * Return the server GUID * * @return UUID */ public final UUID getServerGUID() { return m_serverGUID; } /** * Send a NetBIOS names added event to server listeners * * @param lana int */ public final void fireNetBIOSNamesAddedEvent(int lana) { // Send the event to registered listeners, encode the LANA id in the top of the event id fireServerEvent(CIFSNetBIOSNamesAdded + (lana << 16)); } }