|  | /* Copyright (c) 2003 Juan Lang | 
|  | * | 
|  | * This library is free software; you can redistribute it and/or | 
|  | * modify it under the terms of the GNU Lesser General Public | 
|  | * License as published by the Free Software Foundation; either | 
|  | * version 2.1 of the License, or (at your option) any later version. | 
|  | * | 
|  | * This library 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 | 
|  | * Lesser General Public License for more details. | 
|  | * | 
|  | * You should have received a copy of the GNU Lesser General Public | 
|  | * License along with this library; if not, write to the Free Software | 
|  | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | 
|  | */ | 
|  | #include "config.h" | 
|  | #include "wine/debug.h" | 
|  | #include "nbcmdqueue.h" | 
|  | #include "netbios.h" | 
|  |  | 
|  | WINE_DEFAULT_DEBUG_CHANNEL(netbios); | 
|  |  | 
|  | /* This file provides a NetBIOS emulator that implements the NetBIOS interface, | 
|  | * including thread safety and asynchronous call support.  The protocol | 
|  | * implementation is separate, with blocking (synchronous) functions. | 
|  | */ | 
|  |  | 
|  | #define ADAPTERS_INCR 8 | 
|  | #define DEFAULT_NUM_SESSIONS 16 | 
|  |  | 
|  | typedef struct _NetBIOSTransportTableEntry | 
|  | { | 
|  | ULONG            id; | 
|  | NetBIOSTransport transport; | 
|  | } NetBIOSTransportTableEntry; | 
|  |  | 
|  | typedef struct _NetBIOSSession | 
|  | { | 
|  | BOOL  inUse; | 
|  | UCHAR state; | 
|  | UCHAR local_name[NCBNAMSZ]; | 
|  | UCHAR remote_name[NCBNAMSZ]; | 
|  | void *data; | 
|  | } NetBIOSSession; | 
|  |  | 
|  | /* This struct needs a little explanation, unfortunately.  enabled is only | 
|  | * used by nbInternalEnum (see).  If transport_id is not 0 and transport | 
|  | * is not NULL, the adapter is considered valid.  (transport is a pointer to | 
|  | * an entry in a NetBIOSTransportTableEntry.)  data has data for the callers of | 
|  | * NetBIOSEnumAdapters to be able to see.  The lana is repeated there, even | 
|  | * though I don't use it internally--it's for transports to use reenabling | 
|  | * adapters using NetBIOSEnableAdapter. | 
|  | */ | 
|  | typedef struct _NetBIOSAdapter | 
|  | { | 
|  | BOOL               enabled; | 
|  | BOOL               shuttingDown; | 
|  | ULONG              resetting; | 
|  | ULONG              transport_id; | 
|  | NetBIOSTransport  *transport; | 
|  | NetBIOSAdapterImpl impl; | 
|  | struct NBCmdQueue *cmdQueue; | 
|  | CRITICAL_SECTION   cs; | 
|  | DWORD              sessionsLen; | 
|  | NetBIOSSession    *sessions; | 
|  | } NetBIOSAdapter; | 
|  |  | 
|  | typedef struct _NetBIOSAdapterTable { | 
|  | CRITICAL_SECTION cs; | 
|  | BOOL             enumerated; | 
|  | BOOL             enumerating; | 
|  | UCHAR            tableSize; | 
|  | NetBIOSAdapter  *table; | 
|  | } NetBIOSAdapterTable; | 
|  |  | 
|  | /* Just enough space for NBT right now */ | 
|  | static NetBIOSTransportTableEntry gTransports[1]; | 
|  | static UCHAR gNumTransports = 0; | 
|  | static NetBIOSAdapterTable gNBTable; | 
|  |  | 
|  | static UCHAR nbResizeAdapterTable(UCHAR newSize) | 
|  | { | 
|  | UCHAR ret; | 
|  |  | 
|  | if (gNBTable.table) | 
|  | gNBTable.table = (NetBIOSAdapter *)HeapReAlloc(GetProcessHeap(), | 
|  | HEAP_ZERO_MEMORY, gNBTable.table, | 
|  | newSize * sizeof(NetBIOSAdapter)); | 
|  | else | 
|  | gNBTable.table = (NetBIOSAdapter *)HeapAlloc(GetProcessHeap(), | 
|  | HEAP_ZERO_MEMORY, newSize * sizeof(NetBIOSAdapter)); | 
|  | if (gNBTable.table) | 
|  | { | 
|  | gNBTable.tableSize = newSize; | 
|  | ret = NRC_GOODRET; | 
|  | } | 
|  | else | 
|  | ret = NRC_OSRESNOTAV; | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | void NetBIOSInit(void) | 
|  | { | 
|  | memset(&gNBTable, 0, sizeof(gNBTable)); | 
|  | InitializeCriticalSection(&gNBTable.cs); | 
|  | } | 
|  |  | 
|  | void NetBIOSShutdown(void) | 
|  | { | 
|  | UCHAR i; | 
|  |  | 
|  | EnterCriticalSection(&gNBTable.cs); | 
|  | for (i = 0; i < gNBTable.tableSize; i++) | 
|  | { | 
|  | if (gNBTable.table[i].transport && | 
|  | gNBTable.table[i].transport->cleanupAdapter) | 
|  | gNBTable.table[i].transport->cleanupAdapter( | 
|  | gNBTable.table[i].impl.data); | 
|  | } | 
|  | for (i = 0; i < gNumTransports; i++) | 
|  | if (gTransports[i].transport.cleanup) | 
|  | gTransports[i].transport.cleanup(); | 
|  | LeaveCriticalSection(&gNBTable.cs); | 
|  | DeleteCriticalSection(&gNBTable.cs); | 
|  | HeapFree(GetProcessHeap(), 0, gNBTable.table); | 
|  | } | 
|  |  | 
|  | BOOL NetBIOSRegisterTransport(ULONG id, NetBIOSTransport *transport) | 
|  | { | 
|  | BOOL ret; | 
|  |  | 
|  | TRACE(": transport 0x%08lx, p %p\n", id, transport); | 
|  | if (!transport) | 
|  | ret = FALSE; | 
|  | else if (gNumTransports >= sizeof(gTransports) / sizeof(gTransports[0])) | 
|  | { | 
|  | FIXME("You tried to add %d transports, but I only have space for %d\n", | 
|  | gNumTransports + 1, sizeof(gTransports) / sizeof(gTransports[0])); | 
|  | ret = FALSE; | 
|  | } | 
|  | else | 
|  | { | 
|  | UCHAR i; | 
|  |  | 
|  | ret = FALSE; | 
|  | for (i = 0; !ret && i < gNumTransports; i++) | 
|  | { | 
|  | if (gTransports[i].id == id) | 
|  | { | 
|  | WARN("Replacing NetBIOS transport ID %ld\n", id); | 
|  | memcpy(&gTransports[i].transport, transport, | 
|  | sizeof(NetBIOSTransport)); | 
|  | ret = TRUE; | 
|  | } | 
|  | } | 
|  | if (!ret) | 
|  | { | 
|  | gTransports[gNumTransports].id = id; | 
|  | memcpy(&gTransports[gNumTransports].transport, transport, | 
|  | sizeof(NetBIOSTransport)); | 
|  | gNumTransports++; | 
|  | ret = TRUE; | 
|  | } | 
|  | } | 
|  | TRACE("returning %d\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* In this, I acquire the table lock to make sure no one else is modifying it. | 
|  | * This is _probably_ overkill since it should only be called during the | 
|  | * context of a NetBIOSEnum call, but just to be safe.. | 
|  | */ | 
|  | BOOL NetBIOSRegisterAdapter(ULONG transport, DWORD ifIndex, void *data) | 
|  | { | 
|  | BOOL ret; | 
|  | UCHAR i; | 
|  |  | 
|  | TRACE(": transport 0x%08lx, ifIndex 0x%08lx, data %p\n", transport, ifIndex, | 
|  | data); | 
|  | for (i = 0; i < gNumTransports && gTransports[i].id != transport; i++) | 
|  | ; | 
|  | if (gTransports[i].id == transport) | 
|  | { | 
|  | NetBIOSTransport *transportPtr = &gTransports[i].transport; | 
|  |  | 
|  | TRACE(": found transport %p for id 0x%08lx\n", transportPtr, transport); | 
|  |  | 
|  | EnterCriticalSection(&gNBTable.cs); | 
|  | ret = FALSE; | 
|  | for (i = 0; i < gNBTable.tableSize && | 
|  | gNBTable.table[i].transport != 0; i++) | 
|  | ; | 
|  | if (i == gNBTable.tableSize && gNBTable.tableSize < MAX_LANA + 1) | 
|  | { | 
|  | UCHAR newSize; | 
|  |  | 
|  | if (gNBTable.tableSize < (MAX_LANA + 1) - ADAPTERS_INCR) | 
|  | newSize = gNBTable.tableSize + ADAPTERS_INCR; | 
|  | else | 
|  | newSize = MAX_LANA + 1; | 
|  | nbResizeAdapterTable(newSize); | 
|  | } | 
|  | if (i < gNBTable.tableSize && gNBTable.table[i].transport == 0) | 
|  | { | 
|  | TRACE(": registering as LANA %d\n", i); | 
|  | gNBTable.table[i].transport_id = transport; | 
|  | gNBTable.table[i].transport = transportPtr; | 
|  | gNBTable.table[i].impl.lana = i; | 
|  | gNBTable.table[i].impl.ifIndex = ifIndex; | 
|  | gNBTable.table[i].impl.data = data; | 
|  | gNBTable.table[i].cmdQueue = NBCmdQueueCreate(GetProcessHeap()); | 
|  | InitializeCriticalSection(&gNBTable.table[i].cs); | 
|  | gNBTable.table[i].enabled = TRUE; | 
|  | ret = TRUE; | 
|  | } | 
|  | LeaveCriticalSection(&gNBTable.cs); | 
|  | } | 
|  | else | 
|  | ret = FALSE; | 
|  | TRACE("returning %d\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* In this, I acquire the table lock to make sure no one else is modifying it. | 
|  | * This is _probably_ overkill since it should only be called during the | 
|  | * context of a NetBIOSEnum call, but just to be safe.. | 
|  | */ | 
|  | void NetBIOSEnableAdapter(UCHAR lana) | 
|  | { | 
|  | TRACE(": %d\n", lana); | 
|  | if (lana < gNBTable.tableSize) | 
|  | { | 
|  | EnterCriticalSection(&gNBTable.cs); | 
|  | if (gNBTable.table[lana].transport != 0) | 
|  | gNBTable.table[lana].enabled = TRUE; | 
|  | LeaveCriticalSection(&gNBTable.cs); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void nbShutdownAdapter(NetBIOSAdapter *adapter) | 
|  | { | 
|  | if (adapter) | 
|  | { | 
|  | adapter->shuttingDown = TRUE; | 
|  | NBCmdQueueCancelAll(adapter->cmdQueue); | 
|  | if (adapter->transport->cleanupAdapter) | 
|  | adapter->transport->cleanupAdapter(adapter->impl.data); | 
|  | NBCmdQueueDestroy(adapter->cmdQueue); | 
|  | DeleteCriticalSection(&adapter->cs); | 
|  | memset(adapter, 0, sizeof(NetBIOSAdapter)); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void nbInternalEnum(void) | 
|  | { | 
|  | UCHAR i; | 
|  |  | 
|  | EnterCriticalSection(&gNBTable.cs); | 
|  | TRACE("before mark\n"); | 
|  | /* mark: */ | 
|  | for (i = 0; i < gNBTable.tableSize; i++) | 
|  | if (gNBTable.table[i].enabled && gNBTable.table[i].transport != 0) | 
|  | gNBTable.table[i].enabled = FALSE; | 
|  |  | 
|  | TRACE("marked, before store, %d transports\n", gNumTransports); | 
|  | /* store adapters: */ | 
|  | for (i = 0; i < gNumTransports; i++) | 
|  | if (gTransports[i].transport.enumerate) | 
|  | gTransports[i].transport.enumerate(); | 
|  |  | 
|  | TRACE("before sweep\n"); | 
|  | /* sweep: */ | 
|  | for (i = 0; i < gNBTable.tableSize; i++) | 
|  | if (!gNBTable.table[i].enabled && gNBTable.table[i].transport != 0) | 
|  | nbShutdownAdapter(&gNBTable.table[i]); | 
|  | gNBTable.enumerated = TRUE; | 
|  | LeaveCriticalSection(&gNBTable.cs); | 
|  | } | 
|  |  | 
|  | UCHAR NetBIOSNumAdapters(void) | 
|  | { | 
|  | UCHAR ret, i; | 
|  |  | 
|  | if (!gNBTable.enumerated) | 
|  | nbInternalEnum(); | 
|  | for (i = 0, ret = 0; i < gNBTable.tableSize; i++) | 
|  | if (gNBTable.table[i].transport != 0) | 
|  | ret++; | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | void NetBIOSEnumAdapters(ULONG transport, NetBIOSEnumAdaptersCallback cb, | 
|  | void *closure) | 
|  | { | 
|  | TRACE("transport 0x%08lx, callback %p, closure %p\n", transport, cb, | 
|  | closure); | 
|  | if (cb) | 
|  | { | 
|  | BOOL enumAll = memcmp(&transport, ALL_TRANSPORTS, sizeof(ULONG)) == 0; | 
|  | UCHAR i, numLANAs = 0; | 
|  |  | 
|  | EnterCriticalSection(&gNBTable.cs); | 
|  | if (!gNBTable.enumerating) | 
|  | { | 
|  | gNBTable.enumerating = TRUE; | 
|  | nbInternalEnum(); | 
|  | gNBTable.enumerating = FALSE; | 
|  | } | 
|  | for (i = 0; i < gNBTable.tableSize; i++) | 
|  | if (enumAll || gNBTable.table[i].transport_id == transport) | 
|  | numLANAs++; | 
|  | if (numLANAs > 0) | 
|  | { | 
|  | UCHAR lanaIndex = 0; | 
|  |  | 
|  | for (i = 0; i < gNBTable.tableSize; i++) | 
|  | if (gNBTable.table[i].transport_id != 0 && | 
|  | (enumAll || gNBTable.table[i].transport_id == transport)) | 
|  | cb(numLANAs, lanaIndex++, gNBTable.table[i].transport_id, | 
|  | &gNBTable.table[i].impl, closure); | 
|  | } | 
|  | LeaveCriticalSection(&gNBTable.cs); | 
|  | } | 
|  | } | 
|  |  | 
|  | static NetBIOSAdapter *nbGetAdapter(UCHAR lana) | 
|  | { | 
|  | NetBIOSAdapter *ret = NULL; | 
|  |  | 
|  | TRACE(": lana %d, num allocated adapters %d\n", lana, gNBTable.tableSize); | 
|  | if (lana < gNBTable.tableSize && gNBTable.table[lana].transport_id != 0 | 
|  | && gNBTable.table[lana].transport) | 
|  | ret = &gNBTable.table[lana]; | 
|  | TRACE("returning %p\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static UCHAR nbEnum(PNCB ncb) | 
|  | { | 
|  | PLANA_ENUM lanas = (PLANA_ENUM)ncb->ncb_buffer; | 
|  | UCHAR i, ret; | 
|  |  | 
|  | TRACE(": ncb %p\n", ncb); | 
|  |  | 
|  | if (!lanas) | 
|  | ret = NRC_BUFLEN; | 
|  | else if (ncb->ncb_length < sizeof(LANA_ENUM)) | 
|  | ret = NRC_BUFLEN; | 
|  | else | 
|  | { | 
|  | nbInternalEnum(); | 
|  | lanas->length = 0; | 
|  | for (i = 0; i < gNBTable.tableSize; i++) | 
|  | if (gNBTable.table[i].transport) | 
|  | { | 
|  | lanas->length++; | 
|  | lanas->lana[i] = i; | 
|  | } | 
|  | ret = NRC_GOODRET; | 
|  | } | 
|  | TRACE("returning 0x%02x\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static UCHAR nbInternalHangup(NetBIOSAdapter *adapter, NetBIOSSession *session); | 
|  |  | 
|  | static UCHAR nbCancel(NetBIOSAdapter *adapter, PNCB ncb) | 
|  | { | 
|  | UCHAR ret; | 
|  |  | 
|  | TRACE(": adapter %p, ncb %p\n", adapter, ncb); | 
|  |  | 
|  | if (!adapter) return NRC_BRIDGE; | 
|  | if (!ncb) return NRC_INVADDRESS; | 
|  |  | 
|  | switch (ncb->ncb_command & 0x7f) | 
|  | { | 
|  | case NCBCANCEL: | 
|  | case NCBADDNAME: | 
|  | case NCBADDGRNAME: | 
|  | case NCBDELNAME: | 
|  | case NCBRESET: | 
|  | case NCBSSTAT: | 
|  | ret = NRC_CANCEL; | 
|  | break; | 
|  |  | 
|  | /* NCBCALL, NCBCHAINSEND/NCBSEND, NCBHANGUP all close the associated | 
|  | * session if cancelled */ | 
|  | case NCBCALL: | 
|  | case NCBSEND: | 
|  | case NCBCHAINSEND: | 
|  | case NCBSENDNA: | 
|  | case NCBCHAINSENDNA: | 
|  | case NCBHANGUP: | 
|  | { | 
|  | if (ncb->ncb_lsn >= adapter->sessionsLen) | 
|  | ret = NRC_SNUMOUT; | 
|  | else if (!adapter->sessions[ncb->ncb_lsn].inUse) | 
|  | ret = NRC_SNUMOUT; | 
|  | else | 
|  | { | 
|  | ret = NBCmdQueueCancel(adapter->cmdQueue, ncb); | 
|  | if (ret == NRC_CMDCAN || ret == NRC_CANOCCR) | 
|  | nbInternalHangup(adapter, &adapter->sessions[ncb->ncb_lsn]); | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | default: | 
|  | ret = NBCmdQueueCancel(adapter->cmdQueue, ncb); | 
|  | } | 
|  | TRACE("returning 0x%02x\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* Resizes adapter to contain space for at least sessionsLen sessions. | 
|  | * If allocating more space for sessions, sets the adapter's sessionsLen to | 
|  | * sessionsLen.  If the adapter's sessionsLen was already at least sessionsLen, | 
|  | * does nothing.  Does not modify existing sessions.  Assumes the adapter is | 
|  | * locked. | 
|  | * Returns NRC_GOODRET on success, and something else on failure. | 
|  | */ | 
|  | static UCHAR nbResizeAdapter(NetBIOSAdapter *adapter, UCHAR sessionsLen) | 
|  | { | 
|  | UCHAR ret = NRC_GOODRET; | 
|  |  | 
|  | if (adapter && adapter->sessionsLen < sessionsLen) | 
|  | { | 
|  | NetBIOSSession *newSessions; | 
|  |  | 
|  | if (adapter->sessions) | 
|  | newSessions = (NetBIOSSession *)HeapReAlloc(GetProcessHeap(), | 
|  | HEAP_ZERO_MEMORY, adapter->sessions, sessionsLen * | 
|  | sizeof(NetBIOSSession)); | 
|  | else | 
|  | newSessions = (NetBIOSSession *)HeapAlloc(GetProcessHeap(), | 
|  | HEAP_ZERO_MEMORY, sessionsLen * sizeof(NetBIOSSession)); | 
|  | if (newSessions) | 
|  | { | 
|  | adapter->sessions = newSessions; | 
|  | adapter->sessionsLen = sessionsLen; | 
|  | } | 
|  | else | 
|  | ret = NRC_OSRESNOTAV; | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static UCHAR nbReset(NetBIOSAdapter *adapter, PNCB ncb) | 
|  | { | 
|  | UCHAR ret; | 
|  |  | 
|  | TRACE(": adapter %p, ncb %p\n", adapter, ncb); | 
|  |  | 
|  | if (!adapter) return NRC_BRIDGE; | 
|  | if (!ncb) return NRC_INVADDRESS; | 
|  |  | 
|  | if (InterlockedIncrement(&adapter->resetting) == 1) | 
|  | { | 
|  | UCHAR i, resizeTo; | 
|  |  | 
|  | NBCmdQueueCancelAll(adapter->cmdQueue); | 
|  |  | 
|  | EnterCriticalSection(&adapter->cs); | 
|  | for (i = 0; i < adapter->sessionsLen; i++) | 
|  | if (adapter->sessions[i].inUse) | 
|  | nbInternalHangup(adapter, &adapter->sessions[i]); | 
|  | if (!ncb->ncb_lsn) | 
|  | resizeTo = ncb->ncb_callname[0] == 0 ? DEFAULT_NUM_SESSIONS : | 
|  | ncb->ncb_callname[0]; | 
|  | else if (adapter->sessionsLen == 0) | 
|  | resizeTo = DEFAULT_NUM_SESSIONS; | 
|  | else | 
|  | resizeTo = 0; | 
|  | if (resizeTo > 0) | 
|  | ret = nbResizeAdapter(adapter, resizeTo); | 
|  | else | 
|  | ret = NRC_GOODRET; | 
|  | LeaveCriticalSection(&adapter->cs); | 
|  | } | 
|  | else | 
|  | ret = NRC_TOOMANY; | 
|  | InterlockedDecrement(&adapter->resetting); | 
|  | TRACE("returning 0x%02x\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static UCHAR nbSStat(NetBIOSAdapter *adapter, PNCB ncb) | 
|  | { | 
|  | UCHAR ret, i, spaceFor; | 
|  | PSESSION_HEADER sstat; | 
|  |  | 
|  | TRACE(": adapter %p, NCB %p\n", adapter, ncb); | 
|  |  | 
|  | if (!adapter) return NRC_BADDR; | 
|  | if (adapter->sessionsLen == 0) return NRC_ENVNOTDEF; | 
|  | if (!ncb) return NRC_INVADDRESS; | 
|  | if (!ncb->ncb_buffer) return NRC_BADDR; | 
|  | if (ncb->ncb_length < sizeof(SESSION_HEADER)) return NRC_BUFLEN; | 
|  |  | 
|  | sstat = (PSESSION_HEADER)ncb->ncb_buffer; | 
|  | ret = NRC_GOODRET; | 
|  | memset(sstat, 0, sizeof(SESSION_HEADER)); | 
|  | spaceFor = (ncb->ncb_length - sizeof(SESSION_HEADER)) / | 
|  | sizeof(SESSION_BUFFER); | 
|  | EnterCriticalSection(&adapter->cs); | 
|  | for (i = 0; ret == NRC_GOODRET && i < adapter->sessionsLen; i++) | 
|  | { | 
|  | if (adapter->sessions[i].inUse && (ncb->ncb_name[0] == '*' || | 
|  | !memcmp(ncb->ncb_name, adapter->sessions[i].local_name, NCBNAMSZ))) | 
|  | { | 
|  | if (sstat->num_sess < spaceFor) | 
|  | { | 
|  | PSESSION_BUFFER buf; | 
|  |  | 
|  | buf = (PSESSION_BUFFER)((PUCHAR)sstat + sizeof(SESSION_HEADER) | 
|  | + sstat->num_sess * sizeof(SESSION_BUFFER)); | 
|  | buf->lsn = i; | 
|  | buf->state = adapter->sessions[i].state; | 
|  | memcpy(buf->local_name, adapter->sessions[i].local_name, | 
|  | NCBNAMSZ); | 
|  | memcpy(buf->remote_name, adapter->sessions[i].remote_name, | 
|  | NCBNAMSZ); | 
|  | buf->rcvs_outstanding = buf->sends_outstanding = 0; | 
|  | sstat->num_sess++; | 
|  | } | 
|  | else | 
|  | ret = NRC_BUFLEN; | 
|  | } | 
|  | } | 
|  | LeaveCriticalSection(&adapter->cs); | 
|  |  | 
|  | TRACE("returning 0x%02x\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static UCHAR nbCall(NetBIOSAdapter *adapter, PNCB ncb) | 
|  | { | 
|  | UCHAR ret, i; | 
|  |  | 
|  | TRACE(": adapter %p, NCB %p\n", adapter, ncb); | 
|  |  | 
|  | if (!adapter) return NRC_BRIDGE; | 
|  | if (adapter->sessionsLen == 0) return NRC_ENVNOTDEF; | 
|  | if (!adapter->transport->call) return NRC_ILLCMD; | 
|  | if (!ncb) return NRC_INVADDRESS; | 
|  |  | 
|  | EnterCriticalSection(&adapter->cs); | 
|  | for (i = 0; i < adapter->sessionsLen && adapter->sessions[i].inUse; i++) | 
|  | ; | 
|  | if (i < adapter->sessionsLen) | 
|  | { | 
|  | adapter->sessions[i].inUse = TRUE; | 
|  | adapter->sessions[i].state = CALL_PENDING; | 
|  | memcpy(adapter->sessions[i].local_name, ncb->ncb_name, NCBNAMSZ); | 
|  | memcpy(adapter->sessions[i].remote_name, ncb->ncb_callname, NCBNAMSZ); | 
|  | ret = NRC_GOODRET; | 
|  | } | 
|  | else | 
|  | ret = NRC_LOCTFUL; | 
|  | LeaveCriticalSection(&adapter->cs); | 
|  |  | 
|  | if (ret == NRC_GOODRET) | 
|  | { | 
|  | ret = adapter->transport->call(adapter->impl.data, ncb, | 
|  | &adapter->sessions[i].data); | 
|  | if (ret == NRC_GOODRET) | 
|  | { | 
|  | ncb->ncb_lsn = i; | 
|  | adapter->sessions[i].state = SESSION_ESTABLISHED; | 
|  | } | 
|  | else | 
|  | { | 
|  | adapter->sessions[i].inUse = FALSE; | 
|  | adapter->sessions[i].state = 0; | 
|  | } | 
|  | } | 
|  | TRACE("returning 0x%02x\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static UCHAR nbSend(NetBIOSAdapter *adapter, PNCB ncb) | 
|  | { | 
|  | UCHAR ret; | 
|  | NetBIOSSession *session; | 
|  |  | 
|  | if (!adapter) return NRC_BRIDGE; | 
|  | if (!adapter->transport->send) return NRC_ILLCMD; | 
|  | if (!ncb) return NRC_INVADDRESS; | 
|  | if (ncb->ncb_lsn >= adapter->sessionsLen) return NRC_SNUMOUT; | 
|  | if (!adapter->sessions[ncb->ncb_lsn].inUse) return NRC_SNUMOUT; | 
|  | if (!ncb->ncb_buffer) return NRC_BADDR; | 
|  |  | 
|  | session = &adapter->sessions[ncb->ncb_lsn]; | 
|  | if (session->state != SESSION_ESTABLISHED) | 
|  | ret = NRC_SNUMOUT; | 
|  | else | 
|  | ret = adapter->transport->send(adapter->impl.data, session->data, ncb); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static UCHAR nbRecv(NetBIOSAdapter *adapter, PNCB ncb) | 
|  | { | 
|  | UCHAR ret; | 
|  | NetBIOSSession *session; | 
|  |  | 
|  | if (!adapter) return NRC_BRIDGE; | 
|  | if (!adapter->transport->recv) return NRC_ILLCMD; | 
|  | if (!ncb) return NRC_INVADDRESS; | 
|  | if (ncb->ncb_lsn >= adapter->sessionsLen) return NRC_SNUMOUT; | 
|  | if (!adapter->sessions[ncb->ncb_lsn].inUse) return NRC_SNUMOUT; | 
|  | if (!ncb->ncb_buffer) return NRC_BADDR; | 
|  |  | 
|  | session = &adapter->sessions[ncb->ncb_lsn]; | 
|  | if (session->state != SESSION_ESTABLISHED) | 
|  | ret = NRC_SNUMOUT; | 
|  | else | 
|  | ret = adapter->transport->recv(adapter->impl.data, session->data, ncb); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static UCHAR nbInternalHangup(NetBIOSAdapter *adapter, NetBIOSSession *session) | 
|  | { | 
|  | UCHAR ret; | 
|  |  | 
|  | if (!adapter) return NRC_BRIDGE; | 
|  | if (!session) return NRC_SNUMOUT; | 
|  |  | 
|  | if (adapter->transport->hangup) | 
|  | ret = adapter->transport->hangup(adapter->impl.data, session->data); | 
|  | else | 
|  | ret = NRC_ILLCMD; | 
|  | EnterCriticalSection(&adapter->cs); | 
|  | memset(session, 0, sizeof(NetBIOSSession)); | 
|  | LeaveCriticalSection(&adapter->cs); | 
|  | return NRC_GOODRET; | 
|  | } | 
|  |  | 
|  | static UCHAR nbHangup(NetBIOSAdapter *adapter, PNCB ncb) | 
|  | { | 
|  | UCHAR ret; | 
|  | NetBIOSSession *session; | 
|  |  | 
|  | if (!adapter) return NRC_BRIDGE; | 
|  | if (!ncb) return NRC_INVADDRESS; | 
|  | if (ncb->ncb_lsn >= adapter->sessionsLen) return NRC_SNUMOUT; | 
|  | if (!adapter->sessions[ncb->ncb_lsn].inUse) return NRC_SNUMOUT; | 
|  |  | 
|  | session = &adapter->sessions[ncb->ncb_lsn]; | 
|  | if (session->state != SESSION_ESTABLISHED) | 
|  | ret = NRC_SNUMOUT; | 
|  | else | 
|  | { | 
|  | session->state = HANGUP_PENDING; | 
|  | ret = nbInternalHangup(adapter, session); | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | void NetBIOSHangupSession(PNCB ncb) | 
|  | { | 
|  | NetBIOSAdapter *adapter; | 
|  |  | 
|  | if (!ncb) return; | 
|  |  | 
|  | adapter = nbGetAdapter(ncb->ncb_lana_num); | 
|  | if (adapter) | 
|  | { | 
|  | if (ncb->ncb_lsn < adapter->sessionsLen && | 
|  | adapter->sessions[ncb->ncb_lsn].inUse) | 
|  | nbHangup(adapter, ncb); | 
|  | } | 
|  | } | 
|  |  | 
|  | static UCHAR nbAStat(NetBIOSAdapter *adapter, PNCB ncb) | 
|  | { | 
|  | UCHAR ret; | 
|  |  | 
|  | if (!adapter) return NRC_BRIDGE; | 
|  | if (!adapter->transport->astat) return NRC_ILLCMD; | 
|  | if (!ncb) return NRC_INVADDRESS; | 
|  | if (!ncb->ncb_buffer) return NRC_BADDR; | 
|  | if (ncb->ncb_length < sizeof(ADAPTER_STATUS)) return NRC_BUFLEN; | 
|  |  | 
|  | ret = adapter->transport->astat(adapter->impl.data, ncb); | 
|  | if (ncb->ncb_callname[0] == '*') | 
|  | { | 
|  | PADAPTER_STATUS astat = (PADAPTER_STATUS)ncb->ncb_buffer; | 
|  |  | 
|  | astat->max_sess = astat->max_cfg_sess = adapter->sessionsLen; | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static UCHAR nbDispatch(NetBIOSAdapter *adapter, PNCB ncb) | 
|  | { | 
|  | UCHAR ret, cmd; | 
|  |  | 
|  | TRACE(": adapter %p, ncb %p\n", adapter, ncb); | 
|  |  | 
|  | if (!adapter) return NRC_BRIDGE; | 
|  | if (!ncb) return NRC_INVADDRESS; | 
|  |  | 
|  | cmd = ncb->ncb_command & 0x7f; | 
|  | if (cmd == NCBRESET) | 
|  | ret = nbReset(adapter, ncb); | 
|  | else | 
|  | { | 
|  | ret = NBCmdQueueAdd(adapter->cmdQueue, ncb); | 
|  | if (ret == NRC_GOODRET) | 
|  | { | 
|  | switch (cmd) | 
|  | { | 
|  | case NCBCALL: | 
|  | ret = nbCall(adapter, ncb); | 
|  | break; | 
|  |  | 
|  | /* WinNT doesn't chain sends, it always sends immediately. | 
|  | * Doubt there's any real significance to the NA variants. | 
|  | */ | 
|  | case NCBSEND: | 
|  | case NCBSENDNA: | 
|  | case NCBCHAINSEND: | 
|  | case NCBCHAINSENDNA: | 
|  | ret = nbSend(adapter, ncb); | 
|  | break; | 
|  |  | 
|  | case NCBRECV: | 
|  | ret = nbRecv(adapter, ncb); | 
|  | break; | 
|  |  | 
|  | case NCBHANGUP: | 
|  | ret = nbHangup(adapter, ncb); | 
|  | break; | 
|  |  | 
|  | case NCBASTAT: | 
|  | ret = nbAStat(adapter, ncb); | 
|  | break; | 
|  |  | 
|  | case NCBFINDNAME: | 
|  | if (adapter->transport->findName) | 
|  | ret = adapter->transport->findName(adapter->impl.data, | 
|  | ncb); | 
|  | else | 
|  | ret = NRC_ILLCMD; | 
|  | break; | 
|  |  | 
|  | default: | 
|  | FIXME("(%p): command code 0x%02x\n", ncb, ncb->ncb_command); | 
|  | ret = NRC_ILLCMD; | 
|  | } | 
|  | NBCmdQueueComplete(adapter->cmdQueue, ncb, ret); | 
|  | } | 
|  | } | 
|  | TRACE("returning 0x%02x\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static DWORD WINAPI nbCmdThread(LPVOID lpVoid) | 
|  | { | 
|  | PNCB ncb = (PNCB)lpVoid; | 
|  |  | 
|  | if (ncb) | 
|  | { | 
|  | UCHAR ret; | 
|  | NetBIOSAdapter *adapter = nbGetAdapter(ncb->ncb_lana_num); | 
|  |  | 
|  | if (adapter) | 
|  | ret = nbDispatch(adapter, ncb); | 
|  | else | 
|  | ret = NRC_BRIDGE; | 
|  | ncb->ncb_retcode = ncb->ncb_cmd_cplt = ret; | 
|  | if (ncb->ncb_post) | 
|  | ncb->ncb_post(ncb); | 
|  | else if (ncb->ncb_event) | 
|  | SetEvent(ncb->ncb_event); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | UCHAR WINAPI Netbios(PNCB ncb) | 
|  | { | 
|  | UCHAR ret, cmd; | 
|  |  | 
|  | TRACE("ncb = %p\n", ncb); | 
|  |  | 
|  | if (!ncb) return NRC_INVADDRESS; | 
|  |  | 
|  | TRACE("ncb_command 0x%02x, ncb_lana_num %d, ncb_buffer %p, ncb_length %d\n", | 
|  | ncb->ncb_command, ncb->ncb_lana_num, ncb->ncb_buffer, ncb->ncb_length); | 
|  | cmd = ncb->ncb_command & 0x7f; | 
|  |  | 
|  | if (cmd == NCBENUM) | 
|  | ncb->ncb_retcode = ncb->ncb_cmd_cplt = ret = nbEnum(ncb); | 
|  | else | 
|  | { | 
|  | NetBIOSAdapter *adapter = nbGetAdapter(ncb->ncb_lana_num); | 
|  |  | 
|  | if (!adapter) | 
|  | ret = NRC_BRIDGE; | 
|  | else | 
|  | { | 
|  | if (adapter->shuttingDown) | 
|  | ret = NRC_IFBUSY; | 
|  | else if (adapter->resetting) | 
|  | ret = NRC_TOOMANY; | 
|  | else | 
|  | { | 
|  | /* non-asynch commands first */ | 
|  | if (cmd == NCBCANCEL) | 
|  | ncb->ncb_retcode = ncb->ncb_cmd_cplt = ret = | 
|  | nbCancel(adapter, ncb); | 
|  | else if (cmd == NCBSSTAT) | 
|  | ncb->ncb_retcode = ncb->ncb_cmd_cplt = ret = | 
|  | nbSStat(adapter, ncb); | 
|  | else | 
|  | { | 
|  | if (ncb->ncb_command & ASYNCH) | 
|  | { | 
|  | HANDLE thread = CreateThread(NULL, 0, nbCmdThread, ncb, | 
|  | CREATE_SUSPENDED, NULL); | 
|  |  | 
|  | if (thread != NULL) | 
|  | { | 
|  | ncb->ncb_retcode = ncb->ncb_cmd_cplt = NRC_PENDING; | 
|  | if (ncb->ncb_event) | 
|  | ResetEvent(ncb->ncb_event); | 
|  | ResumeThread(thread); | 
|  | CloseHandle(thread); | 
|  | ret = NRC_GOODRET; | 
|  | } | 
|  | else | 
|  | ncb->ncb_retcode = ncb->ncb_cmd_cplt = ret = | 
|  | NRC_OSRESNOTAV; | 
|  | } | 
|  | else | 
|  | ncb->ncb_retcode = ncb->ncb_cmd_cplt = ret = | 
|  | nbDispatch(adapter, ncb); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | TRACE("returning 0x%02x\n", ret); | 
|  | return ret; | 
|  | } |