kernel32/tests: Fix some test failures on several platforms.
[wine] / dlls / netapi32 / netbios.c
1 /* Copyright (c) 2003 Juan Lang
2  *
3  * This library is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU Lesser General Public
5  * License as published by the Free Software Foundation; either
6  * version 2.1 of the License, or (at your option) any later version.
7  *
8  * This library is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public
14  * License along with this library; if not, write to the Free Software
15  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
16  */
17 #include "config.h"
18 #include "wine/debug.h"
19 #include "nbcmdqueue.h"
20 #include "netbios.h"
21
22 WINE_DEFAULT_DEBUG_CHANNEL(netbios);
23
24 /* This file provides a NetBIOS emulator that implements the NetBIOS interface,
25  * including thread safety and asynchronous call support.  The protocol
26  * implementation is separate, with blocking (synchronous) functions.
27  */
28
29 #define ADAPTERS_INCR 8
30 #define DEFAULT_NUM_SESSIONS 16
31
32 typedef struct _NetBIOSTransportTableEntry
33 {
34     ULONG            id;
35     NetBIOSTransport transport;
36 } NetBIOSTransportTableEntry;
37
38 typedef struct _NetBIOSSession
39 {
40     BOOL  inUse;
41     UCHAR state;
42     UCHAR local_name[NCBNAMSZ];
43     UCHAR remote_name[NCBNAMSZ];
44     void *data;
45 } NetBIOSSession;
46
47 /* This struct needs a little explanation, unfortunately.  enabled is only
48  * used by nbInternalEnum (see).  If transport_id is not 0 and transport
49  * is not NULL, the adapter is considered valid.  (transport is a pointer to
50  * an entry in a NetBIOSTransportTableEntry.)  data has data for the callers of
51  * NetBIOSEnumAdapters to be able to see.  The lana is repeated there, even
52  * though I don't use it internally--it's for transports to use reenabling
53  * adapters using NetBIOSEnableAdapter.
54  */
55 typedef struct _NetBIOSAdapter
56 {
57     BOOL               enabled;
58     BOOL               shuttingDown;
59     LONG               resetting;
60     ULONG              transport_id;
61     NetBIOSTransport  *transport;
62     NetBIOSAdapterImpl impl;
63     struct NBCmdQueue *cmdQueue;
64     CRITICAL_SECTION   cs;
65     DWORD              sessionsLen;
66     NetBIOSSession    *sessions;
67 } NetBIOSAdapter;
68
69 typedef struct _NetBIOSAdapterTable {
70     CRITICAL_SECTION cs;
71     BOOL             enumerated;
72     BOOL             enumerating;
73     UCHAR            tableSize;
74     NetBIOSAdapter  *table;
75 } NetBIOSAdapterTable;
76
77 /* Just enough space for NBT right now */
78 static NetBIOSTransportTableEntry gTransports[1];
79 static UCHAR gNumTransports = 0;
80 static NetBIOSAdapterTable gNBTable;
81
82 static UCHAR nbResizeAdapterTable(UCHAR newSize)
83 {
84     UCHAR ret;
85
86     if (gNBTable.table)
87         gNBTable.table = HeapReAlloc(GetProcessHeap(),
88          HEAP_ZERO_MEMORY, gNBTable.table,
89          newSize * sizeof(NetBIOSAdapter));
90     else
91         gNBTable.table = HeapAlloc(GetProcessHeap(),
92          HEAP_ZERO_MEMORY, newSize * sizeof(NetBIOSAdapter));
93     if (gNBTable.table)
94     {
95         gNBTable.tableSize = newSize;
96         ret = NRC_GOODRET;
97     }
98     else
99         ret = NRC_OSRESNOTAV;
100     return ret;
101 }
102
103 void NetBIOSInit(void)
104 {
105     memset(&gNBTable, 0, sizeof(gNBTable));
106     InitializeCriticalSection(&gNBTable.cs);
107     gNBTable.cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": NetBIOSAdapterTable.cs");
108 }
109
110 void NetBIOSShutdown(void)
111 {
112     UCHAR i;
113
114     EnterCriticalSection(&gNBTable.cs);
115     for (i = 0; i < gNBTable.tableSize; i++)
116     {
117         if (gNBTable.table[i].transport &&
118          gNBTable.table[i].transport->cleanupAdapter)
119             gNBTable.table[i].transport->cleanupAdapter(
120              gNBTable.table[i].impl.data);
121     }
122     for (i = 0; i < gNumTransports; i++)
123         if (gTransports[i].transport.cleanup)
124             gTransports[i].transport.cleanup();
125     LeaveCriticalSection(&gNBTable.cs);
126     gNBTable.cs.DebugInfo->Spare[0] = 0;
127     DeleteCriticalSection(&gNBTable.cs);
128     HeapFree(GetProcessHeap(), 0, gNBTable.table);
129 }
130
131 BOOL NetBIOSRegisterTransport(ULONG id, NetBIOSTransport *transport)
132 {
133     BOOL ret;
134
135     TRACE(": transport 0x%08x, p %p\n", id, transport);
136     if (!transport)
137         ret = FALSE;
138     else if (gNumTransports >= sizeof(gTransports) / sizeof(gTransports[0]))
139     {
140         FIXME("Too many transports %d\n", gNumTransports + 1);
141         ret = FALSE;
142     }
143     else
144     {
145         UCHAR i;
146
147         ret = FALSE;
148         for (i = 0; !ret && i < gNumTransports; i++)
149         {
150             if (gTransports[i].id == id)
151             {
152                 WARN("Replacing NetBIOS transport ID %d\n", id);
153                 memcpy(&gTransports[i].transport, transport,
154                  sizeof(NetBIOSTransport));
155                 ret = TRUE;
156             }
157         }
158         if (!ret)
159         {
160             gTransports[gNumTransports].id = id;
161             memcpy(&gTransports[gNumTransports].transport, transport,
162              sizeof(NetBIOSTransport));
163             gNumTransports++;
164             ret = TRUE;
165         }
166     }
167     TRACE("returning %d\n", ret);
168     return ret;
169 }
170
171 /* In this, I acquire the table lock to make sure no one else is modifying it.
172  * This is _probably_ overkill since it should only be called during the
173  * context of a NetBIOSEnum call, but just to be safe..
174  */
175 BOOL NetBIOSRegisterAdapter(ULONG transport, DWORD ifIndex, void *data)
176 {
177     BOOL ret;
178     UCHAR i;
179
180     TRACE(": transport 0x%08x, ifIndex 0x%08x, data %p\n", transport, ifIndex,
181      data);
182     for (i = 0; i < gNumTransports && gTransports[i].id != transport; i++)
183         ;
184     if ((i < gNumTransports) && gTransports[i].id == transport)
185     {
186         NetBIOSTransport *transportPtr = &gTransports[i].transport;
187
188         TRACE(": found transport %p for id 0x%08x\n", transportPtr, transport);
189
190         EnterCriticalSection(&gNBTable.cs);
191         ret = FALSE;
192         for (i = 0; i < gNBTable.tableSize &&
193          gNBTable.table[i].transport != 0; i++)
194             ;
195         if (i == gNBTable.tableSize && gNBTable.tableSize < MAX_LANA + 1)
196         {
197             UCHAR newSize;
198
199             if (gNBTable.tableSize < (MAX_LANA + 1) - ADAPTERS_INCR)
200                 newSize = gNBTable.tableSize + ADAPTERS_INCR;
201             else
202                 newSize = MAX_LANA + 1;
203             nbResizeAdapterTable(newSize);
204         }
205         if (i < gNBTable.tableSize && gNBTable.table[i].transport == 0)
206         {
207             TRACE(": registering as LANA %d\n", i);
208             gNBTable.table[i].transport_id = transport;
209             gNBTable.table[i].transport = transportPtr;
210             gNBTable.table[i].impl.lana = i;
211             gNBTable.table[i].impl.ifIndex = ifIndex;
212             gNBTable.table[i].impl.data = data;
213             gNBTable.table[i].cmdQueue = NBCmdQueueCreate(GetProcessHeap());
214             InitializeCriticalSection(&gNBTable.table[i].cs);
215             gNBTable.table[i].cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": NetBIOSAdapterTable.NetBIOSAdapter.cs");
216             gNBTable.table[i].enabled = TRUE;
217             ret = TRUE;
218         }
219         LeaveCriticalSection(&gNBTable.cs);
220     }
221     else
222         ret = FALSE;
223     TRACE("returning %d\n", ret);
224     return ret;
225 }
226
227 /* In this, I acquire the table lock to make sure no one else is modifying it.
228  * This is _probably_ overkill since it should only be called during the
229  * context of a NetBIOSEnum call, but just to be safe..
230  */
231 void NetBIOSEnableAdapter(UCHAR lana)
232 {
233     TRACE(": %d\n", lana);
234     if (lana < gNBTable.tableSize)
235     {
236         EnterCriticalSection(&gNBTable.cs);
237         if (gNBTable.table[lana].transport != 0)
238             gNBTable.table[lana].enabled = TRUE;
239         LeaveCriticalSection(&gNBTable.cs);
240     }
241 }
242
243 static void nbShutdownAdapter(NetBIOSAdapter *adapter)
244 {
245     if (adapter)
246     {
247         adapter->shuttingDown = TRUE;
248         NBCmdQueueCancelAll(adapter->cmdQueue);
249         if (adapter->transport->cleanupAdapter)
250             adapter->transport->cleanupAdapter(adapter->impl.data);
251         NBCmdQueueDestroy(adapter->cmdQueue);
252         adapter->cs.DebugInfo->Spare[0] = 0;
253         DeleteCriticalSection(&adapter->cs);
254         memset(adapter, 0, sizeof(NetBIOSAdapter));
255     }
256 }
257
258 static void nbInternalEnum(void)
259 {
260     UCHAR i;
261
262     EnterCriticalSection(&gNBTable.cs);
263     TRACE("before mark\n");
264     /* mark: */
265     for (i = 0; i < gNBTable.tableSize; i++)
266         if (gNBTable.table[i].enabled && gNBTable.table[i].transport != 0)
267             gNBTable.table[i].enabled = FALSE;
268
269     TRACE("marked, before store, %d transports\n", gNumTransports);
270     /* store adapters: */
271     for (i = 0; i < gNumTransports; i++)
272         if (gTransports[i].transport.enumerate)
273             gTransports[i].transport.enumerate();
274
275     TRACE("before sweep\n");
276     /* sweep: */
277     for (i = 0; i < gNBTable.tableSize; i++)
278         if (!gNBTable.table[i].enabled && gNBTable.table[i].transport != 0)
279             nbShutdownAdapter(&gNBTable.table[i]);
280     gNBTable.enumerated = TRUE;
281     LeaveCriticalSection(&gNBTable.cs);
282 }
283
284 UCHAR NetBIOSNumAdapters(void)
285 {
286     UCHAR ret, i;
287
288     if (!gNBTable.enumerated)
289         nbInternalEnum();
290     for (i = 0, ret = 0; i < gNBTable.tableSize; i++)
291         if (gNBTable.table[i].transport != 0)
292             ret++;
293     return ret;
294 }
295
296 void NetBIOSEnumAdapters(ULONG transport, NetBIOSEnumAdaptersCallback cb,
297  void *closure)
298 {
299     TRACE("transport 0x%08x, callback %p, closure %p\n", transport, cb,
300      closure);
301     if (cb)
302     {
303         BOOL enumAll = memcmp(&transport, ALL_TRANSPORTS, sizeof(ULONG)) == 0;
304         UCHAR i, numLANAs = 0;
305
306         EnterCriticalSection(&gNBTable.cs);
307         if (!gNBTable.enumerating)
308         {
309             gNBTable.enumerating = TRUE;
310             nbInternalEnum();
311             gNBTable.enumerating = FALSE;
312         }
313         for (i = 0; i < gNBTable.tableSize; i++)
314             if (enumAll || gNBTable.table[i].transport_id == transport)
315                 numLANAs++;
316         if (numLANAs > 0)
317         {
318             UCHAR lanaIndex = 0;
319
320             for (i = 0; i < gNBTable.tableSize; i++)
321                 if (gNBTable.table[i].transport_id != 0 &&
322                  (enumAll || gNBTable.table[i].transport_id == transport))
323                     cb(numLANAs, lanaIndex++, gNBTable.table[i].transport_id,
324                      &gNBTable.table[i].impl, closure);
325         }
326         LeaveCriticalSection(&gNBTable.cs);
327     }
328 }
329
330 static NetBIOSAdapter *nbGetAdapter(UCHAR lana)
331 {
332     NetBIOSAdapter *ret = NULL;
333
334     TRACE(": lana %d, num allocated adapters %d\n", lana, gNBTable.tableSize);
335     if (lana < gNBTable.tableSize && gNBTable.table[lana].transport_id != 0
336      && gNBTable.table[lana].transport)
337         ret = &gNBTable.table[lana];
338     TRACE("returning %p\n", ret);
339     return ret;
340 }
341
342 static UCHAR nbEnum(PNCB ncb)
343 {
344     PLANA_ENUM lanas = (PLANA_ENUM)ncb->ncb_buffer;
345     UCHAR i, ret;
346
347     TRACE(": ncb %p\n", ncb);
348
349     if (!lanas)
350         ret = NRC_BUFLEN;
351     else if (ncb->ncb_length < sizeof(LANA_ENUM))
352         ret = NRC_BUFLEN;
353     else
354     {
355         nbInternalEnum();
356         lanas->length = 0;
357         for (i = 0; i < gNBTable.tableSize; i++)
358             if (gNBTable.table[i].transport)
359             {
360                 lanas->length++;
361                 lanas->lana[i] = i;
362             }
363         ret = NRC_GOODRET;
364     }
365     TRACE("returning 0x%02x\n", ret);
366     return ret;
367 }
368
369 static UCHAR nbInternalHangup(NetBIOSAdapter *adapter, NetBIOSSession *session);
370
371 static UCHAR nbCancel(NetBIOSAdapter *adapter, PNCB ncb)
372 {
373     UCHAR ret;
374
375     TRACE(": adapter %p, ncb %p\n", adapter, ncb);
376
377     if (!adapter) return NRC_BRIDGE;
378     if (!ncb) return NRC_INVADDRESS;
379
380     switch (ncb->ncb_command & 0x7f)
381     {
382         case NCBCANCEL:
383         case NCBADDNAME:
384         case NCBADDGRNAME:
385         case NCBDELNAME:
386         case NCBRESET:
387         case NCBSSTAT:
388             ret = NRC_CANCEL;
389             break;
390
391         /* NCBCALL, NCBCHAINSEND/NCBSEND, NCBHANGUP all close the associated
392          * session if cancelled */
393         case NCBCALL:
394         case NCBSEND:
395         case NCBCHAINSEND:
396         case NCBSENDNA:
397         case NCBCHAINSENDNA:
398         case NCBHANGUP:
399         {
400             if (ncb->ncb_lsn >= adapter->sessionsLen)
401                 ret = NRC_SNUMOUT;
402             else if (!adapter->sessions[ncb->ncb_lsn].inUse)
403                 ret = NRC_SNUMOUT;
404             else
405             {
406                 ret = NBCmdQueueCancel(adapter->cmdQueue, ncb);
407                 if (ret == NRC_CMDCAN || ret == NRC_CANOCCR)
408                     nbInternalHangup(adapter, &adapter->sessions[ncb->ncb_lsn]);
409             }
410             break;
411         }
412
413         default:
414             ret = NBCmdQueueCancel(adapter->cmdQueue, ncb);
415     }
416     TRACE("returning 0x%02x\n", ret);
417     return ret;
418 }
419
420 /* Resizes adapter to contain space for at least sessionsLen sessions.
421  * If allocating more space for sessions, sets the adapter's sessionsLen to
422  * sessionsLen.  If the adapter's sessionsLen was already at least sessionsLen,
423  * does nothing.  Does not modify existing sessions.  Assumes the adapter is
424  * locked.
425  * Returns NRC_GOODRET on success, and something else on failure.
426  */
427 static UCHAR nbResizeAdapter(NetBIOSAdapter *adapter, UCHAR sessionsLen)
428 {
429     UCHAR ret = NRC_GOODRET;
430
431     if (adapter && adapter->sessionsLen < sessionsLen)
432     {
433         NetBIOSSession *newSessions;
434
435         if (adapter->sessions)
436             newSessions = HeapReAlloc(GetProcessHeap(),
437              HEAP_ZERO_MEMORY, adapter->sessions, sessionsLen *
438              sizeof(NetBIOSSession));
439         else
440             newSessions = HeapAlloc(GetProcessHeap(),
441              HEAP_ZERO_MEMORY, sessionsLen * sizeof(NetBIOSSession));
442         if (newSessions)
443         {
444             adapter->sessions = newSessions;
445             adapter->sessionsLen = sessionsLen;
446         }
447         else
448             ret = NRC_OSRESNOTAV;
449     }
450     return ret;
451 }
452
453 static UCHAR nbReset(NetBIOSAdapter *adapter, PNCB ncb)
454 {
455     UCHAR ret;
456
457     TRACE(": adapter %p, ncb %p\n", adapter, ncb);
458
459     if (!adapter) return NRC_BRIDGE;
460     if (!ncb) return NRC_INVADDRESS;
461
462     if (InterlockedIncrement(&adapter->resetting) == 1)
463     {
464         UCHAR i, resizeTo;
465
466         NBCmdQueueCancelAll(adapter->cmdQueue);
467
468         EnterCriticalSection(&adapter->cs);
469         for (i = 0; i < adapter->sessionsLen; i++)
470             if (adapter->sessions[i].inUse)
471                 nbInternalHangup(adapter, &adapter->sessions[i]);
472         if (!ncb->ncb_lsn)
473             resizeTo = ncb->ncb_callname[0] == 0 ? DEFAULT_NUM_SESSIONS :
474              ncb->ncb_callname[0];
475         else if (adapter->sessionsLen == 0)
476             resizeTo = DEFAULT_NUM_SESSIONS;
477         else
478             resizeTo = 0;
479         if (resizeTo > 0)
480             ret = nbResizeAdapter(adapter, resizeTo);
481         else
482             ret = NRC_GOODRET;
483         LeaveCriticalSection(&adapter->cs);
484     }
485     else
486         ret = NRC_TOOMANY;
487     InterlockedDecrement(&adapter->resetting);
488     TRACE("returning 0x%02x\n", ret);
489     return ret;
490 }
491
492 static UCHAR nbSStat(NetBIOSAdapter *adapter, PNCB ncb)
493 {
494     UCHAR ret, i, spaceFor;
495     PSESSION_HEADER sstat;
496
497     TRACE(": adapter %p, NCB %p\n", adapter, ncb);
498
499     if (!adapter) return NRC_BADDR;
500     if (adapter->sessionsLen == 0) return NRC_ENVNOTDEF;
501     if (!ncb) return NRC_INVADDRESS;
502     if (!ncb->ncb_buffer) return NRC_BADDR;
503     if (ncb->ncb_length < sizeof(SESSION_HEADER)) return NRC_BUFLEN;
504
505     sstat = (PSESSION_HEADER)ncb->ncb_buffer;
506     ret = NRC_GOODRET;
507     memset(sstat, 0, sizeof(SESSION_HEADER));
508     spaceFor = (ncb->ncb_length - sizeof(SESSION_HEADER)) /
509      sizeof(SESSION_BUFFER);
510     EnterCriticalSection(&adapter->cs);
511     for (i = 0; ret == NRC_GOODRET && i < adapter->sessionsLen; i++)
512     {
513         if (adapter->sessions[i].inUse && (ncb->ncb_name[0] == '*' ||
514          !memcmp(ncb->ncb_name, adapter->sessions[i].local_name, NCBNAMSZ)))
515         {
516             if (sstat->num_sess < spaceFor)
517             {
518                 PSESSION_BUFFER buf;
519                
520                 buf = (PSESSION_BUFFER)((PUCHAR)sstat + sizeof(SESSION_HEADER)
521                  + sstat->num_sess * sizeof(SESSION_BUFFER));
522                 buf->lsn = i;
523                 buf->state = adapter->sessions[i].state;
524                 memcpy(buf->local_name, adapter->sessions[i].local_name,
525                  NCBNAMSZ);
526                 memcpy(buf->remote_name, adapter->sessions[i].remote_name,
527                  NCBNAMSZ);
528                 buf->rcvs_outstanding = buf->sends_outstanding = 0;
529                 sstat->num_sess++;
530             }
531             else
532                 ret = NRC_BUFLEN;
533         }
534     }
535     LeaveCriticalSection(&adapter->cs);
536
537     TRACE("returning 0x%02x\n", ret);
538     return ret;
539 }
540
541 static UCHAR nbCall(NetBIOSAdapter *adapter, PNCB ncb)
542 {
543     UCHAR ret, i;
544
545     TRACE(": adapter %p, NCB %p\n", adapter, ncb);
546
547     if (!adapter) return NRC_BRIDGE;
548     if (adapter->sessionsLen == 0) return NRC_ENVNOTDEF;
549     if (!adapter->transport->call) return NRC_ILLCMD;
550     if (!ncb) return NRC_INVADDRESS;
551
552     EnterCriticalSection(&adapter->cs);
553     for (i = 0; i < adapter->sessionsLen && adapter->sessions[i].inUse; i++)
554         ;
555     if (i < adapter->sessionsLen)
556     {
557         adapter->sessions[i].inUse = TRUE;
558         adapter->sessions[i].state = CALL_PENDING;
559         memcpy(adapter->sessions[i].local_name, ncb->ncb_name, NCBNAMSZ);
560         memcpy(adapter->sessions[i].remote_name, ncb->ncb_callname, NCBNAMSZ);
561         ret = NRC_GOODRET;
562     }
563     else
564         ret = NRC_LOCTFUL;
565     LeaveCriticalSection(&adapter->cs);
566
567     if (ret == NRC_GOODRET)
568     {
569         ret = adapter->transport->call(adapter->impl.data, ncb,
570          &adapter->sessions[i].data);
571         if (ret == NRC_GOODRET)
572         {
573             ncb->ncb_lsn = i;
574             adapter->sessions[i].state = SESSION_ESTABLISHED;
575         }
576         else
577         {
578             adapter->sessions[i].inUse = FALSE;
579             adapter->sessions[i].state = 0;
580         }
581     }
582     TRACE("returning 0x%02x\n", ret);
583     return ret;
584 }
585
586 static UCHAR nbSend(NetBIOSAdapter *adapter, PNCB ncb)
587 {
588     UCHAR ret;
589     NetBIOSSession *session;
590
591     if (!adapter) return NRC_BRIDGE;
592     if (!adapter->transport->send) return NRC_ILLCMD;
593     if (!ncb) return NRC_INVADDRESS;
594     if (ncb->ncb_lsn >= adapter->sessionsLen) return NRC_SNUMOUT;
595     if (!adapter->sessions[ncb->ncb_lsn].inUse) return NRC_SNUMOUT;
596     if (!ncb->ncb_buffer) return NRC_BADDR;
597
598     session = &adapter->sessions[ncb->ncb_lsn];
599     if (session->state != SESSION_ESTABLISHED)
600         ret = NRC_SNUMOUT;
601     else
602         ret = adapter->transport->send(adapter->impl.data, session->data, ncb);
603     return ret;
604 }
605
606 static UCHAR nbRecv(NetBIOSAdapter *adapter, PNCB ncb)
607 {
608     UCHAR ret;
609     NetBIOSSession *session;
610
611     if (!adapter) return NRC_BRIDGE;
612     if (!adapter->transport->recv) return NRC_ILLCMD;
613     if (!ncb) return NRC_INVADDRESS;
614     if (ncb->ncb_lsn >= adapter->sessionsLen) return NRC_SNUMOUT;
615     if (!adapter->sessions[ncb->ncb_lsn].inUse) return NRC_SNUMOUT;
616     if (!ncb->ncb_buffer) return NRC_BADDR;
617
618     session = &adapter->sessions[ncb->ncb_lsn];
619     if (session->state != SESSION_ESTABLISHED)
620         ret = NRC_SNUMOUT;
621     else
622         ret = adapter->transport->recv(adapter->impl.data, session->data, ncb);
623     return ret;
624 }
625
626 static UCHAR nbInternalHangup(NetBIOSAdapter *adapter, NetBIOSSession *session)
627 {
628     UCHAR ret;
629
630     if (!adapter) return NRC_BRIDGE;
631     if (!session) return NRC_SNUMOUT;
632
633     if (adapter->transport->hangup)
634         ret = adapter->transport->hangup(adapter->impl.data, session->data);
635     else
636         ret = NRC_ILLCMD;
637     EnterCriticalSection(&adapter->cs);
638     memset(session, 0, sizeof(NetBIOSSession));
639     LeaveCriticalSection(&adapter->cs);
640     return ret;
641 }
642
643 static UCHAR nbHangup(NetBIOSAdapter *adapter, const NCB *ncb)
644 {
645     UCHAR ret;
646     NetBIOSSession *session;
647
648     if (!adapter) return NRC_BRIDGE;
649     if (!ncb) return NRC_INVADDRESS;
650     if (ncb->ncb_lsn >= adapter->sessionsLen) return NRC_SNUMOUT;
651     if (!adapter->sessions[ncb->ncb_lsn].inUse) return NRC_SNUMOUT;
652
653     session = &adapter->sessions[ncb->ncb_lsn];
654     if (session->state != SESSION_ESTABLISHED)
655         ret = NRC_SNUMOUT;
656     else
657     {
658         session->state = HANGUP_PENDING;
659         ret = nbInternalHangup(adapter, session);
660     }
661     return ret;
662 }
663
664 void NetBIOSHangupSession(const NCB *ncb)
665 {
666     NetBIOSAdapter *adapter;
667
668     if (!ncb) return;
669
670     adapter = nbGetAdapter(ncb->ncb_lana_num);
671     if (adapter)
672     {
673         if (ncb->ncb_lsn < adapter->sessionsLen &&
674          adapter->sessions[ncb->ncb_lsn].inUse)
675             nbHangup(adapter, ncb);
676     }
677 }
678
679 static UCHAR nbAStat(NetBIOSAdapter *adapter, PNCB ncb)
680 {
681     UCHAR ret;
682
683     if (!adapter) return NRC_BRIDGE;
684     if (!adapter->transport->astat) return NRC_ILLCMD;
685     if (!ncb) return NRC_INVADDRESS;
686     if (!ncb->ncb_buffer) return NRC_BADDR;
687     if (ncb->ncb_length < sizeof(ADAPTER_STATUS)) return NRC_BUFLEN;
688
689     ret = adapter->transport->astat(adapter->impl.data, ncb);
690     if (ncb->ncb_callname[0] == '*')
691     {
692         PADAPTER_STATUS astat = (PADAPTER_STATUS)ncb->ncb_buffer;
693
694         astat->max_sess = astat->max_cfg_sess = adapter->sessionsLen;
695     }
696     return ret;
697 }
698
699 static UCHAR nbDispatch(NetBIOSAdapter *adapter, PNCB ncb)
700 {
701     UCHAR ret, cmd;
702
703     TRACE(": adapter %p, ncb %p\n", adapter, ncb);
704
705     if (!adapter) return NRC_BRIDGE;
706     if (!ncb) return NRC_INVADDRESS;
707
708     cmd = ncb->ncb_command & 0x7f;
709     if (cmd == NCBRESET)
710         ret = nbReset(adapter, ncb);
711     else
712     {
713         ret = NBCmdQueueAdd(adapter->cmdQueue, ncb);
714         if (ret == NRC_GOODRET)
715         {
716             switch (cmd)
717             {
718                 case NCBCALL:
719                     ret = nbCall(adapter, ncb);
720                     break;
721
722                 /* WinNT doesn't chain sends, it always sends immediately.
723                  * Doubt there's any real significance to the NA variants.
724                  */
725                 case NCBSEND:
726                 case NCBSENDNA:
727                 case NCBCHAINSEND:
728                 case NCBCHAINSENDNA:
729                     ret = nbSend(adapter, ncb);
730                     break;
731
732                 case NCBRECV:
733                     ret = nbRecv(adapter, ncb);
734                     break;
735
736                 case NCBHANGUP:
737                     ret = nbHangup(adapter, ncb);
738                     break;
739
740                 case NCBASTAT:
741                     ret = nbAStat(adapter, ncb);
742                     break;
743
744                 case NCBFINDNAME:
745                     if (adapter->transport->findName)
746                         ret = adapter->transport->findName(adapter->impl.data,
747                          ncb);
748                     else
749                         ret = NRC_ILLCMD;
750                     break;
751
752                 default:
753                     FIXME("(%p): command code 0x%02x\n", ncb, ncb->ncb_command);
754                     ret = NRC_ILLCMD;
755             }
756             NBCmdQueueComplete(adapter->cmdQueue, ncb, ret);
757         }
758     }
759     TRACE("returning 0x%02x\n", ret);
760     return ret;
761 }
762
763 static DWORD WINAPI nbCmdThread(LPVOID lpVoid)
764 {
765     PNCB ncb = (PNCB)lpVoid;
766
767     if (ncb)
768     {
769         UCHAR ret;
770         NetBIOSAdapter *adapter = nbGetAdapter(ncb->ncb_lana_num);
771
772         if (adapter)
773             ret = nbDispatch(adapter, ncb);
774         else
775             ret = NRC_BRIDGE;
776         ncb->ncb_retcode = ncb->ncb_cmd_cplt = ret;
777         if (ncb->ncb_post)
778             ncb->ncb_post(ncb);
779         else if (ncb->ncb_event)
780             SetEvent(ncb->ncb_event);
781     }
782     return 0;
783 }
784
785 UCHAR WINAPI Netbios(PNCB ncb)
786 {
787     UCHAR ret, cmd;
788
789     TRACE("ncb = %p\n", ncb);
790
791     if (!ncb) return NRC_INVADDRESS;
792
793     TRACE("ncb_command 0x%02x, ncb_lana_num %d, ncb_buffer %p, ncb_length %d\n",
794      ncb->ncb_command, ncb->ncb_lana_num, ncb->ncb_buffer, ncb->ncb_length);
795     cmd = ncb->ncb_command & 0x7f;
796
797     if (cmd == NCBENUM)
798         ncb->ncb_retcode = ncb->ncb_cmd_cplt = ret = nbEnum(ncb);
799     else if (cmd == NCBADDNAME)
800     {
801         FIXME("NCBADDNAME: stub, returning success\n");
802         ncb->ncb_retcode = ncb->ncb_cmd_cplt = ret = NRC_GOODRET;
803     }
804     else
805     {
806         NetBIOSAdapter *adapter;
807
808         /* Apps not specifically written for WinNT won't do an NCBENUM first,
809          * so make sure the table has been enumerated at least once
810          */
811         if (!gNBTable.enumerated)
812             nbInternalEnum();
813         adapter = nbGetAdapter(ncb->ncb_lana_num);
814         if (!adapter)
815             ret = NRC_BRIDGE;
816         else
817         {
818             if (adapter->shuttingDown)
819                 ret = NRC_IFBUSY;
820             else if (adapter->resetting)
821                 ret = NRC_TOOMANY;
822             else
823             {
824                 /* non-asynch commands first */
825                 if (cmd == NCBCANCEL)
826                     ncb->ncb_retcode = ncb->ncb_cmd_cplt = ret =
827                      nbCancel(adapter, ncb);
828                 else if (cmd == NCBSSTAT)
829                     ncb->ncb_retcode = ncb->ncb_cmd_cplt = ret =
830                      nbSStat(adapter, ncb);
831                 else
832                 {
833                     if (ncb->ncb_command & ASYNCH)
834                     {
835                         HANDLE thread = CreateThread(NULL, 0, nbCmdThread, ncb,
836                          CREATE_SUSPENDED, NULL);
837
838                         if (thread != NULL)
839                         {
840                             ncb->ncb_retcode = ncb->ncb_cmd_cplt = NRC_PENDING;
841                             if (ncb->ncb_event)
842                                 ResetEvent(ncb->ncb_event);
843                             ResumeThread(thread);
844                             CloseHandle(thread);
845                             ret = NRC_GOODRET;
846                         }
847                         else
848                         ncb->ncb_retcode = ncb->ncb_cmd_cplt = ret =
849                          NRC_OSRESNOTAV;
850                     }
851                     else
852                         ncb->ncb_retcode = ncb->ncb_cmd_cplt = ret =
853                          nbDispatch(adapter, ncb);
854                 }
855             }
856         }
857     }
858     TRACE("returning 0x%02x\n", ret);
859     return ret;
860 }