/* * 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.IOException; import java.net.InetAddress; import java.net.SocketException; import java.net.SocketTimeoutException; import java.util.Enumeration; import java.util.Hashtable; import java.util.Vector; import org.alfresco.jlan.debug.Debug; import org.alfresco.jlan.netbios.NetBIOSException; import org.alfresco.jlan.netbios.NetBIOSName; import org.alfresco.jlan.netbios.NetBIOSSession; import org.alfresco.jlan.netbios.RFCNetBIOSProtocol; import org.alfresco.jlan.server.SrvSession; import org.alfresco.jlan.server.auth.AuthenticatorException; import org.alfresco.jlan.server.auth.ICifsAuthenticator; import org.alfresco.jlan.server.filesys.DiskDeviceContext; import org.alfresco.jlan.server.filesys.NetworkFile; import org.alfresco.jlan.server.filesys.TooManyConnectionsException; import org.alfresco.jlan.server.filesys.TreeConnection; import org.alfresco.jlan.server.thread.ThreadRequestPool; import org.alfresco.jlan.smb.Capability; import org.alfresco.jlan.smb.DataType; import org.alfresco.jlan.smb.Dialect; import org.alfresco.jlan.smb.DialectSelector; import org.alfresco.jlan.smb.NTTime; import org.alfresco.jlan.smb.PacketType; import org.alfresco.jlan.smb.SMBDate; import org.alfresco.jlan.smb.SMBErrorText; import org.alfresco.jlan.smb.SMBStatus; import org.alfresco.jlan.smb.server.notify.NotifyRequest; import org.alfresco.jlan.smb.server.notify.NotifyRequestList; import org.alfresco.jlan.util.DataPacker; import org.alfresco.jlan.util.HexDump; import org.alfresco.jlan.util.StringList; /** * <p> * The SMB server creates a server session object for each incoming session request. * * <p> * The server session holds the context of a particular session, including the list of open files * and active searches. * * @author gkspencer */ public class SMBSrvSession extends SrvSession implements Runnable { // Define the default receive buffer size to allocate. public static final int DefaultBufferSize = 0x010000 + RFCNetBIOSProtocol.HEADER_LEN; public static final int LanManBufferSize = 8192; // Default and maximum number of connection slots public static final int DefaultConnections = 4; public static final int MaxConnections = 16; // Maximum multiplexed packets allowed (client can send up to this many SMBs before waiting for // a response) // // Setting NTMaxMultiplexed to one will disable asynchronous notifications on the client public static final int LanManMaxMultiplexed = 1; public static final int NTMaxMultiplexed = 4; // Maximum number of virtual circuits private static final int MaxVirtualCircuits = 0; // Debug flag values public static final int DBG_NETBIOS = 0x00000001; // NetBIOS layer public static final int DBG_STATE = 0x00000002; // Session state changes public static final int DBG_RXDATA = 0x00000004; // Received data public static final int DBG_TXDATA = 0x00000008; // Transmit data public static final int DBG_DUMPDATA = 0x00000010; // Dump data packets public static final int DBG_NEGOTIATE = 0x00000020; // Protocol negotiate phase public static final int DBG_TREE = 0x00000040; // Tree connection/disconnection public static final int DBG_SEARCH = 0x00000080; // File/directory search public static final int DBG_INFO = 0x00000100; // Information requests public static final int DBG_FILE = 0x00000200; // File open/close/info public static final int DBG_FILEIO = 0x00000400; // File read/write public static final int DBG_TRAN = 0x00000800; // Transactions public static final int DBG_ECHO = 0x00001000; // Echo requests public static final int DBG_ERROR = 0x00002000; // Errors public static final int DBG_IPC = 0x00004000; // IPC$ requests public static final int DBG_LOCK = 0x00008000; // Lock/unlock requests public static final int DBG_PKTTYPE = 0x00010000; // Received packet type public static final int DBG_DCERPC = 0x00020000; // DCE/RPC public static final int DBG_STATECACHE = 0x00040000; // File state cache public static final int DBG_TIMING = 0x00080000; // Time packet processing public static final int DBG_NOTIFY = 0x00100000; // Asynchronous change notification public static final int DBG_STREAMS = 0x00200000; // NTFS streams public static final int DBG_SOCKET = 0x00400000; // NetBIOS/native SMB socket connections public static final int DBG_PKTPOOL = 0x00800000; // Packet pool allocate/release public static final int DBG_PKTSTATS = 0x01000000; // Packet pool statistics public static final int DBG_THREADPOOL = 0x02000000; // Thread pool public static final int DBG_BENCHMARK = 0x04000000; // Benchmarking // Server session object factory private static SrvSessionFactory m_factory = new DefaultSrvSessionFactory(); // Packet handler used to send/receive SMB packets over a particular protocol private PacketHandler m_pktHandler; // Protocol handler for this session, depends upon the negotiated SMB dialect private ProtocolHandler m_handler; // SMB session state. private int m_state = SMBSrvSessionState.NBSESSREQ; // SMB dialect that this session has negotiated to use. private int m_dialect = Dialect.Unknown; // Callers NetBIOS name and target name private String m_callerNBName; private String m_targetNBName; // Notify change requests and notifications pending flag private NotifyRequestList m_notifyList; private boolean m_notifyPending; // Default SMB/CIFS flags and flags2, ORed with the SMB packet flags/flags2 before sending a // response // to the client. private int m_defFlags; private int m_defFlags2; // Asynchronous response packet queue // // Contains SMB response packets that could not be sent due to SMB requests being processed. The // asynchronous responses must be sent after any pending requests have been processed as the client may // disconnect the session. private Vector<SMBSrvPacket> m_asynchQueue; // Maximum client buffer size and multiplex count private int m_maxBufSize; private int m_maxMultiplex; // Client capabilities private int m_clientCaps; // Virtual circuit list private VirtualCircuitList m_vcircuits; // Setup objects used during two stage session setup before the virtual circuit is allocated private Hashtable m_setupObjects; // Flag to indicate an asynchronous read has been queued/is being processed private boolean m_asyncRead; /** * Class constructor. * * @param handler Packet handler used to send/receive SMBs * @param srv Server that this session is associated with. */ protected SMBSrvSession(PacketHandler handler, SMBServer srv) { super(-1, srv, handler.isProtocolName(), null); // Set the packet handler m_pktHandler = handler; // If this is a TCPIP SMB or Win32 NetBIOS session then bypass the NetBIOS session setup // phase. if ( isProtocol() == SMBSrvPacket.PROTOCOL_TCPIP || isProtocol() == SMBSrvPacket.PROTOCOL_WIN32NETBIOS) { // Advance to the SMB negotiate dialect phase setState(SMBSrvSessionState.SMBNEGOTIATE); // Check if the client name is available if ( handler.hasClientName()) m_callerNBName = handler.getClientName(); } // Allocate the virtual circuit list m_vcircuits = new VirtualCircuitList(); } /** * Return the session protocol type * * @return int */ public final int isProtocol() { return m_pktHandler.isProtocol(); } /** * Find the tree connection for the request * * @param smbPkt SMBSrvPacket * @return TreeConnection */ public final TreeConnection findTreeConnection(SMBSrvPacket smbPkt) { // Find the virtual circuit for the request TreeConnection tree = null; VirtualCircuit vc = findVirtualCircuit(smbPkt.getUserId()); if ( vc != null) { // Find the tree connection tree = vc.findConnection(smbPkt.getTreeId()); } // Return the tree connection, or null if invalid UID or TID return tree; } /** * Add a new virtual circuit, return the allocated UID * * @param vc VirtualCircuit * @return int */ public final int addVirtualCircuit(VirtualCircuit vc) { // Add the new virtual circuit return m_vcircuits.addCircuit(vc); } /** * Find a virtual circuit with the allocated UID * * @param uid int * @return VirtualCircuit */ public final VirtualCircuit findVirtualCircuit(int uid) { // Find the virtual circuit with the specified UID VirtualCircuit vc = m_vcircuits.findCircuit(uid); if ( vc != null) { // Set the session client information from the virtual circuit setClientInformation(vc.getClientInformation()); // Setup any authentication context getSMBServer().getCifsAuthenticator().setCurrentUser( getClientInformation()); } // Return the virtual circuit return vc; } /** * Remove a virtual circuit * * @param uid int */ public final void removeVirtualCircuit(int uid) { // Remove the virtual circuit with the specified UID m_vcircuits.removeCircuit(uid, this); } /** * Return the active virtual circuit count * * @return int */ public final int numberOfVirtualCircuits() { return (m_vcircuits != null ? m_vcircuits.getCircuitCount() : 0); } /** * Cleanup any resources owned by this session, close virtual circuits and change notification * requests. */ protected final void cleanupSession() { // Debug if ( Debug.EnableInfo && hasDebug(DBG_STATE)) debugPrintln("Cleanup session, vcircuits=" + m_vcircuits.getCircuitCount() + ", changeNotify=" + getNotifyChangeCount()); // Close the virtual circuits if ( m_vcircuits.getCircuitCount() > 0) { // Enumerate the virtual circuits and close all circuits Enumeration<Integer> uidEnum = m_vcircuits.enumerateUIDs(); while (uidEnum.hasMoreElements()) { // Get the UID for the current circuit Integer uid = uidEnum.nextElement(); // Close the virtual circuit VirtualCircuit vc = m_vcircuits.findCircuit(uid); if ( vc != null) { // DEBUG if ( Debug.EnableInfo && hasDebug(DBG_STATE)) debugPrintln(" Cleanup vc=" + vc); vc.closeCircuit(this); } } // Clear the virtual circuit list m_vcircuits.clearCircuitList(); } // Check if there are active change notification requests if ( m_notifyList != null && m_notifyList.numberOfRequests() > 0) { // Remove the notify requests from the associated device context notify list for (int i = 0; i < m_notifyList.numberOfRequests(); i++) { // Get the current change notification request and remove from the global notify // list NotifyRequest curReq = m_notifyList.getRequest(i); if ( curReq.getDiskContext().hasChangeHandler()) curReq.getDiskContext().getChangeHandler().removeNotifyRequests(this); } } // Delete any temporary shares that were created for this session getSMBServer().deleteTemporaryShares(this); // Commit any outstanding transaction that may have been started during cleanup if ( hasTransaction()) endTransaction(); } /** * Close the session socket */ protected final void closeSocket() { // Indicate that the session is being shutdown setShutdown(true); // Close the packet handler try { m_pktHandler.closeHandler(); } catch (Exception ex) { Debug.println( ex); } } /** * Close the session */ public final void closeSession() { // Cleanup the session (open files/virtual circuits/searches) cleanupSession(); // Call the base class super.closeSession(); try { // Set the session into a hangup state setState(SMBSrvSessionState.NBHANGUP); // Close the socket closeSocket(); } catch (Exception ex) { } } /** * Finalize, object is about to be garbage collected. Make sure resources are released. */ public void finalize() { // Check if there are any active resources cleanupSession(); // Make sure the socket is closed and deallocated closeSocket(); } /** * Return the default flags SMB header value * * @return int */ public final int getDefaultFlags() { return m_defFlags; } /** * Return the default flags2 SMB header value * * @return int */ public final int getDefaultFlags2() { return m_defFlags2; } /** * Return the count of active change notification requests * * @return int */ public final int getNotifyChangeCount() { if ( m_notifyList == null) return 0; return m_notifyList.numberOfRequests(); } /** * Return the client maximum buffer size * * @return int */ public final int getClientMaximumBufferSize() { return m_maxBufSize; } /** * Return the client maximum muliplexed requests * * @return int */ public final int getClientMaximumMultiplex() { return m_maxMultiplex; } /** * Return the client capability flags * * @return int */ public final int getClientCapabilities() { return m_clientCaps; } /** * Determine if the client has the specified capability enabled * * @param cap int * @return boolean */ public final boolean hasClientCapability(int cap) { if ( (m_clientCaps & cap) != 0) return true; return false; } /** * Return the SMB dialect type that the server/client have negotiated. * * @return int */ public final int getNegotiatedSMBDialect() { return m_dialect; } /** * Return the packet handler used by the session * * @return PacketHandler */ public final PacketHandler getPacketHandler() { return m_pktHandler; } /** * Return the CIFS packet pool from the packet handler * * @return CIFSPacketPool */ public final CIFSPacketPool getPacketPool() { return m_pktHandler.getPacketPool(); } /** * Return the thread pool * * @return ThreadRequestPool */ public final ThreadRequestPool getThreadPool() { return getSMBServer().getThreadPool(); } /** * Return the remote NetBIOS name that was used to create the session. * * @return String */ public final String getRemoteNetBIOSName() { return m_callerNBName; } /** * Check if the session has a target NetBIOS name * * @return boolean */ public final boolean hasTargetNetBIOSName() { return m_targetNBName != null ? true : false; } /** * Return the target NetBIOS name that was used to create the session * * @return String */ public final String getTargetNetBIOSName() { return m_targetNBName; } /** * Cehck if the clients remote address is available * * @return boolean */ public final boolean hasRemoteAddress() { return m_pktHandler.hasRemoteAddress(); } /** * Return the client network address * * @return InetAddress */ public final InetAddress getRemoteAddress() { return m_pktHandler.getRemoteAddress(); } /** * Return the server that this session is associated with. * * @return SMBServer */ public final SMBServer getSMBServer() { return (SMBServer) getServer(); } /** * Return the server name that this session is associated with. * * @return String */ public final String getServerName() { return getSMBServer().getServerName(); } /** * Return the session state * * @return int */ public final int getState() { return m_state; } /** * Hangup the session. * * @param reason java.lang.String Reason the session is being closed. */ public void hangupSession(String reason) { // Debug if ( Debug.EnableInfo && hasDebug(DBG_STATE)) { debugPrint("## Session closing. "); debugPrintln(reason); } // Set the session into a NetBIOS hangup state setState(SMBSrvSessionState.NBHANGUP); } /** * Check if the Macintosh exteniosn SMBs are enabled * * @return boolean */ public final boolean hasMacintoshExtensions() { return getSMBServer().getCIFSConfiguration().hasMacintoshExtensions(); } /** * Check if there is a change notification update pending * * @return boolean */ public final boolean hasNotifyPending() { return m_notifyPending; } /** * Determine if the session has a setup object for the specified PID * * @param pid int * @return boolean */ public final boolean hasSetupObject(int pid) { if ( m_setupObjects == null) return false; return m_setupObjects.get(new Integer(pid)) != null ? true : false; } /** * Return the session setup object for the specified PID * * @param pid int * @return Object */ public final Object getSetupObject(int pid) { if ( m_setupObjects == null) return null; return m_setupObjects.get(new Integer(pid)); } /** * Store the setup object for the specified PID * * @param pid int * @param obj Object */ public final void setSetupObject(int pid, Object obj) { if ( m_setupObjects == null) m_setupObjects = new Hashtable(); m_setupObjects.put(new Integer(pid), obj); } /** * Remove the session setup object for the specified PID * * @param pid int * @return Object */ public final Object removeSetupObject(int pid) { if ( m_setupObjects == null) return null; return m_setupObjects.remove(new Integer(pid)); } /** * Set the change notify pending flag * * @param pend boolean */ public final void setNotifyPending(boolean pend) { m_notifyPending = pend; } /** * Set the client maximum buffer size * * @param maxBuf int */ public final void setClientMaximumBufferSize(int maxBuf) { m_maxBufSize = maxBuf; } /** * Set the client maximum multiplexed * * @param maxMpx int */ public final void setClientMaximumMultiplex(int maxMpx) { m_maxMultiplex = maxMpx; } /** * Set the client capability flags * * @param flags int */ public final void setClientCapabilities(int flags) { m_clientCaps = flags; } /** * Set the default flags value to be ORed with outgoing response packet flags * * @param flags int */ public final void setDefaultFlags(int flags) { m_defFlags = flags; } /** * Set the default flags2 value to be ORed with outgoing response packet flags2 field * * @param flags int */ public final void setDefaultFlags2(int flags) { m_defFlags2 = flags; } /** * Set the session state. * * @param state int */ protected void setState(int state) { // Debug if ( Debug.EnableInfo && hasDebug(DBG_STATE)) debugPrintln("State changed to " + SMBSrvSessionState.getStateAsString(state)); // Change the session state m_state = state; } /** * Process the NetBIOS session request message, either accept the session request and send back * a NetBIOS accept or reject the session and send back a NetBIOS reject and hangup the session. * * 2param smbPkt SMBSrvPacket */ protected void procNetBIOSSessionRequest( SMBSrvPacket smbPkt) throws IOException, NetBIOSException { // Check if the received packet contains enough data for a NetBIOS session request packet. if ( smbPkt.getReceivedLength() < RFCNetBIOSProtocol.SESSREQ_LEN || smbPkt.getHeaderType() != RFCNetBIOSProtocol.SESSION_REQUEST) { // Debug if ( Debug.EnableInfo && hasDebug(DBG_NETBIOS)) { Debug.println("NBREQ invalid packet len=" + smbPkt.getReceivedLength() + ", header=0x" + Integer.toHexString(smbPkt.getHeaderType())); HexDump.Dump( smbPkt.getBuffer(), smbPkt.getReceivedLength(), 0, Debug.getDebugInterface()); } throw new NetBIOSException("NBREQ Invalid packet len=" + smbPkt.getReceivedLength()); } // Do a few sanity checks on the received packet byte[] buf = smbPkt.getBuffer(); if ( buf[4] != (byte) 32 || buf[38] != (byte) 32) throw new NetBIOSException("NBREQ Invalid NetBIOS name data"); // Extract the from/to NetBIOS encoded names, and convert to normal strings. StringBuffer nbName = new StringBuffer(32); for (int i = 0; i < 32; i++) nbName.append((char) buf[5 + i]); String toName = NetBIOSSession.DecodeName(nbName.toString()); toName = toName.trim(); nbName.setLength(0); for (int i = 0; i < 32; i++) nbName.append((char) buf[39 + i]); String fromName = NetBIOSSession.DecodeName(nbName.toString()); fromName = fromName.trim(); // Debug if ( Debug.EnableInfo && hasDebug(DBG_NETBIOS)) debugPrintln("NetBIOS CALL From " + fromName + " to " + toName); // Check that the request is for this server boolean forThisServer = false; if ( toName.compareTo(getServerName()) == 0 || toName.compareTo(NetBIOSName.SMBServer) == 0 || toName.compareTo(NetBIOSName.SMBServer2) == 0 || toName.compareTo("*") == 0) { // Request is for this server forThisServer = true; } else if ( getSMBServer().getCIFSConfiguration().hasAliasNames() == true) { // Check for a connection to one of the alias server names StringList aliasNames = getSMBServer().getCIFSConfiguration().getAliasNames(); if ( aliasNames.containsString(toName)) forThisServer = true; } else { // Check if the caller is using an IP address InetAddress[] srvAddr = getSMBServer().getServerAddresses(); if ( srvAddr != null) { // Check for an address match int idx = 0; while (idx < srvAddr.length && forThisServer == false) { // Check the current IP address if ( srvAddr[idx++].getHostAddress().compareTo(toName) == 0) forThisServer = true; } } } // If we did not find an address match then reject the session request if ( forThisServer == false) throw new NetBIOSException("NBREQ Called name is not this server (" + toName + ")"); // Debug if ( Debug.EnableInfo && hasDebug(DBG_NETBIOS)) debugPrintln("NetBIOS session request from " + fromName); // Save the callers name and target name m_callerNBName = fromName; m_targetNBName = toName; // Move the session to the SMB negotiate state setState(SMBSrvSessionState.SMBNEGOTIATE); // Set the remote client name setRemoteName(fromName); // Build a NetBIOS session accept message smbPkt.setHeaderType(RFCNetBIOSProtocol.SESSION_ACK); smbPkt.setHeaderFlags(0); smbPkt.setHeaderLength(0); // Output the NetBIOS session accept packet m_pktHandler.writePacket( smbPkt, 4, true); } /** * Process an SMB dialect negotiate request. * * @param smbPkt SMBSrvPacket */ protected void procSMBNegotiate( SMBSrvPacket smbPkt) throws SMBSrvException, IOException { // Initialize the NetBIOS header byte[] buf = smbPkt.getBuffer(); buf[0] = (byte) RFCNetBIOSProtocol.SESSION_MESSAGE; // Check if the received packet looks like a valid SMB if ( smbPkt.getCommand() != PacketType.Negotiate || smbPkt.checkPacketIsValid(0, 2) == false) { sendErrorResponseSMB( smbPkt, SMBStatus.SRVUnrecognizedCommand, SMBStatus.ErrSrv); return; } // Decode the data block into a list of requested SMB dialects int dataPos = smbPkt.getByteOffset(); int dataLen = smbPkt.getByteCount(); String diaStr = null; StringList dialects = new StringList(); while (dataLen > 0) { // Decode an SMB dialect string from the data block, always ASCII strings diaStr = DataPacker.getDataString(DataType.Dialect, buf, dataPos, dataLen, false); if ( diaStr != null) { // Add the dialect string to the list of requested dialects dialects.addString(diaStr); } else { // Invalid dialect block in the negotiate packet, send an error response and hangup // the session. sendErrorResponseSMB( smbPkt, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); setState(SMBSrvSessionState.NBHANGUP); return; } // Update the remaining data position and count dataPos += diaStr.length() + 2; // data type and null dataLen -= diaStr.length() + 2; } // Find the highest level SMB dialect that the server and client both support DialectSelector dia = getSMBServer().getCIFSConfiguration().getEnabledDialects(); int diaIdx = -1; for (int i = 0; i < Dialect.Max; i++) { // Check if the current dialect is supported by the server if ( dia.hasDialect(i)) { // Check if the client supports the current dialect. If the current dialect is a // higher level dialect than the currently nominated dialect, update the nominated // dialect index. for (int j = 0; j < Dialect.SMB_PROT_MAXSTRING; j++) { // Check if the dialect string maps to the current dialect index if ( Dialect.DialectType(j) == i && dialects.containsString(Dialect.DialectString(j))) { // Update the selected dialect type, if the current dialect is a newer // dialect if ( i > diaIdx) diaIdx = i; } } } } // Debug if ( Debug.EnableInfo && hasDebug(DBG_NEGOTIATE)) { if ( diaIdx == -1) debugPrintln("Failed to negotiate SMB dialect"); else debugPrintln("Negotiated SMB dialect - " + Dialect.DialectTypeString(diaIdx)); } // Check if we successfully negotiated an SMB dialect with the client if ( diaIdx != -1) { // Store the negotiated SMB diialect type m_dialect = diaIdx; // Convert the dialect type to an index within the clients SMB dialect list diaIdx = dialects.findString(Dialect.DialectTypeString(diaIdx)); // Allocate a protocol handler for the negotiated dialect, if we cannot get a protocol // handler then bounce the request. m_handler = ProtocolFactory.getHandler(m_dialect); if ( m_handler != null) { // Debug if ( Debug.EnableInfo && hasDebug(DBG_NEGOTIATE)) debugPrintln("Assigned protocol handler - " + m_handler.getClass().getName()); // Set the protocol handlers associated session m_handler.setSession(this); } else { // Could not get a protocol handler for the selected SMB dialect, indicate to the // client that no suitable dialect available. diaIdx = -1; } } // Check if the extended security flag has been set by the client boolean extendedSecurity = (smbPkt.getFlags2() & SMBSrvPacket.FLG2_EXTENDEDSECURITY) != 0 ? true : false; // Build the negotiate response SMB for Core dialect if ( m_dialect == -1 || m_dialect <= Dialect.CorePlus) { // Core dialect negotiate response, or no valid dialect response smbPkt.setParameterCount(1); smbPkt.setParameter(0, diaIdx); smbPkt.setByteCount(0); smbPkt.setTreeId(0); smbPkt.setUserId(0); } else if ( m_dialect <= Dialect.LanMan2_1) { // We are using case sensitive pathnames and long file names smbPkt.setFlags(SMBSrvPacket.FLG_CASELESS); smbPkt.setFlags2(SMBSrvPacket.FLG2_LONGFILENAMES); // Access the authenticator for this server and determine if the server is in share or // user level security mode. ICifsAuthenticator auth = getSMBServer().getCifsAuthenticator(); // LanMan dialect negotiate response smbPkt.setParameterCount(13); smbPkt.setParameter(0, diaIdx); smbPkt.setParameter(1, auth.getSecurityMode()); smbPkt.setParameter(2, LanManBufferSize); smbPkt.setParameter(3, LanManMaxMultiplexed); // maximum multiplexed requests smbPkt.setParameter(4, MaxVirtualCircuits); // maximum number of virtual circuits smbPkt.setParameter(5, 0); // read/write raw mode support // Create a session token, using the system clock smbPkt.setParameterLong(6, (int) (System.currentTimeMillis() & 0xFFFFFFFF)); // Return the current server date/time SMBDate srvDate = new SMBDate(System.currentTimeMillis()); smbPkt.setParameter(8, srvDate.asSMBTime()); smbPkt.setParameter(9, srvDate.asSMBDate()); // Server timezone offset from UTC smbPkt.setParameter(10, getServer().getGlobalConfiguration().getTimeZoneOffset()); // Encryption key length smbPkt.setParameter(11, auth.getEncryptionKeyLength()); smbPkt.setParameter(12, 0); smbPkt.setTreeId(0); smbPkt.setUserId(0); // Let the authenticator pack any remaining fields in the negotiate response try { // Pack the remaining negotiate response fields auth.generateNegotiateResponse(this, smbPkt, false); } catch (AuthenticatorException ex) { // Log the error if ( Debug.EnableError && hasDebug(DBG_NEGOTIATE)) debugPrintln("Negotiate error - " + ex.getMessage()); // Close the session setState(SMBSrvSessionState.NBHANGUP); return; } } else if ( m_dialect == Dialect.NT) { // We are using case sensitive pathnames and long file names setDefaultFlags(SMBSrvPacket.FLG_CASELESS); setDefaultFlags2(SMBSrvPacket.FLG2_LONGFILENAMES + SMBSrvPacket.FLG2_UNICODE); // Access the authenticator for this server and determine if the server is in share or // user level security mode. ICifsAuthenticator auth = getSMBServer().getCIFSConfiguration().getAuthenticator(); // Check if the authenticator supports extended security, override the client setting if ( auth.hasExtendedSecurity() == false) extendedSecurity = false; // NT dialect negotiate response NTParameterPacker nt = new NTParameterPacker( smbPkt.getBuffer()); smbPkt.setParameterCount(17); nt.packWord(diaIdx); // selected dialect index nt.packByte(auth.getSecurityMode()); nt.packWord(NTMaxMultiplexed); // maximum multiplexed requests // setting to 1 will disable change notify requests from the client nt.packWord(MaxVirtualCircuits); // maximum number of virtual circuits int maxBufSize = getSMBServer().getPacketPool().getLargestSize() - RFCNetBIOSProtocol.HEADER_LEN; nt.packInt(maxBufSize); nt.packInt(0); // maximum raw size // Create a session token, using the system clock if ( auth.hasExtendedSecurity() == false || extendedSecurity == false) nt.packInt((int) (System.currentTimeMillis() & 0xFFFFFFFFL)); else nt.packInt(0); // Set server capabilities, switch off extended security if the client does not support // it int srvCapabs = auth.getServerCapabilities(); if ( auth.hasExtendedSecurity() == false || extendedSecurity == false) srvCapabs &= ~Capability.ExtendedSecurity; nt.packInt(srvCapabs); // Return the current server date/time, and timezone offset long srvTime = NTTime.toNTTime(new java.util.Date(System.currentTimeMillis())); nt.packLong(srvTime); nt.packWord(getServer().getGlobalConfiguration().getTimeZoneOffset()); // Encryption key length if ( auth.hasExtendedSecurity() == false || extendedSecurity == false) nt.packByte(auth.getEncryptionKeyLength()); else nt.packByte(0); smbPkt.setFlags(getDefaultFlags()); smbPkt.setFlags2(getDefaultFlags2()); smbPkt.setTreeId(0); smbPkt.setUserId(0); // Let the authenticator pack any remaining fields in the negotiate response try { // Pack the remaining negotiate response fields auth.generateNegotiateResponse(this, smbPkt, extendedSecurity); } catch (AuthenticatorException ex) { // Log the error if ( Debug.EnableError && hasDebug(DBG_NEGOTIATE)) debugPrintln("Negotiate error - " + ex.getMessage()); // Close the session setState(SMBSrvSessionState.NBHANGUP); return; } } // Make sure the response flag is set if ( smbPkt.isResponse() == false) smbPkt.setFlags( smbPkt.getFlags() + SMBPacket.FLG_RESPONSE); // Send the negotiate response m_pktHandler.writePacket( smbPkt, smbPkt.getLength()); // Check if the negotiated SMB dialect supports the session setup command, if not then // bypass the session setup phase. if ( m_dialect == -1) setState(SMBSrvSessionState.NBHANGUP); else if ( Dialect.DialectSupportsCommand(m_dialect, PacketType.SessionSetupAndX)) setState(SMBSrvSessionState.SMBSESSSETUP); else setState(SMBSrvSessionState.SMBSESSION); // If a dialect was selected inform the server that the session has been opened if ( m_dialect != -1) getSMBServer().sessionOpened(this); } /** * Start the SMB server session in a seperate thread. */ public void run() { // Server packet allocated from the pool SMBSrvPacket smbPkt = null; try { // Debug if ( Debug.EnableInfo && hasDebug(SMBSrvSession.DBG_NEGOTIATE)) debugPrintln("Server session started"); // The server session loops until the NetBIOS hangup state is set. while (m_state != SMBSrvSessionState.NBHANGUP) { try { // Wait for a request packet smbPkt = m_pktHandler.readPacket(); } catch (SocketTimeoutException ex) { // Debug if ( Debug.EnableInfo && hasDebug(SMBSrvSession.DBG_SOCKET)) debugPrintln("Socket read timed out, closing session"); // Socket read timed out hangupSession("Socket read timeout"); // Clear the request packet smbPkt = null; } catch (IOException ex) { // Check if there is no more data, the other side has dropped the connection hangupSession("Remote disconnect"); // Clear the request packet smbPkt = null; } // Check for an empty packet if ( smbPkt == null) continue; // Check the packet signature if we are in an SMB state if ( m_state > SMBSrvSessionState.NBSESSREQ) { // Check for an SMB2 packet signature if ( smbPkt.isSMB2()) { // Debug if ( Debug.EnableInfo && hasDebug(DBG_PKTTYPE)) debugPrintln("SMB2 request received, ignoring"); continue; } // Check the packet signature if ( smbPkt.checkPacketSignature() == false) { // Debug if ( Debug.EnableInfo && hasDebug(DBG_PKTTYPE)) debugPrintln("Invalid SMB packet signature received, packet ignored"); continue; } } // Queue the request to the thread pool for processing getThreadPool().queueRequest( new CIFSThreadRequest( this, smbPkt)); smbPkt = null; } // Cleanup the session, then close the session/socket closeSession(); } catch (Exception ex) { // Output the exception details if ( isShutdown() == false) { debugPrintln("Closing session due to exception"); debugPrintln(ex); Debug.println( ex); } } catch (Throwable ex) { debugPrintln("Closing session due to throwable"); debugPrintln(ex.toString()); Debug.println( ex); } finally { // Release any allocated request packet back to the pool if ( smbPkt != null) getSMBServer().getPacketPool().releasePacket( smbPkt); } } /** * Handle a session message, receive all data and run the SMB protocol handler. * * @param smbPkt SMBSrvPacket */ protected final void runHandler( SMBSrvPacket smbPkt) throws IOException, SMBSrvException, TooManyConnectionsException { // DEBUG if ( Debug.EnableInfo && hasDebug(DBG_PKTTYPE)) debugPrintln("Rx packet type - " + smbPkt.getPacketTypeString() + ", SID=" + smbPkt.getSID()); // Call the protocol handler if ( m_handler.runProtocol( smbPkt) == false) { // The sessions protocol handler did not process the request, return an unsupported // SMB error status. sendErrorResponseSMB( smbPkt, SMBStatus.SRVNotSupported, SMBStatus.ErrSrv); } // Commit/rollback any active transaction if ( hasTransaction()) endTransaction(); // Check if there are any pending asynchronous response packets while (hasAsynchResponse()) { // Remove the current asynchronous response SMB packet and send to the client SMBSrvPacket asynchPkt = removeFirstAsynchResponse(); sendResponseSMB(asynchPkt, asynchPkt.getLength()); // DEBUG if ( Debug.EnableInfo && hasDebug(DBG_NOTIFY)) { debugPrintln("Sent queued asynch response type=" + asynchPkt.getPacketTypeString() + ", mid=" + asynchPkt.getMultiplexId() + ", pid=" + asynchPkt.getProcessId()); debugPrintln(" Async queue len=" + m_asynchQueue.size()); } } } /** * Process a CIFS request packet * * @param smbPkt SMBSrvPacket */ public final void processPacket( SMBSrvPacket smbPkt) { // Process the packet, if valid if ( smbPkt != null) { try { // Start/end times if timing debug is enabled long startTime = 0L; long endTime = 0L; // Debug if ( Debug.EnableInfo && hasDebug(DBG_TIMING)) startTime = System.currentTimeMillis(); // Debug if ( Debug.EnableInfo && hasDebug(DBG_RXDATA)) { debugPrintln("Rx Data len=" + smbPkt.getReceivedLength()); HexDump.Dump( smbPkt.getBuffer(), smbPkt.getReceivedLength(), 0, Debug.getDebugInterface()); } // Process the received packet if ( smbPkt.getReceivedLength() > 0) { switch (m_state) { // NetBIOS session request pending case SMBSrvSessionState.NBSESSREQ: procNetBIOSSessionRequest( smbPkt); break; // SMB dialect negotiate case SMBSrvSessionState.SMBNEGOTIATE: procSMBNegotiate( smbPkt); break; // SMB session setup case SMBSrvSessionState.SMBSESSSETUP: m_handler.runProtocol( smbPkt); break; // SMB session main request processing case SMBSrvSessionState.SMBSESSION: // Run the main protocol handler runHandler( smbPkt); // Debug if ( Debug.EnableInfo && hasDebug(DBG_TIMING)) { endTime = System.currentTimeMillis(); long duration = endTime - startTime; if ( duration > 20) debugPrintln("Processed packet " + PacketType.getCommandName( smbPkt.getCommand()) + " (0x" + Integer.toHexString( smbPkt.getCommand()) + ") in " + duration + "ms, MID=" + smbPkt.getMultiplexId()); } break; } } // Release the current packet back to the pool getPacketPool().releasePacket( smbPkt); smbPkt = null; // DEBUG if ( Debug.EnableInfo && hasDebug(DBG_PKTSTATS)) Debug.println("[SMB] Packet pool stats: " + getPacketPool()); } catch (SocketException ex) { // DEBUG if ( Debug.EnableInfo && hasDebug(DBG_STATE)) debugPrintln("Socket closed by remote client"); } catch (Exception ex) { // Output the exception details if ( isShutdown() == false) { debugPrintln("Closing session due to exception"); debugPrintln(ex); Debug.println( ex); } } catch (Throwable ex) { debugPrintln("Closing session due to throwable"); debugPrintln(ex.toString()); Debug.println( ex); } finally { // Release any allocated request packet back to the pool if ( smbPkt != null) getSMBServer().getPacketPool().releasePacket( smbPkt); } } // Check if there is an active transaction if ( hasTransaction()) { // DEBUG if ( Debug.EnableError) debugPrintln("** Active transaction after packet processing, cleaning up **"); // Close the active transaction endTransaction(); } // Check if the session has been closed, either cleanly or due to an exception if ( m_state == SMBSrvSessionState.NBHANGUP) { // Cleanup the session, make sure all resources are released cleanupSession(); // Debug if ( Debug.EnableInfo && hasDebug(DBG_STATE)) debugPrintln("Server session closed"); // Close the session closeSocket(); // Notify the server that the session has closed getSMBServer().sessionClosed(this); } // Clear any user context if ( hasClientInformation()) getSMBServer().getCifsAuthenticator().setCurrentUser( null); } /** * Send an SMB response * * @param pkt SMBSrvPacket * @exception IOException */ public final void sendResponseSMB(SMBSrvPacket pkt) throws IOException { sendResponseSMB(pkt, pkt.getLength()); } /** * Send an SMB response * * @param pkt SMBSrvPacket * @param len int * @exception IOException */ public synchronized final void sendResponseSMB(SMBSrvPacket pkt, int len) throws IOException { // Commit/rollback any active transactions before sending the response if ( hasTransaction()) { // DEBUG long startTime = 0L; if ( Debug.EnableInfo && hasDebug( DBG_BENCHMARK)) startTime = System.currentTimeMillis(); // Commit or rollback the transaction endTransaction(); // DEBUG if ( Debug.EnableInfo && hasDebug( DBG_BENCHMARK)) { long elapsedTime = System.currentTimeMillis() - startTime; if ( elapsedTime > 5L) Debug.println("Benchmark: End transaction took " + elapsedTime + "ms"); } } // Make sure the response flag is set if ( pkt.isResponse() == false) pkt.setFlags(pkt.getFlags() + SMBSrvPacket.FLG_RESPONSE); // Add default flags/flags2 values pkt.setFlags(pkt.getFlags() | getDefaultFlags()); // Mask out certain flags that the client may have sent int flags2 = pkt.getFlags2() | getDefaultFlags2(); flags2 &= ~(SMBSrvPacket.FLG2_EXTENDEDATTRIB + SMBSrvPacket.FLG2_DFSRESOLVE + SMBSrvPacket.FLG2_SECURITYSIGS); pkt.setFlags2(flags2); // Send the response packet m_pktHandler.writePacket(pkt, len); m_pktHandler.flushPacket(); // Debug if ( Debug.EnableInfo && hasDebug(DBG_TXDATA)) { debugPrintln("Tx Data len=" + len); HexDump.Dump(pkt.getBuffer(), 64, 0, Debug.getDebugInterface()); } } /** * Send a success response SMB * * @param smbPkt SMBSrvPacket * @exception IOException If a network error occurs */ public final void sendSuccessResponseSMB( SMBSrvPacket smbPkt) throws IOException { // Make sure the response flag is set if ( smbPkt.isResponse() == false) smbPkt.setFlags( smbPkt.getFlags() + SMBSrvPacket.FLG_RESPONSE); // Add default flags/flags2 values smbPkt.setFlags( smbPkt.getFlags() | getDefaultFlags()); smbPkt.setFlags2( smbPkt.getFlags2() | getDefaultFlags2()); // Clear the parameter and byte counts smbPkt.setParameterCount(0); smbPkt.setByteCount(0); if ( smbPkt.isLongErrorCode()) smbPkt.setLongErrorCode(SMBStatus.NTSuccess); else { smbPkt.setErrorClass(SMBStatus.Success); smbPkt.setErrorCode(SMBStatus.Success); } // Return the success response to the client sendResponseSMB( smbPkt, smbPkt.getLength()); // Debug if ( Debug.EnableInfo && hasDebug(DBG_TXDATA)) debugPrintln("Tx Data len=" + smbPkt.getLength() + ", success SMB"); } /** * Send an error response SMB. The returned code depends on the client long error code flag * setting. * * @param smbPkt SMBSrvPacket * @param ntCode 32bit error code * @param stdCode Standard error code * @param stdClass Standard error class */ public final void sendErrorResponseSMB( SMBSrvPacket smbPkt, int ntCode, int stdCode, int stdClass) throws java.io.IOException { // Check if long error codes are required by the client if ( smbPkt.isLongErrorCode()) { // Return the long/NT status code if ( ntCode != -1) { // Use the 32bit NT error code sendErrorResponseSMB( smbPkt, ntCode, SMBStatus.NTErr); } else { // Use the DOS error code sendErrorResponseSMB( smbPkt, stdCode, stdClass); } } else { // Return the standard/DOS error code sendErrorResponseSMB( smbPkt, stdCode, stdClass); } } /** * Send an error response SMB. * * @param smbPkt SMBSrvPacket * @param errCode int Error code. * @param errClass int Error class. */ public final void sendErrorResponseSMB( SMBSrvPacket smbPkt, int errCode, int errClass) throws java.io.IOException { // Make sure the response flag is set if ( smbPkt.isResponse() == false) smbPkt.setFlags( smbPkt.getFlags() + SMBSrvPacket.FLG_RESPONSE); // Set the error code and error class in the response packet smbPkt.setParameterCount(0); smbPkt.setByteCount(0); // Add default flags/flags2 values smbPkt.setFlags( smbPkt.getFlags() | getDefaultFlags()); smbPkt.setFlags2( smbPkt.getFlags2() | getDefaultFlags2()); // Check if the error is a NT 32bit error status if ( errClass == SMBStatus.NTErr) { // Enable the long error status flag if ( smbPkt.isLongErrorCode() == false) smbPkt.setFlags2( smbPkt.getFlags2() + SMBSrvPacket.FLG2_LONGERRORCODE); // Set the NT status code smbPkt.setLongErrorCode(errCode); } else { // Disable the long error status flag if ( smbPkt.isLongErrorCode() == true) smbPkt.setFlags2(smbPkt.getFlags2() - SMBSrvPacket.FLG2_LONGERRORCODE); // Set the error status/class smbPkt.setErrorCode(errCode); smbPkt.setErrorClass(errClass); } // Return the error response to the client sendResponseSMB( smbPkt, smbPkt.getLength()); // Debug if ( Debug.EnableInfo && hasDebug(DBG_ERROR)) debugPrintln("Error : Cmd = " + smbPkt.getPacketTypeString() + " - " + SMBErrorText.ErrorString(errClass, errCode)); } /** * Send, or queue, an asynchronous response SMB * * @param pkt SMBSrvPacket * @param len int * @return true if the packet was sent, or false if it was queued * @exception IOException If an I/O error occurs */ public final boolean sendAsynchResponseSMB(SMBSrvPacket pkt, int len) throws IOException { // Check if there is pending data from the client boolean sts = false; if ( m_pktHandler.availableBytes() == 0) { // Send the asynchronous response immediately sendResponseSMB(pkt, len); m_pktHandler.flushPacket(); // Indicate that the SMB response has been sent sts = true; } else { // Queue the packet to send out when current SMB requests have been processed queueAsynchResponseSMB(pkt); } // Return the sent/queued status return sts; } /** * Queue an asynchronous response SMB for sending when current SMB requests have been processed. * * @param pkt SMBSrvPacket */ protected final synchronized void queueAsynchResponseSMB(SMBSrvPacket pkt) { // Check if the asynchronous response queue has been allocated if ( m_asynchQueue == null) { // Allocate the asynchronous response queue m_asynchQueue = new Vector<SMBSrvPacket>(); } // Add the SMB response packet to the queue m_asynchQueue.add(pkt); } /** * Check if there are any asynchronous requests queued * * @return boolean */ protected final synchronized boolean hasAsynchResponse() { // Check if the queue is valid if ( m_asynchQueue != null && m_asynchQueue.size() > 0) return true; return false; } /** * Remove an asynchronous response packet from the head of the list * * @return SMBSrvPacket */ protected final synchronized SMBSrvPacket removeFirstAsynchResponse() { // Check if there are asynchronous response packets queued if ( m_asynchQueue == null || m_asynchQueue.size() == 0) return null; // Return the SMB packet from the head of the queue SMBSrvPacket pkt = (SMBSrvPacket) m_asynchQueue.elementAt(0); m_asynchQueue.removeElementAt(0); return pkt; } /** * Find the notify request with the specified ids * * @param mid int * @param tid int * @param uid int * @param pid int * @return NotifyRequest */ public final NotifyRequest findNotifyRequest(int mid, int tid, int uid, int pid) { // Check if the local notify list is valid if ( m_notifyList == null) return null; // Find the matching notify request return m_notifyList.findRequest(mid, tid, uid, pid); } /** * Find an existing notify request for the specified directory and filter * * @param dir NetworkFile * @param filter int * @param watchTree boolean * @return NotifyRequest */ public final NotifyRequest findNotifyRequest(NetworkFile dir, int filter, boolean watchTree) { // Check if the local notify list is valid if ( m_notifyList == null) return null; // Find the matching notify request return m_notifyList.findRequest(dir, filter, watchTree); } /** * Add a change notification request * * @param req NotifyRequest * @param ctx DiskDeviceContext */ public final void addNotifyRequest(NotifyRequest req, DiskDeviceContext ctx) { // Check if the local notify list has been allocated if ( m_notifyList == null) m_notifyList = new NotifyRequestList(); // Add the request to the local list and the shares global list m_notifyList.addRequest(req); ctx.addNotifyRequest(req); } /** * Remove a change notification request * * @param req NotifyRequest */ public final void removeNotifyRequest(NotifyRequest req) { // Check if the local notify list has been allocated if ( m_notifyList == null) return; // Remove the request from the local list and the shares global list m_notifyList.removeRequest(req); if ( req.getDiskContext() != null) req.getDiskContext().removeNotifyRequest(req); } /** * Return the server session object factory * * @return SrvSessionFactory */ public static final SrvSessionFactory getFactory() { return m_factory; } /** * Set the server session object factory * * @param factory SrvSessionFactory */ public static final void setFactory(SrvSessionFactory factory) { m_factory = factory; } /** * Create a new server session instance * * @param handler PacketHandler * @param server SMBServer * @param sessId int * @return SMBSrvSession */ public static final SMBSrvSession createSession(PacketHandler handler, SMBServer server, int sessId) { return m_factory.createSession(handler, server, sessId); } /** * Check if an asynchronous read is queued/being processed by this session * * @return boolean */ public final boolean hasReadInProgress() { return m_asyncRead; } /** * Set/clear the read in progress flag * * @param inProgress boolean */ public final void setReadInProgress(boolean inProgress) { m_asyncRead = inProgress; } }