| /* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, 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 re-enabling |
| * adapters using NetBIOSEnableAdapter. |
| */ |
| typedef struct _NetBIOSAdapter |
| { |
| BOOL enabled; |
| BOOL shuttingDown; |
| LONG 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 = HeapReAlloc(GetProcessHeap(), |
| HEAP_ZERO_MEMORY, gNBTable.table, |
| newSize * sizeof(NetBIOSAdapter)); |
| else |
| gNBTable.table = 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); |
| gNBTable.cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": NetBIOSAdapterTable.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); |
| gNBTable.cs.DebugInfo->Spare[0] = 0; |
| DeleteCriticalSection(&gNBTable.cs); |
| HeapFree(GetProcessHeap(), 0, gNBTable.table); |
| } |
| |
| BOOL NetBIOSRegisterTransport(ULONG id, NetBIOSTransport *transport) |
| { |
| BOOL ret; |
| |
| TRACE(": transport 0x%08x, p %p\n", id, transport); |
| if (!transport) |
| ret = FALSE; |
| else if (gNumTransports >= sizeof(gTransports) / sizeof(gTransports[0])) |
| { |
| FIXME("Too many transports %d\n", gNumTransports + 1); |
| ret = FALSE; |
| } |
| else |
| { |
| UCHAR i; |
| |
| ret = FALSE; |
| for (i = 0; !ret && i < gNumTransports; i++) |
| { |
| if (gTransports[i].id == id) |
| { |
| WARN("Replacing NetBIOS transport ID %d\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%08x, ifIndex 0x%08x, data %p\n", transport, ifIndex, |
| data); |
| for (i = 0; i < gNumTransports && gTransports[i].id != transport; i++) |
| ; |
| if ((i < gNumTransports) && gTransports[i].id == transport) |
| { |
| NetBIOSTransport *transportPtr = &gTransports[i].transport; |
| |
| TRACE(": found transport %p for id 0x%08x\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].cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": NetBIOSAdapterTable.NetBIOSAdapter.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); |
| adapter->cs.DebugInfo->Spare[0] = 0; |
| 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%08x, 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 = HeapReAlloc(GetProcessHeap(), |
| HEAP_ZERO_MEMORY, adapter->sessions, sessionsLen * |
| sizeof(NetBIOSSession)); |
| else |
| newSessions = 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 ret; |
| } |
| |
| static UCHAR nbHangup(NetBIOSAdapter *adapter, const NCB *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(const NCB *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 = 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 if (cmd == NCBADDNAME) |
| { |
| FIXME("NCBADDNAME: stub, returning success\n"); |
| ncb->ncb_retcode = ncb->ncb_cmd_cplt = ret = NRC_GOODRET; |
| } |
| else |
| { |
| NetBIOSAdapter *adapter; |
| |
| /* Apps not specifically written for WinNT won't do an NCBENUM first, |
| * so make sure the table has been enumerated at least once |
| */ |
| if (!gNBTable.enumerated) |
| nbInternalEnum(); |
| 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; |
| } |
| |
| DWORD WINAPI NetpNetBiosStatusToApiStatus(DWORD nrc) |
| { |
| DWORD ret; |
| |
| switch (nrc) |
| { |
| case NRC_GOODRET: |
| ret = NO_ERROR; |
| break; |
| case NRC_NORES: |
| ret = NERR_NoNetworkResource; |
| break; |
| case NRC_DUPNAME: |
| ret = NERR_AlreadyExists; |
| break; |
| case NRC_NAMTFUL: |
| ret = NERR_TooManyNames; |
| break; |
| case NRC_ACTSES: |
| ret = NERR_DeleteLater; |
| break; |
| case NRC_REMTFUL: |
| ret = ERROR_REM_NOT_LIST; |
| break; |
| case NRC_NOCALL: |
| ret = NERR_NameNotFound; |
| break; |
| case NRC_NOWILD: |
| ret = ERROR_INVALID_PARAMETER; |
| break; |
| case NRC_INUSE: |
| ret = NERR_DuplicateName; |
| break; |
| case NRC_NAMERR: |
| ret = ERROR_INVALID_PARAMETER; |
| break; |
| case NRC_NAMCONF: |
| ret = NERR_DuplicateName; |
| break; |
| default: |
| ret = NERR_NetworkError; |
| } |
| return ret; |
| } |