/*
* 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.netbios.server;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import org.alfresco.jlan.debug.Debug;
import org.alfresco.jlan.netbios.NetBIOSName;
import org.alfresco.jlan.netbios.NetBIOSNameList;
import org.alfresco.jlan.netbios.NetBIOSPacket;
import org.alfresco.jlan.netbios.NetworkSettings;
import org.alfresco.jlan.netbios.RFCNetBIOSProtocol;
import org.alfresco.jlan.server.NetworkServer;
import org.alfresco.jlan.server.ServerListener;
import org.alfresco.jlan.server.Version;
import org.alfresco.jlan.server.config.ConfigId;
import org.alfresco.jlan.server.config.ConfigurationListener;
import org.alfresco.jlan.server.config.InvalidConfigurationException;
import org.alfresco.jlan.server.config.ServerConfiguration;
import org.alfresco.jlan.smb.server.CIFSConfigSection;
import org.alfresco.jlan.util.HexDump;
import org.alfresco.jlan.util.StringList;
/**
* NetBIOS name server class.
*
* @author gkspencer
*/
public class NetBIOSNameServer extends NetworkServer implements Runnable, ConfigurationListener {
// Server version
private static final String ServerVersion = Version.NetBIOSServerVersion;
// Various NetBIOS packet sizes
public static final int AddNameSize = 256;
public static final int DeleteNameSize = 256;
public static final int RefreshNameSize = 256;
// Add name thread broadcast interval and retry count
private static final int AddNameInterval = 2000; // ms between transmits
private static final int AddNameRetries = 5; // number of broadcasts
private static final int AddNameWINSInterval = 250; // ms between requests when using WINS
// Delete name interval and retry count
private static final int DeleteNameInterval = 200; // ms between transmits
private static final int DeleteNameRetries = 1; // number of broadcasts
// Refresh name retry count
public static final int RefreshNameRetries = 2; // number of broadcasts
// NetBIOS flags
public static final int GroupName = 0x8000;
// Default time to live value for names registered by this server, in seconds
public static final int DefaultTTL = 10800; // 3 hours
// Name refresh thread wakeup interval
public static final long NameRefreshWakeupInterval = 180000L; // 3 minutes
// CIFS server configuration section
private CIFSConfigSection m_cifsConfig;
// Name transaction id
private static int m_tranId;
// NetBIOS name service datagram socket
private DatagramSocket m_socket;
// Shutdown flag
private boolean m_shutdown;
// Local address to bind the name server to
private InetAddress m_bindAddress;
// Broadcast address, if not using WINS
private InetAddress m_bcastAddr;
// Port/socket to bind to
private int m_port = RFCNetBIOSProtocol.NAME_PORT;
// WINS server addresses
private InetAddress m_winsPrimary;
private InetAddress m_winsSecondary;
// Local add name listener list
private Vector<AddNameListener> m_addListeners;
// Local name query listener list
private Vector<QueryNameListener> m_queryListeners;
// Remote name add listener list
private Vector<RemoteNameListener> m_remoteListeners;
// Local NetBIOS name table
private Vector<NetBIOSName> m_localNames;
// Remote NetBIOS name table
private Hashtable<NetBIOSName, byte[]> m_remoteNames;
// List of active add name requests
private Vector<NetBIOSRequest> m_reqList;
// NetBIOS request handler and name refresh threads
private NetBIOSRequestHandler m_reqHandler;
private NetBIOSNameRefresh m_refreshThread;
// Server thread
private Thread m_srvThread;
// NetBIOS request handler thread inner class
class NetBIOSRequestHandler extends Thread {
// Shutdown request flag
private boolean m_hshutdown = false;
/**
* Default constructor
*/
public NetBIOSRequestHandler() {
setDaemon(true);
setName("NetBIOSRequest");
}
/**
* Shutdown the request handler thread
*/
public final void shutdownRequest() {
m_hshutdown = true;
synchronized ( m_reqList) {
m_reqList.notify();
}
}
/**
* Main thread code
*/
public void run() {
// Loop until shutdown requested
while ( m_hshutdown == false) {
try {
// Wait for something to do
NetBIOSRequest req = null;
synchronized ( m_reqList) {
// Check if there are any requests in the queue
if ( m_reqList.size() == 0) {
// Debug
if ( Debug.EnableInfo && hasDebug())
Debug.println("NetBIOS handler waiting for request ...");
// Wait for some work ...
m_reqList.wait();
}
// Remove a request from the queue
if ( m_reqList.size() > 0)
req = m_reqList.elementAt(0);
else if ( m_hshutdown == true)
break;
}
// Get the request retry count, for WINS only send one request
int reqRetry = req.getRetryCount();
if ( hasPrimaryWINSServer())
reqRetry = 1;
// Process the request
boolean txsts = true;
int retry = 0;
while ( req.hasErrorStatus() == false && retry++ < reqRetry) {
// Debug
if (Debug.EnableInfo && hasDebug())
Debug.println("NetBIOS handler, processing " + req);
// Process the request
switch( req.isType()) {
// Add name request
case NetBIOSRequest.AddName:
// Check if a WINS server is configured
if ( hasPrimaryWINSServer())
txsts = sendAddName(req, getPrimaryWINSServer(), false);
else
txsts = sendAddName(req, getBroadcastAddress(), true);
break;
// Delete name request
case NetBIOSRequest.DeleteName:
// Check if a WINS server is configured
if ( hasPrimaryWINSServer())
txsts = sendDeleteName(req, getPrimaryWINSServer(), false);
else
txsts = sendDeleteName(req, getBroadcastAddress(), true);
break;
// Refresh name request
case NetBIOSRequest.RefreshName:
// Check if a WINS server is configured
if ( hasPrimaryWINSServer())
txsts = sendRefreshName(req, getPrimaryWINSServer(), false);
else
txsts = sendRefreshName(req, getBroadcastAddress(), true);
break;
}
// Check if the request was successful
if ( txsts == true && req.getRetryInterval() > 0) {
// Sleep for a while
sleep(req.getRetryInterval());
}
}
// Check if the request was successful
if ( req.hasErrorStatus() == false) {
// Debug
if (Debug.EnableInfo && hasDebug())
Debug.println("NetBIOS handler successful, " + req);
// Update the name record
NetBIOSName nbName = req.getNetBIOSName();
switch ( req.isType()) {
// Add name request
case NetBIOSRequest.AddName:
// Add the name to the list of local names
if ( m_localNames.contains(nbName) == false)
m_localNames.add(nbName);
// Update the expiry time for the name
nbName.setExpiryTime(System.currentTimeMillis() + ( nbName.getTimeToLive() * 1000L));
// Inform listeners that the request was successful
fireAddNameEvent(nbName, NetBIOSNameEvent.ADD_SUCCESS);
break;
// Delete name request
case NetBIOSRequest.DeleteName:
// Remove the name from the list of local names
m_localNames.removeElement(req.getNetBIOSName());
break;
// Refresh name registration request
case NetBIOSRequest.RefreshName:
// Update the expiry time for the name
nbName.setExpiryTime(System.currentTimeMillis() + ( nbName.getTimeToLive() * 1000L));
break;
}
}
else {
// Error occurred
switch( req.isType()) {
// Add name request
case NetBIOSRequest.AddName:
// Remove the name from the local name list
m_localNames.removeElement(req.getNetBIOSName());
break;
}
}
// Remove the request from the queue
synchronized( m_reqList) {
m_reqList.removeElementAt(0);
}
}
catch (InterruptedException ex) {
}
// Check if the request handler has been shutdown
if ( m_hshutdown == true)
break;
}
}
/**
* Send an add name request
*
* @param req NetBIOSRequest
* @param dest InetAddress
* @param bcast boolean
* @return boolean
*/
private final boolean sendAddName(NetBIOSRequest req, InetAddress dest, boolean bcast) {
try {
// Allocate a buffer for the add name NetBIOS packet
byte[] buf = new byte[AddNameSize];
NetBIOSPacket addPkt = new NetBIOSPacket(buf);
// Build an add name packet for each IP address
for ( int i = 0; i < req.getNetBIOSName().numberOfAddresses(); i++) {
// Build an add name request for the current IP address
int len = addPkt.buildAddNameRequest(req.getNetBIOSName(), i, req.getTransactionId());
if ( bcast == false)
addPkt.setFlags(0);
// Allocate the datagram packet, using the add name buffer
DatagramPacket pkt = new DatagramPacket(buf, len, dest, getPort());
// Send the add name request
if ( m_socket != null)
m_socket.send(pkt);
// Debug
if (Debug.EnableInfo && hasDebug())
Debug.println(" Add name " + ( bcast ? "broadcast" : "WINS") + ", " + req);
}
}
catch (IOException ex) {
fireAddNameEvent(req.getNetBIOSName(), NetBIOSNameEvent.ADD_IOERROR);
req.setErrorStatus(true);
return false;
}
// Add name broadcast successful
return true;
}
/**
* Send a refresh name request
*
* @param req NetBIOSRequest
* @param dest InetAddress
* @param bcast boolean
* @return boolean
*/
private final boolean sendRefreshName(NetBIOSRequest req, InetAddress dest, boolean bcast) {
try {
// Allocate a buffer for the refresh name NetBIOS packet
byte[] buf = new byte[RefreshNameSize];
NetBIOSPacket refreshPkt = new NetBIOSPacket(buf);
// Build a refresh name packet for each IP address
for ( int i = 0; i < req.getNetBIOSName().numberOfAddresses(); i++) {
// Build a refresh name request for the current IP address
int len = refreshPkt.buildRefreshNameRequest(req.getNetBIOSName(), i, req.getTransactionId());
if ( bcast == false)
refreshPkt.setFlags(0);
// Allocate the datagram packet, using the refresh name buffer
DatagramPacket pkt = new DatagramPacket(buf, len, dest, getPort());
// Send the refresh name request
if ( m_socket != null)
m_socket.send(pkt);
// Debug
if (Debug.EnableInfo && hasDebug())
Debug.println(" Refresh name " + ( bcast ? "broadcast" : "WINS") + ", " + req);
}
}
catch (IOException ex) {
req.setErrorStatus(true);
return false;
}
// Add name broadcast successful
return true;
}
/**
* Send a delete name request via a network broadcast
*
* @param req NetBIOSRequest
* @param dest InetAddress
* @param bcast boolean
* @return boolean
*/
private final boolean sendDeleteName(NetBIOSRequest req, InetAddress dest, boolean bcast) {
try {
// Allocate a buffer for the delete name NetBIOS packet
byte[] buf = new byte[DeleteNameSize];
NetBIOSPacket delPkt = new NetBIOSPacket(buf);
// Build a delete name packet for each IP address
for ( int i = 0; i < req.getNetBIOSName().numberOfAddresses(); i++) {
// Build an add name request for the current IP address
int len = delPkt.buildDeleteNameRequest(req.getNetBIOSName(), i, req.getTransactionId());
if ( bcast == false)
delPkt.setFlags(0);
// Allocate the datagram packet, using the add name buffer
DatagramPacket pkt = new DatagramPacket(buf, len, dest, getPort());
// Send the add name request
if ( m_socket != null)
m_socket.send(pkt);
// Debug
if (Debug.EnableInfo && hasDebug())
Debug.println(" Delete name " + ( bcast ? "broadcast" : "WINS") + ", " + req);
}
}
catch (IOException ex) {
req.setErrorStatus(true);
return false;
}
// Delete name broadcast successful
return true;
}
};
// NetBIOS name refresh thread inner class
class NetBIOSNameRefresh extends Thread {
// Shutdown request flag
private boolean m_hshutdown = false;
/**
* Default constructor
*/
public NetBIOSNameRefresh() {
setDaemon(true);
setName("NetBIOSRefresh");
}
/**
* Shutdown the name refresh thread
*/
public final void shutdownRequest() {
m_hshutdown = true;
// Wakeup the thread
this.interrupt();
}
/**
* Main thread code
*/
public void run() {
// Loop for ever
while ( m_hshutdown == false) {
try {
// Sleep for a while
sleep(NameRefreshWakeupInterval);
// Check if there is a shutdown pending
if ( m_hshutdown == true)
break;
// Debug
if ( Debug.EnableInfo && hasDebug())
Debug.println("NetBIOS name refresh wakeup ...");
// Check if there are any registered names that will expire in the next interval
synchronized ( m_localNames) {
// Get the current time plus the wakeup interval
long expireTime = System.currentTimeMillis() + NameRefreshWakeupInterval;
// Loop through the local name list
for ( int i = 0; i < m_localNames.size(); i++) {
// Get a name from the list
NetBIOSName nbName = (NetBIOSName) m_localNames.elementAt(i);
// Check if the name has expired, or will expire before the next wakeup event
if ( nbName.getExpiryTime() < expireTime) {
// Debug
if ( Debug.EnableInfo && hasDebug())
Debug.println("Queuing name refresh for " + nbName);
// Queue a refresh request for the NetBIOS name
NetBIOSRequest nbReq = new NetBIOSRequest(NetBIOSRequest.RefreshName, nbName, getNextTransactionId());
nbReq.setRetryCount(RefreshNameRetries);
// Queue the request
synchronized (m_reqList) {
// Add the request to the list
m_reqList.addElement(nbReq);
// Wakeup the processing thread
m_reqList.notify();
}
}
}
}
}
catch (Exception ex) {
// Debug
if ( Debug.EnableError && hasDebug()) {
Debug.println("NetBIOS Name refresh thread exception");
Debug.println(ex);
}
}
}
}
};
/**
* Default constructor
*
* @param config ServerConfiguration
* @exception SocketException If a network setup error occurs
*/
public NetBIOSNameServer(ServerConfiguration config)
throws SocketException {
super("NetBIOS", config);
// Perform common constructor code
commonConstructor();
}
/**
* Common constructor code
*
* @exception SocketException If a network setup error occurs
*/
private final void commonConstructor()
throws SocketException {
// Add the NetBIOS name server as a configuration change listener of the server configuration
getConfiguration().addListener(this);
// Set the server version
setVersion(ServerVersion);
// Find the CIFS server configuration
m_cifsConfig = (CIFSConfigSection) getConfiguration().getConfigSection( CIFSConfigSection.SectionName);
if ( m_cifsConfig != null) {
// Allocate the local and remote name tables
m_localNames = new Vector<NetBIOSName>();
m_remoteNames = new Hashtable<NetBIOSName, byte[]>();
// Check if NetBIOS name server debug output is enabled
if ( getCIFSConfiguration().hasNetBIOSDebug())
setDebug(true);
// Set the local address to bind the server to, and server port
setBindAddress(getCIFSConfiguration().getNetBIOSBindAddress());
setServerPort(getCIFSConfiguration().getNameServerPort());
// Copy the WINS server addresses, if set
setPrimaryWINSServer(getCIFSConfiguration().getPrimaryWINSServer());
setSecondaryWINSServer(getCIFSConfiguration().getSecondaryWINSServer());
// Check if WINS is not enabled, use broadcasts instead
if ( hasPrimaryWINSServer() == false) {
try {
m_bcastAddr = InetAddress.getByName(getCIFSConfiguration().getBroadcastMask());
}
catch (Exception ex) {
}
}
}
else
setEnabled( false);
}
/**
* Return the local address the server binds to, or null if all local addresses
* are used.
*
* @return java.net.InetAddress
*/
public final InetAddress getBindAddress() {
return m_bindAddress;
}
/**
* Return the next available transaction id for outgoing NetBIOS packets.
*
* @return int
*/
protected final synchronized int getNextTransactionId() {
return m_tranId++;
}
/**
* Return the port/socket that the server is bound to.
*
* @return int
*/
public final int getPort() {
return m_port;
}
/**
* Determine if the server binds to a particulat local address, or all addresses
*
* @return boolean
*/
public final boolean hasBindAddress() {
return m_bindAddress != null ? true : false;
}
/**
* Return the CIFS server configuration
*
* @return CIFSConfigSection
*/
private final CIFSConfigSection getCIFSConfiguration() {
return m_cifsConfig;
}
/**
* Return the remote name table
*
* @return Hashtable
*/
public final Hashtable getNameTable() {
return m_remoteNames;
}
/**
* Return the broadcast address, if WINS is disabled
*
* @return InetAddress
*/
public final InetAddress getBroadcastAddress() {
return m_bcastAddr;
}
/**
* Determine if the primary WINS server address has been set
*
* @return boolean
*/
public final boolean hasPrimaryWINSServer() {
return m_winsPrimary != null ? true : false;
}
/**
* Return the primary WINS server address
*
* @return InetAddress
*/
public final InetAddress getPrimaryWINSServer() {
return m_winsPrimary;
}
/**
* Determine if the secondary WINS server address has been set
*
* @return boolean
*/
public final boolean hasSecondaryWINSServer() {
return m_winsSecondary != null ? true : false;
}
/**
* Return the secondary WINS server address
*
* @return InetAddress
*/
public final InetAddress getSecondaryWINSServer() {
return m_winsSecondary;
}
/**
* Add a NetBIOS name.
*
* @param name NetBIOS name to be added
* @exception java.io.IOException I/O error occurred.
*/
public final synchronized void AddName(NetBIOSName name)
throws IOException {
// Check if the NetBIOS name socket has been initialized
if (m_socket == null)
throw new IOException("NetBIOS name socket not initialized");
// Create an add name request and add to the request list
NetBIOSRequest nbReq = new NetBIOSRequest(NetBIOSRequest.AddName, name, getNextTransactionId());
// Set the retry interval
if ( hasPrimaryWINSServer())
nbReq.setRetryInterval(AddNameWINSInterval);
else
nbReq.setRetryInterval(AddNameInterval);
// Add the name to the local name list
m_localNames.addElement(name);
// Queue the request
synchronized (m_reqList) {
// Add the request to the list
m_reqList.addElement(nbReq);
// Wakeup the processing thread
m_reqList.notify();
}
}
/**
* Delete a NetBIOS name.
*
* @param name NetBIOS name to be deleted
* @exception java.io.IOException I/O error occurred.
*/
public final synchronized void DeleteName(NetBIOSName name)
throws IOException {
// Check if the NetBIOS name socket has been initialized
if (m_socket == null)
throw new IOException("NetBIOS name socket not initialized");
// Create a delete name request and add to the request list
NetBIOSRequest nbReq = new NetBIOSRequest(NetBIOSRequest.DeleteName, name, getNextTransactionId(), DeleteNameRetries);
nbReq.setRetryInterval(DeleteNameInterval);
synchronized (m_reqList) {
// Add the request to the list
m_reqList.addElement(nbReq);
// Wakeup the processing thread
m_reqList.notify();
}
}
/**
* Add a local add name listener to the NetBIOS name server.
*
* @param l AddNameListener
*/
public final synchronized void addAddNameListener(AddNameListener l) {
// Check if the add name listener list is allocated
if (m_addListeners == null)
m_addListeners = new Vector<AddNameListener>();
m_addListeners.add(l);
}
/**
* Add a query name listener to the NetBIOS name server.
*
* @param l QueryNameListener
*/
public final synchronized void addQueryListener(QueryNameListener l) {
// Check if the query name listener list is allocated
if (m_queryListeners == null)
m_queryListeners = new Vector<QueryNameListener>();
m_queryListeners.add(l);
}
/**
* Add a remote name listener to the NetBIOS name server.
*
* @param l RemoteNameListener
*/
public final synchronized void addRemoteListener(RemoteNameListener l) {
// Check if the remote name listener list is allocated
if (m_remoteListeners == null)
m_remoteListeners = new Vector<RemoteNameListener>();
m_remoteListeners.add(l);
}
/**
* Trigger an add name event to all registered listeners.
*
* @param name NetBIOSName
* @param sts int
*/
protected final synchronized void fireAddNameEvent(NetBIOSName name, int sts) {
// Check if there are any listeners
if (m_addListeners == null || m_addListeners.size() == 0)
return;
// Create a NetBIOS name event
NetBIOSNameEvent evt = new NetBIOSNameEvent(name, sts);
// Inform all registered listeners
for (int i = 0; i < m_addListeners.size(); i++) {
AddNameListener addListener = m_addListeners.elementAt(i);
addListener.netbiosNameAdded(evt);
}
}
/**
* Trigger an query name event to all registered listeners.
*
* @param name NetBIOSName
* @param addr InetAddress
*/
protected final synchronized void fireQueryNameEvent(NetBIOSName name, InetAddress addr) {
// Check if there are any listeners
if (m_queryListeners == null || m_queryListeners.size() == 0)
return;
// Create a NetBIOS name event
NetBIOSNameEvent evt = new NetBIOSNameEvent(name, NetBIOSNameEvent.QUERY_NAME);
// Inform all registered listeners
for (int i = 0; i < m_queryListeners.size(); i++) {
QueryNameListener queryListener = m_queryListeners.elementAt(i);
queryListener.netbiosNameQuery(evt, addr);
}
}
/**
* Trigger a name register event to all registered listeners.
*
* @param name NetBIOSName
* @param addr InetAddress
*/
protected final synchronized void fireNameRegisterEvent(NetBIOSName name, InetAddress addr) {
// Check if there are any listeners
if (m_remoteListeners == null || m_remoteListeners.size() == 0)
return;
// Create a NetBIOS name event
NetBIOSNameEvent evt = new NetBIOSNameEvent(name, NetBIOSNameEvent.REGISTER_NAME);
// Inform all registered listeners
for (int i = 0; i < m_remoteListeners.size(); i++) {
RemoteNameListener nameListener = m_remoteListeners.elementAt(i);
nameListener.netbiosAddRemoteName(evt,addr);
}
}
/**
* Trigger a name release event to all registered listeners.
*
* @param name NetBIOSName
* @param addr InetAddress
*/
protected final synchronized void fireNameReleaseEvent(NetBIOSName name, InetAddress addr) {
// Check if there are any listeners
if (m_remoteListeners == null || m_remoteListeners.size() == 0)
return;
// Create a NetBIOS name event
NetBIOSNameEvent evt = new NetBIOSNameEvent(name, NetBIOSNameEvent.REGISTER_NAME);
// Inform all registered listeners
for (int i = 0; i < m_remoteListeners.size(); i++) {
RemoteNameListener nameListener = m_remoteListeners.elementAt(i);
nameListener.netbiosReleaseRemoteName(evt,addr);
}
}
/**
* Open the server socket
*
* @exception SocketException
*/
private void openSocket()
throws java.net.SocketException {
// Check if the server should bind to a particular local address, or all addresses
if (hasBindAddress())
m_socket = new DatagramSocket(getPort(), m_bindAddress);
else
m_socket = new DatagramSocket(getPort());
}
/**
* Process a NetBIOS name query.
*
* @param pkt NetBIOSPacket
* @param fromAddr InetAddress
* @param fromPort int
*/
protected final void processNameQuery(NetBIOSPacket pkt, InetAddress fromAddr, int fromPort) {
// Check that the name query packet is valid
if (pkt.getQuestionCount() != 1)
return;
// Get the name that is being queried
String searchName = pkt.getQuestionName();
char nameType = searchName.charAt(15);
int len = 0;
while (len <= 14 && searchName.charAt(len) != ' ' && searchName.charAt(len) != 0)
len++;
searchName = searchName.substring(0, len);
// Check if this is an adapter status request
if ( searchName.equals( NetBIOSName.AdapterStatusName)) {
// Process the adapter status request
processAdapterStatus( pkt, fromAddr, fromPort);
return;
}
// Debug
if (Debug.EnableInfo && hasDebug())
Debug.println("%% Query name=" + searchName + ", type=" + NetBIOSName.TypeAsString(nameType) + ", len=" + len);
// Search for the name in the local name table
Enumeration<NetBIOSName> enm = m_localNames.elements();
NetBIOSName nbName = null;
boolean foundName = false;
while (enm.hasMoreElements() && foundName == false) {
// Get the current NetBIOS name item from the local name table
nbName = enm.nextElement();
// Debug
if (Debug.EnableInfo && hasDebug())
Debug.println("NetBIOS Name - " + nbName.getName() + ", len=" + nbName.getName().length() + ",type=" + NetBIOSName.TypeAsString(nbName.getType()));
// Check if the name matches the query name
if (nbName.getType() == nameType && nbName.getName().compareTo(searchName) == 0)
foundName = true;
}
// Check if we found a matching name
if (foundName == true) {
// Debug
if (Debug.EnableInfo && hasDebug())
Debug.println("%% Found name " + searchName + " in local name table : " + nbName.toString());
// Build the name query response
int pktLen = pkt.buildNameQueryResponse(nbName);
// Debug
if (Debug.EnableInfo && hasDebug()) {
Debug.println("%% NetBIOS Reply to " + fromAddr.getHostAddress() + " :-");
pkt.DumpPacket(false);
}
// Send the reply packet
try {
// Send the name query reply
sendPacket(pkt, pktLen, fromAddr, fromPort);
}
catch (java.io.IOException ex) {
Debug.println(ex);
}
// Inform listeners of the name query
fireQueryNameEvent(nbName, fromAddr);
}
else {
// Debug
if (Debug.EnableInfo && hasDebug())
Debug.println("%% Failed to find match for name " + searchName);
}
}
/**
* Process a NetBIOS name register request.
*
* @param pkt NetBIOSPacket
* @param fromAddr InetAddress
* @param fromPort int
*/
protected final void processNameRegister(NetBIOSPacket pkt, InetAddress fromAddr, int fromPort) {
// Check that the name register packet is valid
if (pkt.getQuestionCount() != 1)
return;
// Get the name that is being registered
String regName = pkt.getQuestionName();
char nameType = regName.charAt(15);
int len = 0;
while (len <= 14 && regName.charAt(len) != ' ')
len++;
regName = regName.substring(0, len);
// Debug
if (Debug.EnableInfo && hasDebug())
Debug.println("%% Register name=" + regName + ", type=" + NetBIOSName.TypeAsString(nameType) + ", len=" + len);
// Create a NetBIOS name for the host
byte[] hostIP = fromAddr.getAddress();
NetBIOSName nbName = new NetBIOSName(regName, nameType, false, hostIP);
// Add the name to the remote host name table
m_remoteNames.put(nbName, hostIP);
// Inform listeners that a new remote name has been added
fireNameRegisterEvent(nbName,fromAddr);
// Debug
if ( Debug.EnableInfo && hasDebug())
Debug.println("%% Added remote name " + nbName.toString() + " to remote names table");
}
/**
* Process a NetBIOS name release.
*
* @param pkt NetBIOSPacket
* @param fromAddr InetAddress
* @param fromPort int
*/
protected final void processNameRelease(NetBIOSPacket pkt, InetAddress fromAddr, int fromPort) {
// Check that the name release packet is valid
if (pkt.getQuestionCount() != 1)
return;
// Get the name that is being released
String regName = pkt.getQuestionName();
char nameType = regName.charAt(15);
int len = 0;
while (len <= 14 && regName.charAt(len) != ' ')
len++;
regName = regName.substring(0, len);
// Debug
if (Debug.EnableInfo && hasDebug())
Debug.println("%% Release name=" + regName + ", type=" + NetBIOSName.TypeAsString(nameType) + ", len=" + len);
// Create a NetBIOS name for the host
byte[] hostIP = fromAddr.getAddress();
NetBIOSName nbName = new NetBIOSName(regName, nameType, false, hostIP);
// Remove the name from the remote host name table
m_remoteNames.remove(nbName);
// Inform listeners that a remote name has been released
fireNameReleaseEvent(nbName, fromAddr);
// Debug
if ( Debug.EnableInfo && hasDebug())
Debug.println("%% Released remote name " + nbName.toString() + " from remote names table");
}
/**
* Process a NetBIOS query response.
*
* @param pkt NetBIOSPacket
* @param fromAddr InetAddress
* @param fromPort int
*/
protected final void processQueryResponse(NetBIOSPacket pkt, InetAddress fromAddr, int fromPort) {
}
/**
* Process a NetBIOS name register response.
*
* @param pkt NetBIOSPacket
* @param fromAddr InetAddress
* @param fromPort int
*/
protected final void processRegisterResponse(NetBIOSPacket pkt, InetAddress fromAddr, int fromPort) {
// Check if there are any reply name details
if ( pkt.getAnswerCount() == 0)
return;
// Get the details from the response packet
int tranId = pkt.getTransactionId();
// Find the matching request
NetBIOSRequest req = findRequest(tranId);
if ( req == null)
return;
// Get the error code from the response
int errCode = pkt.getResultCode();
if ( errCode != 0) {
// Mark the request error
req.setErrorStatus(true);
// Get the name details
String regName = pkt.getAnswerName();
char nameType = regName.charAt(15);
int len = 0;
while (len <= 14 && regName.charAt(len) != ' ')
len++;
regName = regName.substring(0, len);
// Create a NetBIOS name for the host
byte[] hostIP = fromAddr.getAddress();
NetBIOSName nbName = new NetBIOSName(regName, nameType, false, hostIP);
// Debug
if (Debug.EnableInfo && hasDebug())
Debug.println("%% Negative Name Registration name=" + nbName);
// Inform listeners of the add name failure
fireAddNameEvent(req.getNetBIOSName(), NetBIOSNameEvent.ADD_FAILED);
}
else {
// Debug
if (Debug.EnableInfo && hasDebug())
Debug.println("%% Name Registration Successful name=" + req.getNetBIOSName().getName());
// Inform listeners that the add name was successful
fireAddNameEvent(req.getNetBIOSName(), NetBIOSNameEvent.ADD_SUCCESS);
}
}
/**
* Process a NetBIOS name release response.
*
* @param pkt NetBIOSPacket
* @param fromAddr InetAddress
* @param fromPort int
*/
protected final void processReleaseResponse(NetBIOSPacket pkt, InetAddress fromAddr, int fromPort) {
}
/**
* Process a NetBIOS WACK.
*
* @param pkt NetBIOSPacket
* @param fromAddr InetAddress
* @param fromPort int
*/
protected final void processWack(NetBIOSPacket pkt, InetAddress fromAddr, int fromPort) {
}
/**
* Process an adapter status request
* @param pkt NetBIOSPacket
* @param fromAddr InetAddress
* @param fromPort int
*/
protected final void processAdapterStatus(NetBIOSPacket pkt, InetAddress fromAddr, int fromPort) {
// Debug
if (Debug.EnableInfo && hasDebug())
Debug.println("%% Adapter status request");
// Build the local name list
NetBIOSNameList nameList = new NetBIOSNameList();
for ( int i = 0; i < m_localNames.size(); i++)
nameList.addName( m_localNames.get( i));
// Build the name query response
int pktLen = pkt.buildAdapterStatusResponse( nameList, hasPrimaryWINSServer() ? NetBIOSPacket.NAME_TYPE_PNODE : NetBIOSPacket.NAME_TYPE_BNODE);
// Debug
if (Debug.EnableInfo && hasDebug()) {
Debug.println("%% NetBIOS Reply to " + fromAddr.getHostAddress() + " :-");
pkt.DumpPacket(false);
}
// Send the reply packet
try {
// Send the name query reply
sendPacket(pkt, pktLen, fromAddr, fromPort);
}
catch (java.io.IOException ex) {
Debug.println(ex);
}
}
/**
* Remove a local add name listener from the NetBIOS name server.
*
* @param l AddNameListener
*/
public final synchronized void removeAddNameListener(AddNameListener l) {
// Check if the listener list is valid
if (m_addListeners == null)
return;
m_addListeners.removeElement(l);
}
/**
* Remove a query name listner from the NetBIOS name server.
*
* @param l QueryNameListener
*/
public final synchronized void removeQueryNameListener(QueryNameListener l) {
// Check if the listener list is valid
if (m_queryListeners == null)
return;
m_queryListeners.removeElement(l);
}
/**
* Remove a remote name listener from the NetBIOS name server.
*
* @param l RemoteNameListener
*/
public final synchronized void removeRemoteListener(RemoteNameListener l) {
// Check if the listener list is valid
if (m_remoteListeners == null)
return;
m_remoteListeners.removeElement(l);
}
/**
* Run the NetBIOS name server.
*/
public void run() {
// Initialize the NetBIOS name socket
NetBIOSPacket nbPkt = null;
DatagramPacket pkt = null;
byte[] buf = null;
try {
// Get a list of the local IP addresses
Vector ipList = new Vector();
if ( hasBindAddress()) {
// Use the specified bind address
ipList.addElement(getBindAddress().getAddress());
}
else {
// Get a list of all the local addresses
InetAddress[] addrs = InetAddress.getAllByName(InetAddress.getLocalHost().getHostName());
for ( int i = 0; i < addrs.length; i++) {
// Check for a valid address, filter out '127.0.0.1' and '0.0.0.0' addresses
if ( addrs[i].getHostAddress().equals("127.0.0.1") == false &&
addrs[i].getHostAddress().equals("0.0.0.0") == false)
ipList.addElement(addrs[i].getAddress());
}
// If no valid addresses were found use the network interface list to find the local server address(es)
if ( ipList.size() == 0) {
// Enumerate the network adapter list
Enumeration niEnum = NetworkInterface.getNetworkInterfaces();
if ( niEnum != null) {
while ( niEnum.hasMoreElements()) {
// Get the current network interface
NetworkInterface ni = (NetworkInterface) niEnum.nextElement();
// Enumerate the addresses for the network adapter
Enumeration niAddrs = ni.getInetAddresses();
if ( niAddrs != null)
{
// Check for any valid addresses
while ( niAddrs.hasMoreElements())
{
InetAddress curAddr = (InetAddress) niAddrs.nextElement();
if ( curAddr.getHostAddress().equals("127.0.0.1") == false &&
curAddr.getHostAddress().equals("0.0.0.0") == false)
ipList.add( curAddr.getAddress());
}
}
}
// DEBUG
if ( Debug.EnableInfo && ipList.size() > 0 && hasDebug())
Debug.println("Found " + ipList.size() + " addresses using interface list");
}
}
}
// Initialize the NetBIOS name socket
if (m_socket == null)
openSocket();
// Allocate the NetBIOS request queue, and add the server name/alias name requests
m_reqList = new Vector<NetBIOSRequest>();
// Add the server name requests to the queue
AddName(new NetBIOSName(m_cifsConfig.getServerName(), NetBIOSName.FileServer, false, ipList, DefaultTTL));
AddName(new NetBIOSName(m_cifsConfig.getServerName(), NetBIOSName.WorkStation, false, ipList, DefaultTTL));
if ( getCIFSConfiguration().getDomainName() != null)
AddName(new NetBIOSName(m_cifsConfig.getDomainName(), NetBIOSName.Domain, true, ipList, DefaultTTL));
// Check if the server has alias names configured, if so then also add those names
if ( getCIFSConfiguration().hasAliasNames()) {
// Add the alias names to the network
StringList names = getCIFSConfiguration().getAliasNames();
for ( int i = 0; i < names.numberOfStrings(); i++) {
// Get the current alias name
String alias = (String) names.getStringAt(i);
// Add the name to the network
AddName(new NetBIOSName(alias, NetBIOSName.FileServer, false, ipList, DefaultTTL));
AddName(new NetBIOSName(alias, NetBIOSName.WorkStation, false, ipList, DefaultTTL));
}
}
// Create the request handler thread
m_reqHandler = new NetBIOSRequestHandler();
m_reqHandler.start();
// Create the name refresh thread
m_refreshThread = new NetBIOSNameRefresh();
m_refreshThread.start();
// Allocate a receive buffer, NetBIOS packet and datagram packet
buf = new byte[1024];
nbPkt = new NetBIOSPacket(buf);
pkt = new DatagramPacket(buf, buf.length);
}
catch ( Exception ex) {
if ( Debug.EnableError && hasDebug())
Debug.println("NetBIOSNameServer setup error:" + ex.toString());
// Save the exception and inform listeners of the error
setException(ex);
fireServerEvent(ServerListener.ServerError);
}
// If there are any pending requests in the queue then wakeup the request handler thread
if ( m_reqList != null && m_reqList.size() > 0) {
synchronized ( m_reqList) {
m_reqList.notify();
}
}
// Indicate that the server is active
setActive(true);
fireServerEvent(ServerListener.ServerActive);
// Loop
if ( hasException() == false) {
// Clear the shutdown request flag
m_shutdown = false;
while (m_shutdown == false) {
try {
// Wait for an incoming packet ....
m_socket.receive(pkt);
// Debug
if (Debug.EnableInfo && hasDebug()) {
// Dump the received packet
Debug.println("%% NetBIOS Name Server Rx Datagram from " + pkt.getAddress().getHostAddress() + ", opCode=" + nbPkt.getOpcode());
// Dump the raw packet details
HexDump.Dump(buf, pkt.getLength(), 0, Debug.getDebugInterface());
}
// Check for a zero length datagram
if ( pkt.getLength() == 0)
continue;
// Get the incoming NetBIOS packet opcode
InetAddress fromAddr = pkt.getAddress();
int fromPort = pkt.getPort();
switch (nbPkt.getOpcode()) {
// Name query
case NetBIOSPacket.NAME_QUERY :
processNameQuery(nbPkt, fromAddr, fromPort);
break;
// Name register
case NetBIOSPacket.NAME_REGISTER :
processNameRegister(nbPkt, fromAddr, fromPort);
break;
// Name release
case NetBIOSPacket.NAME_RELEASE :
processNameRelease(nbPkt, fromAddr, fromPort);
break;
// Name register response
case NetBIOSPacket.RESP_REGISTER :
processRegisterResponse(nbPkt, fromAddr, fromPort);
break;
// Name query response
case NetBIOSPacket.RESP_QUERY :
processQueryResponse(nbPkt, fromAddr, fromPort);
break;
// Name release response
case NetBIOSPacket.RESP_RELEASE :
processReleaseResponse(nbPkt, fromAddr, fromPort);
break;
// WACK
case NetBIOSPacket.WACK :
processWack(nbPkt, fromAddr, fromPort);
break;
// Refresh
case NetBIOSPacket.REFRESH:
processNameRegister(nbPkt, fromAddr, fromPort);
break;
// Multi-homed name registration
case NetBIOSPacket.NAME_REGISTER_MULTI:
processNameRegister(nbPkt, fromAddr, fromPort);
break;
// Unknown opcode
default:
if ( Debug.EnableError && hasDebug()) {
int opCode = nbPkt.getOpcode();
Debug.println("Unknown OpCode 0x" + Integer.toHexString(opCode) + ", " + opCode);
}
break;
}
}
catch (Exception ex) {
if ( hasDebug()) {
Debug.println("NetBIOSNameServer error"); Debug.println(ex);
}
// Store the error and inform listeners of the server error. If the server is shutting down we expect a
// socket error as the socket is closed by the shutdown thread and the pending read request generates an
// exception.
if ( m_shutdown == false) {
setException(ex);
fireServerEvent(ServerListener.ServerError);
}
}
}
}
// Indicate that the server is closed
setActive(false);
fireServerEvent(ServerListener.ServerShutdown);
}
/**
* Send a packet via the NetBIOS naming datagram socket.
*
* @param nbpkt NetBIOSPacket
* @param len int
* @exception java.io.IOException The exception description.
*/
protected final void sendPacket(NetBIOSPacket nbpkt, int len)
throws java.io.IOException {
// Allocate the datagram packet, using the add name buffer
DatagramPacket pkt = new DatagramPacket(nbpkt.getBuffer(), len, NetworkSettings.getBroadcastAddress(), getPort());
// Send the datagram packet
m_socket.send(pkt);
}
/**
* Send a packet via the NetBIOS naming datagram socket.
*
* @param nbpkt NetBIOSPacket
* @param len int
* @param replyAddr InetAddress
* @param replyPort int
* @exception java.io.IOException The exception description.
*/
protected final void sendPacket(NetBIOSPacket nbpkt, int len, InetAddress replyAddr, int replyPort)
throws java.io.IOException {
// Allocate the datagram packet, using the add name buffer
DatagramPacket pkt = new DatagramPacket(nbpkt.getBuffer(), len, replyAddr, replyPort);
// Send the datagram packet
m_socket.send(pkt);
}
/**
* Set the local address that the server should bind to
*
* @param addr java.net.InetAddress
*/
public final void setBindAddress(InetAddress addr) {
m_bindAddress = addr;
}
/**
* Set the server port
*
* @param port int
*/
public final void setServerPort(int port) {
m_port = port;
}
/**
* Set the primary WINS server address
*
* @param addr InetAddress
*/
public final void setPrimaryWINSServer(InetAddress addr) {
m_winsPrimary = addr;
}
/**
* Set the secondary WINS server address
*
* @param addr InetAddress
*/
public final void setSecondaryWINSServer(InetAddress addr) {
m_winsSecondary = addr;
}
/**
* Find the NetBIOS request with the specified transation id
*
* @param id int
* @return NetBIOSRequest
*/
private final NetBIOSRequest findRequest(int id) {
// Check if the request list is valid
if ( m_reqList == null)
return null;
// Need to lock access to the request list
NetBIOSRequest req = null;
synchronized ( m_reqList) {
// Search for the required request
int idx = 0;
while ( req == null && idx < m_reqList.size()) {
// Get the current request and check if it is the required request
NetBIOSRequest curReq = (NetBIOSRequest) m_reqList.elementAt(idx++);
if ( curReq.getTransactionId() == id)
req = curReq;
}
}
// Return the request, or null if not found
return req;
}
/**
* Shutdown the NetBIOS name server
*
* @param immediate boolean
*/
public void shutdownServer(boolean immediate) {
// Close the name refresh thread
try {
if ( m_refreshThread != null) {
m_refreshThread.shutdownRequest();
}
}
catch (Exception ex) {
// Debug
if ( Debug.EnableError)
Debug.println(ex);
}
// If the shutdown is not immediate then release all of the names registered by this server
if ( isActive() && immediate == false) {
// Release all local names
for ( int i = 0; i < m_localNames.size(); i++) {
// Get the current name details
NetBIOSName nbName = (NetBIOSName) m_localNames.elementAt(i);
// Queue a delete name request
try {
DeleteName(nbName);
}
catch (IOException ex) {
Debug.println(ex);
}
}
// Wait for the request handler thread to process the delete name requests
while ( m_reqList.size() > 0) {
try {
Thread.sleep(100);
}
catch (InterruptedException ex) {
}
}
}
// Close the request handler thread
try {
// Close the request handler thread
if (m_reqHandler != null) {
m_reqHandler.shutdownRequest();
m_reqHandler.join(1000);
m_reqHandler = null;
}
}
catch (Exception ex) {
// Debug
if ( Debug.EnableError)
Debug.println(ex);
}
// Indicate that the server is closing
m_shutdown = true;
try {
// Close the server socket so that any pending receive is cancelled
if (m_socket != null) {
// Send a dummy packet to release the receive on the datagram socket, then close the socket
// m_socket.send(new DatagramPacket(new byte[0], 0, m_socket.getLocalAddress(), getPort()));
try {
m_socket.close();
}
catch (Exception ex) {
}
m_socket = null;
}
}
catch (Exception ex) {
Debug.println(ex);
}
// Fire a shutdown notification event
fireServerEvent(ServerListener.ServerShutdown);
}
/**
* Start the NetBIOS name server is a seperate thread
*/
public void startServer() {
// Create a seperate thread to run the NetBIOS name server
m_srvThread = new Thread(this);
m_srvThread.setName("NetBIOS Name Server");
m_srvThread.setDaemon(true);
m_srvThread.start();
// Fire a server startup event
fireServerEvent(ServerListener.ServerStartup);
}
/**
* Validate configuration changes that are relevant to the NetBIOS name 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 = StsIgnored;
try {
// Check if the configuration change affects the NetBIOS name server
switch ( id) {
// Server enable/disable
case ConfigId.SMBNetBIOSEnable:
// 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 = StsAccepted;
break;
// Debug enable
case ConfigId.NetBIOSDebugEnable:
// Toggle the debug setting
Boolean b = (Boolean) newVal;
setDebug(b.booleanValue());
sts = StsAccepted;
break;
// Settings that require a restart
case ConfigId.NetBIOSNamePort:
case ConfigId.NetBIOSSessionPort:
case ConfigId.NetBIOSBindAddress:
case ConfigId.SMBBroadcastMask:
sts = StsRestartRequired;
break;
}
}
catch (Exception ex) {
throw new InvalidConfigurationException("NetBIOS Server configuration error", ex);
}
// Return the status
return sts;
}
}