|  | /* 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 | 
|  | * | 
|  | * I am heavily indebted to Chris Hertel's excellent Implementing CIFS, | 
|  | * http://ubiqx.org/cifs/ , for whatever understanding I have of NBT. | 
|  | * I also stole from Mike McCormack's smb.c and netapi32.c, although little of | 
|  | * that code remains. | 
|  | * Lack of understanding and bugs are my fault. | 
|  | * | 
|  | * FIXME: | 
|  | * - Of the NetBIOS session functions, only client functions are supported, and | 
|  | *   it's likely they'll be the only functions supported.  NBT requires session | 
|  | *   servers to listen on TCP/139.  This requires root privilege, and Samba is | 
|  | *   likely to be listening here already.  This further restricts NetBIOS | 
|  | *   applications, both explicit users and implicit ones:  CreateNamedPipe | 
|  | *   won't actually create a listening pipe, for example, so applications can't | 
|  | *   act as RPC servers using a named pipe protocol binding, DCOM won't be able | 
|  | *   to support callbacks or servers over the named pipe protocol, etc. | 
|  | * | 
|  | * - Datagram support is omitted for the same reason.  To send a NetBIOS | 
|  | *   datagram, you must include the NetBIOS name by which your application is | 
|  | *   known.  This requires you to have registered the name previously, and be | 
|  | *   able to act as a NetBIOS datagram server (listening on UDP/138). | 
|  | * | 
|  | * - Name registration functions are omitted for the same reason--registering a | 
|  | *   name requires you to be able to defend it, and this means listening on | 
|  | *   UDP/137. | 
|  | *   Win98 requires you either use your computer's NetBIOS name (with the NULL | 
|  | *   suffix byte) as the calling name when creating a session, or to register | 
|  | *   a new name before creating one:  it disallows '*' as the calling name. | 
|  | *   Win2K initially starts with an empty name table, and doesn't allow you to | 
|  | *   use the machine's NetBIOS name (with the NULL suffix byte) as the calling | 
|  | *   name.  Although it allows sessions to be created with '*' as the calling | 
|  | *   name, doing so results in timeouts for all receives, because the | 
|  | *   application never gets them. | 
|  | *   So, a well-behaved NetBIOS application will typically want to register a | 
|  | *   name.  I should probably support a do-nothing name list that allows | 
|  | *   NCBADDNAME to add to it, but doesn't actually register the name, or does | 
|  | *   attempt to register it without being able to defend it. | 
|  | * | 
|  | * - Name lookups may not behave quite as you'd expect/like if you have | 
|  | *   multiple LANAs.  If a name is resolvable through DNS, or if you're using | 
|  | *   WINS, it'll resolve on _any_ LANA.  So, a Call will succeed on any LANA as | 
|  | *   well. | 
|  | *   I'm not sure how Windows behaves in this case.  I could try to force | 
|  | *   lookups to the correct adapter by using one of the GetPreferred* | 
|  | *   functions, but with the possibility of multiple adapters in the same | 
|  | *   same subnet, there's no guarantee that what IpHlpApi thinks is the | 
|  | *   preferred adapter will actually be a LANA.  (It's highly probable because | 
|  | *   this is an unusual configuration, but not guaranteed.) | 
|  | * | 
|  | * See also other FIXMEs in the code. | 
|  | */ | 
|  |  | 
|  | #include "config.h" | 
|  | #include <stdarg.h> | 
|  |  | 
|  | #include "winsock2.h" | 
|  | #include "windef.h" | 
|  | #include "winbase.h" | 
|  | #include "wine/debug.h" | 
|  | #include "winreg.h" | 
|  | #include "iphlpapi.h" | 
|  |  | 
|  | #include "netbios.h" | 
|  | #include "nbnamecache.h" | 
|  |  | 
|  | WINE_DEFAULT_DEBUG_CHANNEL(netbios); | 
|  |  | 
|  | #define PORT_NBNS 137 | 
|  | #define PORT_NBDG 138 | 
|  | #define PORT_NBSS 139 | 
|  |  | 
|  | #ifndef INADDR_NONE | 
|  | #define INADDR_NONE ~0UL | 
|  | #endif | 
|  |  | 
|  | #define NBR_ADDWORD(p,word) (*(WORD *)(p)) = htons(word) | 
|  | #define NBR_GETWORD(p) ntohs(*(WORD *)(p)) | 
|  |  | 
|  | #define MIN_QUERIES         1 | 
|  | #define MAX_QUERIES         0xffff | 
|  | #define MIN_QUERY_TIMEOUT   100 | 
|  | #define MAX_QUERY_TIMEOUT   0xffffffff | 
|  | #define BCAST_QUERIES       3 | 
|  | #define BCAST_QUERY_TIMEOUT 750 | 
|  | #define WINS_QUERIES        3 | 
|  | #define WINS_QUERY_TIMEOUT  750 | 
|  | #define MAX_WINS_SERVERS    2 | 
|  | #define MIN_CACHE_TIMEOUT   60000 | 
|  | #define CACHE_TIMEOUT       360000 | 
|  |  | 
|  | #define MAX_NBT_NAME_SZ            255 | 
|  | #define SIMPLE_NAME_QUERY_PKT_SIZE 16 + MAX_NBT_NAME_SZ | 
|  |  | 
|  | #define NBNS_TYPE_NB             0x0020 | 
|  | #define NBNS_TYPE_NBSTAT         0x0021 | 
|  | #define NBNS_CLASS_INTERNET      0x00001 | 
|  | #define NBNS_HEADER_SIZE         (sizeof(WORD) * 6) | 
|  | #define NBNS_RESPONSE_AND_OPCODE 0xf800 | 
|  | #define NBNS_RESPONSE_AND_QUERY  0x8000 | 
|  | #define NBNS_REPLYCODE           0x0f | 
|  |  | 
|  | #define NBSS_HDRSIZE 4 | 
|  |  | 
|  | #define NBSS_MSG       0x00 | 
|  | #define NBSS_REQ       0x81 | 
|  | #define NBSS_ACK       0x82 | 
|  | #define NBSS_NACK      0x83 | 
|  | #define NBSS_RETARGET  0x84 | 
|  | #define NBSS_KEEPALIVE 0x85 | 
|  |  | 
|  | #define NBSS_ERR_NOT_LISTENING_ON_NAME    0x80 | 
|  | #define NBSS_ERR_NOT_LISTENING_FOR_CALLER 0x81 | 
|  | #define NBSS_ERR_BAD_NAME                 0x82 | 
|  | #define NBSS_ERR_INSUFFICIENT_RESOURCES   0x83 | 
|  |  | 
|  | #define NBSS_EXTENSION 0x01 | 
|  |  | 
|  | typedef struct _NetBTSession | 
|  | { | 
|  | CRITICAL_SECTION cs; | 
|  | SOCKET           fd; | 
|  | DWORD            bytesPending; | 
|  | } NetBTSession; | 
|  |  | 
|  | typedef struct _NetBTAdapter | 
|  | { | 
|  | MIB_IPADDRROW       ipr; | 
|  | WORD                nameQueryXID; | 
|  | struct NBNameCache *nameCache; | 
|  | DWORD               xmit_success; | 
|  | DWORD               recv_success; | 
|  | } NetBTAdapter; | 
|  |  | 
|  | static ULONG gTransportID; | 
|  | static BOOL  gEnableDNS; | 
|  | static DWORD gBCastQueries; | 
|  | static DWORD gBCastQueryTimeout; | 
|  | static DWORD gWINSQueries; | 
|  | static DWORD gWINSQueryTimeout; | 
|  | static DWORD gWINSServers[MAX_WINS_SERVERS]; | 
|  | static int   gNumWINSServers; | 
|  | static char  gScopeID[MAX_SCOPE_ID_LEN]; | 
|  | static DWORD gCacheTimeout; | 
|  | static struct NBNameCache *gNameCache; | 
|  |  | 
|  | /* Converts from a NetBIOS name into a Second Level Encoding-formatted name. | 
|  | * Assumes p is not NULL and is either NULL terminated or has at most NCBNAMSZ | 
|  | * bytes, and buffer has at least MAX_NBT_NAME_SZ bytes.  Pads with space bytes | 
|  | * if p is NULL-terminated.  Returns the number of bytes stored in buffer. | 
|  | */ | 
|  | static int NetBTNameEncode(const UCHAR *p, UCHAR *buffer) | 
|  | { | 
|  | int i,len=0; | 
|  |  | 
|  | if (!p) return 0; | 
|  | if (!buffer) return 0; | 
|  |  | 
|  | buffer[len++] = NCBNAMSZ * 2; | 
|  | for (i = 0; p[i] && i < NCBNAMSZ; i++) | 
|  | { | 
|  | buffer[len++] = ((p[i] & 0xf0) >> 4) + 'A'; | 
|  | buffer[len++] =  (p[i] & 0x0f) + 'A'; | 
|  | } | 
|  | while (len < NCBNAMSZ * 2) | 
|  | { | 
|  | buffer[len++] = 'C'; | 
|  | buffer[len++] = 'A'; | 
|  | } | 
|  | if (*gScopeID) | 
|  | { | 
|  | int scopeIDLen = strlen(gScopeID); | 
|  |  | 
|  | memcpy(buffer + len, gScopeID, scopeIDLen); | 
|  | len += scopeIDLen; | 
|  | } | 
|  | buffer[len++] = 0;     /* add second terminator */ | 
|  | return len; | 
|  | } | 
|  |  | 
|  | /* Creates a NBT name request packet for name in buffer.  If broadcast is true, | 
|  | * creates a broadcast request, otherwise creates a unicast request. | 
|  | * Returns the number of bytes stored in buffer. | 
|  | */ | 
|  | static DWORD NetBTNameReq(const UCHAR name[NCBNAMSZ], WORD xid, WORD qtype, | 
|  | BOOL broadcast, UCHAR *buffer, int len) | 
|  | { | 
|  | int i = 0; | 
|  |  | 
|  | if (len < SIMPLE_NAME_QUERY_PKT_SIZE) return 0; | 
|  |  | 
|  | NBR_ADDWORD(&buffer[i],xid);    i+=2; /* transaction */ | 
|  | if (broadcast) | 
|  | { | 
|  | NBR_ADDWORD(&buffer[i],0x0110); /* flags: r=req,op=query,rd=1,b=1 */ | 
|  | i+=2; | 
|  | } | 
|  | else | 
|  | { | 
|  | NBR_ADDWORD(&buffer[i],0x0100); /* flags: r=req,op=query,rd=1,b=0 */ | 
|  | i+=2; | 
|  | } | 
|  | NBR_ADDWORD(&buffer[i],0x0001); i+=2; /* one name query */ | 
|  | NBR_ADDWORD(&buffer[i],0x0000); i+=2; /* zero answers */ | 
|  | NBR_ADDWORD(&buffer[i],0x0000); i+=2; /* zero authorities */ | 
|  | NBR_ADDWORD(&buffer[i],0x0000); i+=2; /* zero additional */ | 
|  |  | 
|  | i += NetBTNameEncode(name, &buffer[i]); | 
|  |  | 
|  | NBR_ADDWORD(&buffer[i],qtype); i+=2; | 
|  | NBR_ADDWORD(&buffer[i],NBNS_CLASS_INTERNET); i+=2; | 
|  |  | 
|  | return i; | 
|  | } | 
|  |  | 
|  | /* Sends a name query request for name on fd to destAddr.  Sets SO_BROADCAST on | 
|  | * fd if broadcast is TRUE.  Assumes fd is not INVALID_SOCKET, and name is not | 
|  | * NULL. | 
|  | * Returns 0 on success, -1 on failure. | 
|  | */ | 
|  | static int NetBTSendNameQuery(SOCKET fd, const UCHAR name[NCBNAMSZ], WORD xid, | 
|  | WORD qtype, DWORD destAddr, BOOL broadcast) | 
|  | { | 
|  | int ret = 0, on = 1; | 
|  | struct in_addr addr; | 
|  |  | 
|  | addr.s_addr = destAddr; | 
|  | TRACE("name %s, dest addr %s\n", name, inet_ntoa(addr)); | 
|  |  | 
|  | if (broadcast) | 
|  | ret = setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (const char*)&on, sizeof(on)); | 
|  | if(ret == 0) | 
|  | { | 
|  | WSABUF wsaBuf; | 
|  | UCHAR buf[SIMPLE_NAME_QUERY_PKT_SIZE]; | 
|  | struct sockaddr_in sin; | 
|  |  | 
|  | memset(&sin, 0, sizeof(sin)); | 
|  | sin.sin_addr.s_addr = destAddr; | 
|  | sin.sin_family      = AF_INET; | 
|  | sin.sin_port        = htons(PORT_NBNS); | 
|  |  | 
|  | wsaBuf.buf = (CHAR*)buf; | 
|  | wsaBuf.len = NetBTNameReq(name, xid, qtype, broadcast, buf, | 
|  | sizeof(buf)); | 
|  | if (wsaBuf.len > 0) | 
|  | { | 
|  | DWORD bytesSent; | 
|  |  | 
|  | ret = WSASendTo(fd, &wsaBuf, 1, &bytesSent, 0, | 
|  | (struct sockaddr*)&sin, sizeof(sin), NULL, NULL); | 
|  | if (ret < 0 || bytesSent < wsaBuf.len) | 
|  | ret = -1; | 
|  | else | 
|  | ret = 0; | 
|  | } | 
|  | else | 
|  | ret = -1; | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | typedef BOOL (*NetBTAnswerCallback)(void *data, WORD answerCount, | 
|  | WORD answerIndex, PUCHAR rData, WORD rdLength); | 
|  |  | 
|  | /* Waits on fd until GetTickCount() returns a value greater than or equal to | 
|  | * waitUntil for a name service response.  If a name response matching xid | 
|  | * is received, calls answerCallback once for each answer resource record in | 
|  | * the response.  (The callback's answerCount will be the total number of | 
|  | * answers to expect, and answerIndex will be the 0-based index that's being | 
|  | * sent this time.)  Quits parsing if answerCallback returns FALSE. | 
|  | * Returns NRC_GOODRET on timeout or a valid response received, something else | 
|  | * on error. | 
|  | */ | 
|  | static UCHAR NetBTWaitForNameResponse(const NetBTAdapter *adapter, SOCKET fd, | 
|  | DWORD waitUntil, NetBTAnswerCallback answerCallback, void *data) | 
|  | { | 
|  | BOOL found = FALSE; | 
|  | DWORD now; | 
|  | UCHAR ret = NRC_GOODRET; | 
|  |  | 
|  | if (!adapter) return NRC_BADDR; | 
|  | if (fd == INVALID_SOCKET) return NRC_BADDR; | 
|  | if (!answerCallback) return NRC_BADDR; | 
|  |  | 
|  | while (!found && ret == NRC_GOODRET && (now = GetTickCount()) < waitUntil) | 
|  | { | 
|  | DWORD msToWait = waitUntil - now; | 
|  | struct fd_set fds; | 
|  | struct timeval timeout = { msToWait / 1000, msToWait % 1000 }; | 
|  | int r; | 
|  |  | 
|  | FD_ZERO(&fds); | 
|  | FD_SET(fd, &fds); | 
|  | r = select(fd + 1, &fds, NULL, NULL, &timeout); | 
|  | if (r < 0) | 
|  | ret = NRC_SYSTEM; | 
|  | else if (r == 1) | 
|  | { | 
|  | /* FIXME: magic #, is this always enough? */ | 
|  | UCHAR buffer[256]; | 
|  | int fromsize; | 
|  | struct sockaddr_in fromaddr; | 
|  | WORD respXID, flags, queryCount, answerCount; | 
|  | WSABUF wsaBuf = { sizeof(buffer), (CHAR*)buffer }; | 
|  | DWORD bytesReceived, recvFlags = 0; | 
|  |  | 
|  | fromsize = sizeof(fromaddr); | 
|  | r = WSARecvFrom(fd, &wsaBuf, 1, &bytesReceived, &recvFlags, | 
|  | (struct sockaddr*)&fromaddr, &fromsize, NULL, NULL); | 
|  | if(r < 0) | 
|  | { | 
|  | ret = NRC_SYSTEM; | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (bytesReceived < NBNS_HEADER_SIZE) | 
|  | continue; | 
|  |  | 
|  | respXID = NBR_GETWORD(buffer); | 
|  | if (adapter->nameQueryXID != respXID) | 
|  | continue; | 
|  |  | 
|  | flags = NBR_GETWORD(buffer + 2); | 
|  | queryCount = NBR_GETWORD(buffer + 4); | 
|  | answerCount = NBR_GETWORD(buffer + 6); | 
|  |  | 
|  | /* a reply shouldn't contain a query, ignore bad packet */ | 
|  | if (queryCount > 0) | 
|  | continue; | 
|  |  | 
|  | if ((flags & NBNS_RESPONSE_AND_OPCODE) == NBNS_RESPONSE_AND_QUERY) | 
|  | { | 
|  | if ((flags & NBNS_REPLYCODE) != 0) | 
|  | ret = NRC_NAMERR; | 
|  | else if ((flags & NBNS_REPLYCODE) == 0 && answerCount > 0) | 
|  | { | 
|  | PUCHAR ptr = buffer + NBNS_HEADER_SIZE; | 
|  | BOOL shouldContinue = TRUE; | 
|  | WORD answerIndex = 0; | 
|  |  | 
|  | found = TRUE; | 
|  | /* decode one answer at a time */ | 
|  | while (ret == NRC_GOODRET && answerIndex < answerCount && | 
|  | ptr - buffer < bytesReceived && shouldContinue) | 
|  | { | 
|  | WORD rLen; | 
|  |  | 
|  | /* scan past name */ | 
|  | for (; ptr[0] && ptr - buffer < bytesReceived; ) | 
|  | ptr += ptr[0] + 1; | 
|  | ptr++; | 
|  | ptr += 2; /* scan past type */ | 
|  | if (ptr - buffer < bytesReceived && ret == NRC_GOODRET | 
|  | && NBR_GETWORD(ptr) == NBNS_CLASS_INTERNET) | 
|  | ptr += sizeof(WORD); | 
|  | else | 
|  | ret = NRC_SYSTEM; /* parse error */ | 
|  | ptr += sizeof(DWORD); /* TTL */ | 
|  | rLen = NBR_GETWORD(ptr); | 
|  | rLen = min(rLen, bytesReceived - (ptr - buffer)); | 
|  | ptr += sizeof(WORD); | 
|  | shouldContinue = answerCallback(data, answerCount, | 
|  | answerIndex, ptr, rLen); | 
|  | ptr += rLen; | 
|  | answerIndex++; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | TRACE("returning 0x%02x\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | typedef struct _NetBTNameQueryData { | 
|  | NBNameCacheEntry *cacheEntry; | 
|  | UCHAR ret; | 
|  | } NetBTNameQueryData; | 
|  |  | 
|  | /* Name query callback function for NetBTWaitForNameResponse, creates a cache | 
|  | * entry on the first answer, adds each address as it's called again (as long | 
|  | * as there's space).  If there's an error that should be propagated as the | 
|  | * NetBIOS error, modifies queryData's ret member to the proper return code. | 
|  | */ | 
|  | static BOOL NetBTFindNameAnswerCallback(void *pVoid, WORD answerCount, | 
|  | WORD answerIndex, PUCHAR rData, WORD rLen) | 
|  | { | 
|  | NetBTNameQueryData *queryData = pVoid; | 
|  | BOOL ret; | 
|  |  | 
|  | if (queryData) | 
|  | { | 
|  | if (queryData->cacheEntry == NULL) | 
|  | { | 
|  | queryData->cacheEntry = HeapAlloc( | 
|  | GetProcessHeap(), 0, sizeof(NBNameCacheEntry) + | 
|  | (answerCount - 1) * sizeof(DWORD)); | 
|  | if (queryData->cacheEntry) | 
|  | queryData->cacheEntry->numAddresses = 0; | 
|  | else | 
|  | queryData->ret = NRC_OSRESNOTAV; | 
|  | } | 
|  | if (rLen == 6 && queryData->cacheEntry && | 
|  | queryData->cacheEntry->numAddresses < answerCount) | 
|  | { | 
|  | queryData->cacheEntry->addresses[queryData->cacheEntry-> | 
|  | numAddresses++] = *(const DWORD *)(rData + 2); | 
|  | ret = queryData->cacheEntry->numAddresses < answerCount; | 
|  | } | 
|  | else | 
|  | ret = FALSE; | 
|  | } | 
|  | else | 
|  | ret = FALSE; | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* Workhorse NetBT name lookup function.  Sends a name lookup query for | 
|  | * ncb->ncb_callname to sendTo, as a broadcast if broadcast is TRUE, using | 
|  | * adapter->nameQueryXID as the transaction ID.  Waits up to timeout | 
|  | * milliseconds, and retries up to maxQueries times, waiting for a reply. | 
|  | * If a valid response is received, stores the looked up addresses as a | 
|  | * NBNameCacheEntry in *cacheEntry. | 
|  | * Returns NRC_GOODRET on success, though this may not mean the name was | 
|  | * resolved--check whether *cacheEntry is NULL. | 
|  | */ | 
|  | static UCHAR NetBTNameWaitLoop(const NetBTAdapter *adapter, SOCKET fd, const NCB *ncb, | 
|  | DWORD sendTo, BOOL broadcast, DWORD timeout, DWORD maxQueries, | 
|  | NBNameCacheEntry **cacheEntry) | 
|  | { | 
|  | unsigned int queries; | 
|  | NetBTNameQueryData queryData; | 
|  |  | 
|  | if (!adapter) return NRC_BADDR; | 
|  | if (fd == INVALID_SOCKET) return NRC_BADDR; | 
|  | if (!ncb) return NRC_BADDR; | 
|  | if (!cacheEntry) return NRC_BADDR; | 
|  |  | 
|  | queryData.cacheEntry = NULL; | 
|  | queryData.ret = NRC_GOODRET; | 
|  | for (queries = 0; queryData.cacheEntry == NULL && queries < maxQueries; | 
|  | queries++) | 
|  | { | 
|  | if (!NCB_CANCELLED(ncb)) | 
|  | { | 
|  | int r = NetBTSendNameQuery(fd, ncb->ncb_callname, | 
|  | adapter->nameQueryXID, NBNS_TYPE_NB, sendTo, broadcast); | 
|  |  | 
|  | if (r == 0) | 
|  | queryData.ret = NetBTWaitForNameResponse(adapter, fd, | 
|  | GetTickCount() + timeout, NetBTFindNameAnswerCallback, | 
|  | &queryData); | 
|  | else | 
|  | queryData.ret = NRC_SYSTEM; | 
|  | } | 
|  | else | 
|  | queryData.ret = NRC_CMDCAN; | 
|  | } | 
|  | if (queryData.cacheEntry) | 
|  | { | 
|  | memcpy(queryData.cacheEntry->name, ncb->ncb_callname, NCBNAMSZ); | 
|  | memcpy(queryData.cacheEntry->nbname, ncb->ncb_callname, NCBNAMSZ); | 
|  | } | 
|  | *cacheEntry = queryData.cacheEntry; | 
|  | return queryData.ret; | 
|  | } | 
|  |  | 
|  | /* Attempts to add cacheEntry to the name cache in *nameCache; if *nameCache | 
|  | * has not yet been created, creates it, using gCacheTimeout as the cache | 
|  | * entry timeout.  If memory allocation fails, or if NBNameCacheAddEntry fails, | 
|  | * frees cacheEntry. | 
|  | * Returns NRC_GOODRET on success, and something else on failure. | 
|  | */ | 
|  | static UCHAR NetBTStoreCacheEntry(struct NBNameCache **nameCache, | 
|  | NBNameCacheEntry *cacheEntry) | 
|  | { | 
|  | UCHAR ret; | 
|  |  | 
|  | if (!nameCache) return NRC_BADDR; | 
|  | if (!cacheEntry) return NRC_BADDR; | 
|  |  | 
|  | if (!*nameCache) | 
|  | *nameCache = NBNameCacheCreate(GetProcessHeap(), gCacheTimeout); | 
|  | if (*nameCache) | 
|  | ret = NBNameCacheAddEntry(*nameCache, cacheEntry) | 
|  | ?  NRC_GOODRET : NRC_OSRESNOTAV; | 
|  | else | 
|  | { | 
|  | HeapFree(GetProcessHeap(), 0, cacheEntry); | 
|  | ret = NRC_OSRESNOTAV; | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* Attempts to resolve name using inet_addr(), then gethostbyname() if | 
|  | * gEnableDNS is TRUE, if the suffix byte is either <00> or <20>.  If the name | 
|  | * can be looked up, returns 0 and stores the looked up addresses as a | 
|  | * NBNameCacheEntry in *cacheEntry. | 
|  | * Returns NRC_GOODRET on success, though this may not mean the name was | 
|  | * resolved--check whether *cacheEntry is NULL.  Returns something else on | 
|  | * error. | 
|  | */ | 
|  | static UCHAR NetBTinetResolve(const UCHAR name[NCBNAMSZ], | 
|  | NBNameCacheEntry **cacheEntry) | 
|  | { | 
|  | UCHAR ret = NRC_GOODRET; | 
|  |  | 
|  | TRACE("name %s, cacheEntry %p\n", name, cacheEntry); | 
|  |  | 
|  | if (!name) return NRC_BADDR; | 
|  | if (!cacheEntry) return NRC_BADDR; | 
|  |  | 
|  | if (isalnum(name[0]) && (name[NCBNAMSZ - 1] == 0 || | 
|  | name[NCBNAMSZ - 1] == 0x20)) | 
|  | { | 
|  | CHAR toLookup[NCBNAMSZ]; | 
|  | unsigned int i; | 
|  |  | 
|  | for (i = 0; i < NCBNAMSZ - 1 && name[i] && name[i] != ' '; i++) | 
|  | toLookup[i] = name[i]; | 
|  | toLookup[i] = '\0'; | 
|  |  | 
|  | if (isdigit(toLookup[0])) | 
|  | { | 
|  | unsigned long addr = inet_addr(toLookup); | 
|  |  | 
|  | if (addr != INADDR_NONE) | 
|  | { | 
|  | *cacheEntry = HeapAlloc(GetProcessHeap(), | 
|  | 0, sizeof(NBNameCacheEntry)); | 
|  | if (*cacheEntry) | 
|  | { | 
|  | memcpy((*cacheEntry)->name, name, NCBNAMSZ); | 
|  | memset((*cacheEntry)->nbname, 0, NCBNAMSZ); | 
|  | (*cacheEntry)->nbname[0] = '*'; | 
|  | (*cacheEntry)->numAddresses = 1; | 
|  | (*cacheEntry)->addresses[0] = addr; | 
|  | } | 
|  | else | 
|  | ret = NRC_OSRESNOTAV; | 
|  | } | 
|  | } | 
|  | if (gEnableDNS && ret == NRC_GOODRET && !*cacheEntry) | 
|  | { | 
|  | struct hostent *host; | 
|  |  | 
|  | if ((host = gethostbyname(toLookup)) != NULL) | 
|  | { | 
|  | for (i = 0; ret == NRC_GOODRET && host->h_addr_list && | 
|  | host->h_addr_list[i]; i++) | 
|  | ; | 
|  | if (host->h_addr_list && host->h_addr_list[0]) | 
|  | { | 
|  | *cacheEntry = HeapAlloc( | 
|  | GetProcessHeap(), 0, sizeof(NBNameCacheEntry) + | 
|  | (i - 1) * sizeof(DWORD)); | 
|  | if (*cacheEntry) | 
|  | { | 
|  | memcpy((*cacheEntry)->name, name, NCBNAMSZ); | 
|  | memset((*cacheEntry)->nbname, 0, NCBNAMSZ); | 
|  | (*cacheEntry)->nbname[0] = '*'; | 
|  | (*cacheEntry)->numAddresses = i; | 
|  | for (i = 0; i < (*cacheEntry)->numAddresses; i++) | 
|  | (*cacheEntry)->addresses[i] = | 
|  | *(DWORD*)host->h_addr_list[i]; | 
|  | } | 
|  | else | 
|  | ret = NRC_OSRESNOTAV; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | TRACE("returning 0x%02x\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* Looks up the name in ncb->ncb_callname, first in the name caches (global | 
|  | * and this adapter's), then using gethostbyname(), next by WINS if configured, | 
|  | * and finally using broadcast NetBT name resolution.  In NBT parlance, this | 
|  | * makes this an "H-node".  Stores an entry in the appropriate name cache for a | 
|  | * found node, and returns it as *cacheEntry. | 
|  | * Assumes data, ncb, and cacheEntry are not NULL. | 
|  | * Returns NRC_GOODRET on success--which doesn't mean the name was resolved, | 
|  | * just that all name lookup operations completed successfully--and something | 
|  | * else on failure.  *cacheEntry will be NULL if the name was not found. | 
|  | */ | 
|  | static UCHAR NetBTInternalFindName(NetBTAdapter *adapter, PNCB ncb, | 
|  | const NBNameCacheEntry **cacheEntry) | 
|  | { | 
|  | UCHAR ret = NRC_GOODRET; | 
|  |  | 
|  | TRACE("adapter %p, ncb %p, cacheEntry %p\n", adapter, ncb, cacheEntry); | 
|  |  | 
|  | if (!cacheEntry) return NRC_BADDR; | 
|  | *cacheEntry = NULL; | 
|  |  | 
|  | if (!adapter) return NRC_BADDR; | 
|  | if (!ncb) return NRC_BADDR; | 
|  |  | 
|  | if (ncb->ncb_callname[0] == '*') | 
|  | ret = NRC_NOWILD; | 
|  | else | 
|  | { | 
|  | *cacheEntry = NBNameCacheFindEntry(gNameCache, ncb->ncb_callname); | 
|  | if (!*cacheEntry) | 
|  | *cacheEntry = NBNameCacheFindEntry(adapter->nameCache, | 
|  | ncb->ncb_callname); | 
|  | if (!*cacheEntry) | 
|  | { | 
|  | NBNameCacheEntry *newEntry = NULL; | 
|  |  | 
|  | ret = NetBTinetResolve(ncb->ncb_callname, &newEntry); | 
|  | if (ret == NRC_GOODRET && newEntry) | 
|  | { | 
|  | ret = NetBTStoreCacheEntry(&gNameCache, newEntry); | 
|  | if (ret != NRC_GOODRET) | 
|  | newEntry = NULL; | 
|  | } | 
|  | else | 
|  | { | 
|  | SOCKET fd = WSASocketA(PF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, | 
|  | 0, WSA_FLAG_OVERLAPPED); | 
|  |  | 
|  | if(fd == INVALID_SOCKET) | 
|  | ret = NRC_OSRESNOTAV; | 
|  | else | 
|  | { | 
|  | int winsNdx; | 
|  |  | 
|  | adapter->nameQueryXID++; | 
|  | for (winsNdx = 0; ret == NRC_GOODRET && *cacheEntry == NULL | 
|  | && winsNdx < gNumWINSServers; winsNdx++) | 
|  | ret = NetBTNameWaitLoop(adapter, fd, ncb, | 
|  | gWINSServers[winsNdx], FALSE, gWINSQueryTimeout, | 
|  | gWINSQueries, &newEntry); | 
|  | if (ret == NRC_GOODRET && newEntry) | 
|  | { | 
|  | ret = NetBTStoreCacheEntry(&gNameCache, newEntry); | 
|  | if (ret != NRC_GOODRET) | 
|  | newEntry = NULL; | 
|  | } | 
|  | if (ret == NRC_GOODRET && *cacheEntry == NULL) | 
|  | { | 
|  | DWORD bcastAddr = | 
|  | adapter->ipr.dwAddr & adapter->ipr.dwMask; | 
|  |  | 
|  | if (adapter->ipr.dwBCastAddr) | 
|  | bcastAddr |= ~adapter->ipr.dwMask; | 
|  | ret = NetBTNameWaitLoop(adapter, fd, ncb, bcastAddr, | 
|  | TRUE, gBCastQueryTimeout, gBCastQueries, &newEntry); | 
|  | if (ret == NRC_GOODRET && newEntry) | 
|  | { | 
|  | ret = NetBTStoreCacheEntry(&adapter->nameCache, | 
|  | newEntry); | 
|  | if (ret != NRC_GOODRET) | 
|  | newEntry = NULL; | 
|  | } | 
|  | } | 
|  | closesocket(fd); | 
|  | } | 
|  | } | 
|  | *cacheEntry = newEntry; | 
|  | } | 
|  | } | 
|  | TRACE("returning 0x%02x\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | typedef struct _NetBTNodeQueryData | 
|  | { | 
|  | BOOL gotResponse; | 
|  | PADAPTER_STATUS astat; | 
|  | WORD astatLen; | 
|  | } NetBTNodeQueryData; | 
|  |  | 
|  | /* Callback function for NetBTAstatRemote, parses the rData for the node | 
|  | * status and name list of the remote node.  Always returns FALSE, since | 
|  | * there's never more than one answer we care about in a node status response. | 
|  | */ | 
|  | static BOOL NetBTNodeStatusAnswerCallback(void *pVoid, WORD answerCount, | 
|  | WORD answerIndex, PUCHAR rData, WORD rLen) | 
|  | { | 
|  | NetBTNodeQueryData *data = pVoid; | 
|  |  | 
|  | if (data && !data->gotResponse && rData && rLen >= 1) | 
|  | { | 
|  | /* num names is first byte; each name is NCBNAMSZ + 2 bytes */ | 
|  | if (rLen >= rData[0] * (NCBNAMSZ + 2)) | 
|  | { | 
|  | WORD i; | 
|  | PUCHAR src; | 
|  | PNAME_BUFFER dst; | 
|  |  | 
|  | data->gotResponse = TRUE; | 
|  | data->astat->name_count = rData[0]; | 
|  | for (i = 0, src = rData + 1, | 
|  | dst = (PNAME_BUFFER)((PUCHAR)data->astat + | 
|  | sizeof(ADAPTER_STATUS)); | 
|  | i < data->astat->name_count && src - rData < rLen && | 
|  | (PUCHAR)dst - (PUCHAR)data->astat < data->astatLen; | 
|  | i++, dst++, src += NCBNAMSZ + 2) | 
|  | { | 
|  | UCHAR flags = *(src + NCBNAMSZ); | 
|  |  | 
|  | memcpy(dst->name, src, NCBNAMSZ); | 
|  | /* we won't actually see a registering name in the returned | 
|  | * response.  It's useful to see if no other flags are set; if | 
|  | * none are, then the name is registered. */ | 
|  | dst->name_flags = REGISTERING; | 
|  | if (flags & 0x80) | 
|  | dst->name_flags |= GROUP_NAME; | 
|  | if (flags & 0x10) | 
|  | dst->name_flags |= DEREGISTERED; | 
|  | if (flags & 0x08) | 
|  | dst->name_flags |= DUPLICATE; | 
|  | if (dst->name_flags == REGISTERING) | 
|  | dst->name_flags = REGISTERED; | 
|  | } | 
|  | /* arbitrarily set HW type to Ethernet */ | 
|  | data->astat->adapter_type = 0xfe; | 
|  | if (src - rData < rLen) | 
|  | memcpy(data->astat->adapter_address, src, | 
|  | min(rLen - (src - rData), 6)); | 
|  | } | 
|  | } | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | /* This uses the WINS timeout and query values, as they're the | 
|  | * UCAST_REQ_RETRY_TIMEOUT and UCAST_REQ_RETRY_COUNT according to the RFCs. | 
|  | */ | 
|  | static UCHAR NetBTAstatRemote(NetBTAdapter *adapter, PNCB ncb) | 
|  | { | 
|  | UCHAR ret = NRC_GOODRET; | 
|  | const NBNameCacheEntry *cacheEntry = NULL; | 
|  |  | 
|  | TRACE("adapter %p, NCB %p\n", adapter, ncb); | 
|  |  | 
|  | if (!adapter) return NRC_BADDR; | 
|  | if (!ncb) return NRC_INVADDRESS; | 
|  |  | 
|  | ret = NetBTInternalFindName(adapter, ncb, &cacheEntry); | 
|  | if (ret == NRC_GOODRET && cacheEntry) | 
|  | { | 
|  | if (cacheEntry->numAddresses > 0) | 
|  | { | 
|  | SOCKET fd = WSASocketA(PF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, 0, | 
|  | WSA_FLAG_OVERLAPPED); | 
|  |  | 
|  | if(fd == INVALID_SOCKET) | 
|  | ret = NRC_OSRESNOTAV; | 
|  | else | 
|  | { | 
|  | NetBTNodeQueryData queryData; | 
|  | DWORD queries; | 
|  | PADAPTER_STATUS astat = (PADAPTER_STATUS)ncb->ncb_buffer; | 
|  |  | 
|  | adapter->nameQueryXID++; | 
|  | astat->name_count = 0; | 
|  | queryData.gotResponse = FALSE; | 
|  | queryData.astat = astat; | 
|  | queryData.astatLen = ncb->ncb_length; | 
|  | for (queries = 0; !queryData.gotResponse && | 
|  | queries < gWINSQueries; queries++) | 
|  | { | 
|  | if (!NCB_CANCELLED(ncb)) | 
|  | { | 
|  | int r = NetBTSendNameQuery(fd, ncb->ncb_callname, | 
|  | adapter->nameQueryXID, NBNS_TYPE_NBSTAT, | 
|  | cacheEntry->addresses[0], FALSE); | 
|  |  | 
|  | if (r == 0) | 
|  | ret = NetBTWaitForNameResponse(adapter, fd, | 
|  | GetTickCount() + gWINSQueryTimeout, | 
|  | NetBTNodeStatusAnswerCallback, &queryData); | 
|  | else | 
|  | ret = NRC_SYSTEM; | 
|  | } | 
|  | else | 
|  | ret = NRC_CMDCAN; | 
|  | } | 
|  | closesocket(fd); | 
|  | } | 
|  | } | 
|  | else | 
|  | ret = NRC_CMDTMO; | 
|  | } | 
|  | else if (ret == NRC_CMDCAN) | 
|  | ; /* do nothing, we were cancelled */ | 
|  | else | 
|  | ret = NRC_CMDTMO; | 
|  | TRACE("returning 0x%02x\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static UCHAR NetBTAstat(void *adapt, PNCB ncb) | 
|  | { | 
|  | NetBTAdapter *adapter = adapt; | 
|  | UCHAR ret; | 
|  |  | 
|  | TRACE("adapt %p, NCB %p\n", adapt, ncb); | 
|  |  | 
|  | if (!adapter) return NRC_ENVNOTDEF; | 
|  | if (!ncb) return NRC_INVADDRESS; | 
|  | if (!ncb->ncb_buffer) return NRC_BADDR; | 
|  | if (ncb->ncb_length < sizeof(ADAPTER_STATUS)) return NRC_BUFLEN; | 
|  |  | 
|  | if (ncb->ncb_callname[0] == '*') | 
|  | { | 
|  | DWORD physAddrLen; | 
|  | MIB_IFROW ifRow; | 
|  | PADAPTER_STATUS astat = (PADAPTER_STATUS)ncb->ncb_buffer; | 
|  |  | 
|  | memset(astat, 0, sizeof(ADAPTER_STATUS)); | 
|  | astat->rev_major = 3; | 
|  | ifRow.dwIndex = adapter->ipr.dwIndex; | 
|  | if (GetIfEntry(&ifRow) != NO_ERROR) | 
|  | ret = NRC_BRIDGE; | 
|  | else | 
|  | { | 
|  | physAddrLen = min(ifRow.dwPhysAddrLen, 6); | 
|  | if (physAddrLen > 0) | 
|  | memcpy(astat->adapter_address, ifRow.bPhysAddr, physAddrLen); | 
|  | /* doubt anyone cares, but why not.. */ | 
|  | if (ifRow.dwType == MIB_IF_TYPE_TOKENRING) | 
|  | astat->adapter_type = 0xff; | 
|  | else | 
|  | astat->adapter_type = 0xfe; /* for Ethernet */ | 
|  | astat->max_sess_pkt_size = 0xffff; | 
|  | astat->xmit_success = adapter->xmit_success; | 
|  | astat->recv_success = adapter->recv_success; | 
|  | ret = NRC_GOODRET; | 
|  | } | 
|  | } | 
|  | else | 
|  | ret = NetBTAstatRemote(adapter, ncb); | 
|  | TRACE("returning 0x%02x\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static UCHAR NetBTFindName(void *adapt, PNCB ncb) | 
|  | { | 
|  | NetBTAdapter *adapter = adapt; | 
|  | UCHAR ret; | 
|  | const NBNameCacheEntry *cacheEntry = NULL; | 
|  | PFIND_NAME_HEADER foundName; | 
|  |  | 
|  | TRACE("adapt %p, NCB %p\n", adapt, ncb); | 
|  |  | 
|  | if (!adapter) return NRC_ENVNOTDEF; | 
|  | if (!ncb) return NRC_INVADDRESS; | 
|  | if (!ncb->ncb_buffer) return NRC_BADDR; | 
|  | if (ncb->ncb_length < sizeof(FIND_NAME_HEADER)) return NRC_BUFLEN; | 
|  |  | 
|  | foundName = (PFIND_NAME_HEADER)ncb->ncb_buffer; | 
|  | memset(foundName, 0, sizeof(FIND_NAME_HEADER)); | 
|  |  | 
|  | ret = NetBTInternalFindName(adapter, ncb, &cacheEntry); | 
|  | if (ret == NRC_GOODRET) | 
|  | { | 
|  | if (cacheEntry) | 
|  | { | 
|  | DWORD spaceFor = min((ncb->ncb_length - sizeof(FIND_NAME_HEADER)) / | 
|  | sizeof(FIND_NAME_BUFFER), cacheEntry->numAddresses); | 
|  | DWORD ndx; | 
|  |  | 
|  | for (ndx = 0; ndx < spaceFor; ndx++) | 
|  | { | 
|  | PFIND_NAME_BUFFER findNameBuffer; | 
|  |  | 
|  | findNameBuffer = | 
|  | (PFIND_NAME_BUFFER)((PUCHAR)foundName + | 
|  | sizeof(FIND_NAME_HEADER) + foundName->node_count * | 
|  | sizeof(FIND_NAME_BUFFER)); | 
|  | memset(findNameBuffer->destination_addr, 0, 2); | 
|  | memcpy(findNameBuffer->destination_addr + 2, | 
|  | &adapter->ipr.dwAddr, sizeof(DWORD)); | 
|  | memset(findNameBuffer->source_addr, 0, 2); | 
|  | memcpy(findNameBuffer->source_addr + 2, | 
|  | &cacheEntry->addresses[ndx], sizeof(DWORD)); | 
|  | foundName->node_count++; | 
|  | } | 
|  | if (spaceFor < cacheEntry->numAddresses) | 
|  | ret = NRC_BUFLEN; | 
|  | } | 
|  | else | 
|  | ret = NRC_CMDTMO; | 
|  | } | 
|  | TRACE("returning 0x%02x\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static UCHAR NetBTSessionReq(SOCKET fd, const UCHAR *calledName, | 
|  | const UCHAR *callingName) | 
|  | { | 
|  | UCHAR buffer[NBSS_HDRSIZE + MAX_DOMAIN_NAME_LEN * 2], ret; | 
|  | int r; | 
|  | unsigned int len = 0; | 
|  | DWORD bytesSent, bytesReceived, recvFlags = 0; | 
|  | WSABUF wsaBuf; | 
|  |  | 
|  | buffer[0] = NBSS_REQ; | 
|  | buffer[1] = 0; | 
|  |  | 
|  | len += NetBTNameEncode(calledName, &buffer[NBSS_HDRSIZE]); | 
|  | len += NetBTNameEncode(callingName, &buffer[NBSS_HDRSIZE + len]); | 
|  |  | 
|  | NBR_ADDWORD(&buffer[2], len); | 
|  |  | 
|  | wsaBuf.len = len + NBSS_HDRSIZE; | 
|  | wsaBuf.buf = (char*)buffer; | 
|  |  | 
|  | r = WSASend(fd, &wsaBuf, 1, &bytesSent, 0, NULL, NULL); | 
|  | if(r < 0 || bytesSent < len + NBSS_HDRSIZE) | 
|  | { | 
|  | ERR("send failed\n"); | 
|  | return NRC_SABORT; | 
|  | } | 
|  |  | 
|  | /* I've already set the recv timeout on this socket (if it supports it), so | 
|  | * just block.  Hopefully we'll always receive the session acknowledgement | 
|  | * within one timeout. | 
|  | */ | 
|  | wsaBuf.len = NBSS_HDRSIZE + 1; | 
|  | r = WSARecv(fd, &wsaBuf, 1, &bytesReceived, &recvFlags, NULL, NULL); | 
|  | if (r < 0 || bytesReceived < NBSS_HDRSIZE) | 
|  | ret = NRC_SABORT; | 
|  | else if (buffer[0] == NBSS_NACK) | 
|  | { | 
|  | if (r == NBSS_HDRSIZE + 1) | 
|  | { | 
|  | switch (buffer[NBSS_HDRSIZE]) | 
|  | { | 
|  | case NBSS_ERR_INSUFFICIENT_RESOURCES: | 
|  | ret = NRC_REMTFUL; | 
|  | break; | 
|  | default: | 
|  | ret = NRC_NOCALL; | 
|  | } | 
|  | } | 
|  | else | 
|  | ret = NRC_NOCALL; | 
|  | } | 
|  | else if (buffer[0] == NBSS_RETARGET) | 
|  | { | 
|  | FIXME("Got a session retarget, can't deal\n"); | 
|  | ret = NRC_NOCALL; | 
|  | } | 
|  | else if (buffer[0] == NBSS_ACK) | 
|  | ret = NRC_GOODRET; | 
|  | else | 
|  | ret = NRC_SYSTEM; | 
|  |  | 
|  | TRACE("returning 0x%02x\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static UCHAR NetBTCall(void *adapt, PNCB ncb, void **sess) | 
|  | { | 
|  | NetBTAdapter *adapter = adapt; | 
|  | UCHAR ret; | 
|  | const NBNameCacheEntry *cacheEntry = NULL; | 
|  |  | 
|  | TRACE("adapt %p, ncb %p\n", adapt, ncb); | 
|  |  | 
|  | if (!adapter) return NRC_ENVNOTDEF; | 
|  | if (!ncb) return NRC_INVADDRESS; | 
|  | if (!sess) return NRC_BADDR; | 
|  |  | 
|  | ret = NetBTInternalFindName(adapter, ncb, &cacheEntry); | 
|  | if (ret == NRC_GOODRET) | 
|  | { | 
|  | if (cacheEntry && cacheEntry->numAddresses > 0) | 
|  | { | 
|  | SOCKET fd; | 
|  |  | 
|  | fd = WSASocketA(PF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, | 
|  | WSA_FLAG_OVERLAPPED); | 
|  | if (fd != INVALID_SOCKET) | 
|  | { | 
|  | DWORD timeout; | 
|  | struct sockaddr_in sin; | 
|  |  | 
|  | if (ncb->ncb_rto > 0) | 
|  | { | 
|  | timeout = ncb->ncb_rto * 500; | 
|  | setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, | 
|  | sizeof(timeout)); | 
|  | } | 
|  | if (ncb->ncb_rto > 0) | 
|  | { | 
|  | timeout = ncb->ncb_sto * 500; | 
|  | setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, | 
|  | sizeof(timeout)); | 
|  | } | 
|  |  | 
|  | memset(&sin, 0, sizeof(sin)); | 
|  | memcpy(&sin.sin_addr, &cacheEntry->addresses[0], | 
|  | sizeof(sin.sin_addr)); | 
|  | sin.sin_family = AF_INET; | 
|  | sin.sin_port   = htons(PORT_NBSS); | 
|  | /* FIXME: use nonblocking mode for the socket, check the | 
|  | * cancel flag periodically | 
|  | */ | 
|  | if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) | 
|  | == SOCKET_ERROR) | 
|  | ret = NRC_CMDTMO; | 
|  | else | 
|  | { | 
|  | static const UCHAR fakedCalledName[] = "*SMBSERVER"; | 
|  | const UCHAR *calledParty = cacheEntry->nbname[0] == '*' | 
|  | ? fakedCalledName : cacheEntry->nbname; | 
|  |  | 
|  | ret = NetBTSessionReq(fd, calledParty, ncb->ncb_name); | 
|  | if (ret != NRC_GOODRET && calledParty[0] == '*') | 
|  | { | 
|  | FIXME("NBT session to \"*SMBSERVER\" refused,\n"); | 
|  | FIXME("should try finding name using ASTAT\n"); | 
|  | } | 
|  | } | 
|  | if (ret != NRC_GOODRET) | 
|  | closesocket(fd); | 
|  | else | 
|  | { | 
|  | NetBTSession *session = HeapAlloc( | 
|  | GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(NetBTSession)); | 
|  |  | 
|  | if (session) | 
|  | { | 
|  | session->fd = fd; | 
|  | InitializeCriticalSection(&session->cs); | 
|  | session->cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": NetBTSession.cs"); | 
|  | *sess = session; | 
|  | } | 
|  | else | 
|  | { | 
|  | ret = NRC_OSRESNOTAV; | 
|  | closesocket(fd); | 
|  | } | 
|  | } | 
|  | } | 
|  | else | 
|  | ret = NRC_OSRESNOTAV; | 
|  | } | 
|  | else | 
|  | ret = NRC_NAMERR; | 
|  | } | 
|  | TRACE("returning 0x%02x\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* Notice that I don't protect against multiple thread access to NetBTSend. | 
|  | * This is because I don't update any data in the adapter, and I only make a | 
|  | * single call to WSASend, which I assume to act atomically (not interleaving | 
|  | * data from other threads). | 
|  | * I don't lock, because I only depend on the fd being valid, and this won't be | 
|  | * true until a session setup is completed. | 
|  | */ | 
|  | static UCHAR NetBTSend(void *adapt, void *sess, PNCB ncb) | 
|  | { | 
|  | NetBTAdapter *adapter = adapt; | 
|  | NetBTSession *session = sess; | 
|  | UCHAR buffer[NBSS_HDRSIZE], ret; | 
|  | int r; | 
|  | WSABUF wsaBufs[2]; | 
|  | DWORD bytesSent; | 
|  |  | 
|  | TRACE("adapt %p, session %p, NCB %p\n", adapt, session, ncb); | 
|  |  | 
|  | if (!adapter) return NRC_ENVNOTDEF; | 
|  | if (!ncb) return NRC_INVADDRESS; | 
|  | if (!ncb->ncb_buffer) return NRC_BADDR; | 
|  | if (!session) return NRC_SNUMOUT; | 
|  | if (session->fd == INVALID_SOCKET) return NRC_SNUMOUT; | 
|  |  | 
|  | buffer[0] = NBSS_MSG; | 
|  | buffer[1] = 0; | 
|  | NBR_ADDWORD(&buffer[2], ncb->ncb_length); | 
|  |  | 
|  | wsaBufs[0].len = NBSS_HDRSIZE; | 
|  | wsaBufs[0].buf = (char*)buffer; | 
|  | wsaBufs[1].len = ncb->ncb_length; | 
|  | wsaBufs[1].buf = (char*)ncb->ncb_buffer; | 
|  |  | 
|  | r = WSASend(session->fd, wsaBufs, sizeof(wsaBufs) / sizeof(wsaBufs[0]), | 
|  | &bytesSent, 0, NULL, NULL); | 
|  | if (r == SOCKET_ERROR) | 
|  | { | 
|  | NetBIOSHangupSession(ncb); | 
|  | ret = NRC_SABORT; | 
|  | } | 
|  | else if (bytesSent < NBSS_HDRSIZE + ncb->ncb_length) | 
|  | { | 
|  | FIXME("Only sent %d bytes (of %d), hanging up session\n", bytesSent, | 
|  | NBSS_HDRSIZE + ncb->ncb_length); | 
|  | NetBIOSHangupSession(ncb); | 
|  | ret = NRC_SABORT; | 
|  | } | 
|  | else | 
|  | { | 
|  | ret = NRC_GOODRET; | 
|  | adapter->xmit_success++; | 
|  | } | 
|  | TRACE("returning 0x%02x\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static UCHAR NetBTRecv(void *adapt, void *sess, PNCB ncb) | 
|  | { | 
|  | NetBTAdapter *adapter = adapt; | 
|  | NetBTSession *session = sess; | 
|  | UCHAR buffer[NBSS_HDRSIZE], ret; | 
|  | int r; | 
|  | WSABUF wsaBufs[2]; | 
|  | DWORD bufferCount, bytesReceived, flags; | 
|  |  | 
|  | TRACE("adapt %p, session %p, NCB %p\n", adapt, session, ncb); | 
|  |  | 
|  | if (!adapter) return NRC_ENVNOTDEF; | 
|  | if (!ncb) return NRC_BADDR; | 
|  | if (!ncb->ncb_buffer) return NRC_BADDR; | 
|  | if (!session) return NRC_SNUMOUT; | 
|  | if (session->fd == INVALID_SOCKET) return NRC_SNUMOUT; | 
|  |  | 
|  | EnterCriticalSection(&session->cs); | 
|  | bufferCount = 0; | 
|  | if (session->bytesPending == 0) | 
|  | { | 
|  | bufferCount++; | 
|  | wsaBufs[0].len = NBSS_HDRSIZE; | 
|  | wsaBufs[0].buf = (char*)buffer; | 
|  | } | 
|  | wsaBufs[bufferCount].len = ncb->ncb_length; | 
|  | wsaBufs[bufferCount].buf = (char*)ncb->ncb_buffer; | 
|  | bufferCount++; | 
|  |  | 
|  | flags = 0; | 
|  | /* FIXME: should poll a bit so I can check the cancel flag */ | 
|  | r = WSARecv(session->fd, wsaBufs, bufferCount, &bytesReceived, &flags, | 
|  | NULL, NULL); | 
|  | if (r == SOCKET_ERROR && WSAGetLastError() != WSAEWOULDBLOCK) | 
|  | { | 
|  | LeaveCriticalSection(&session->cs); | 
|  | ERR("Receive error, WSAGetLastError() returns %d\n", WSAGetLastError()); | 
|  | NetBIOSHangupSession(ncb); | 
|  | ret = NRC_SABORT; | 
|  | } | 
|  | else if (NCB_CANCELLED(ncb)) | 
|  | { | 
|  | LeaveCriticalSection(&session->cs); | 
|  | ret = NRC_CMDCAN; | 
|  | } | 
|  | else | 
|  | { | 
|  | if (bufferCount == 2) | 
|  | { | 
|  | if (buffer[0] == NBSS_KEEPALIVE) | 
|  | { | 
|  | LeaveCriticalSection(&session->cs); | 
|  | FIXME("Oops, received a session keepalive and lost my place\n"); | 
|  | /* need to read another session header until we get a session | 
|  | * message header. */ | 
|  | NetBIOSHangupSession(ncb); | 
|  | ret = NRC_SABORT; | 
|  | goto error; | 
|  | } | 
|  | else if (buffer[0] != NBSS_MSG) | 
|  | { | 
|  | LeaveCriticalSection(&session->cs); | 
|  | FIXME("Received unexpected session msg type %d\n", buffer[0]); | 
|  | NetBIOSHangupSession(ncb); | 
|  | ret = NRC_SABORT; | 
|  | goto error; | 
|  | } | 
|  | else | 
|  | { | 
|  | if (buffer[1] & NBSS_EXTENSION) | 
|  | { | 
|  | LeaveCriticalSection(&session->cs); | 
|  | FIXME("Received a message that's too long for my taste\n"); | 
|  | NetBIOSHangupSession(ncb); | 
|  | ret = NRC_SABORT; | 
|  | goto error; | 
|  | } | 
|  | else | 
|  | { | 
|  | session->bytesPending = NBSS_HDRSIZE | 
|  | + NBR_GETWORD(&buffer[2]) - bytesReceived; | 
|  | ncb->ncb_length = bytesReceived - NBSS_HDRSIZE; | 
|  | LeaveCriticalSection(&session->cs); | 
|  | } | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | if (bytesReceived < session->bytesPending) | 
|  | session->bytesPending -= bytesReceived; | 
|  | else | 
|  | session->bytesPending = 0; | 
|  | LeaveCriticalSection(&session->cs); | 
|  | ncb->ncb_length = bytesReceived; | 
|  | } | 
|  | if (session->bytesPending > 0) | 
|  | ret = NRC_INCOMP; | 
|  | else | 
|  | { | 
|  | ret = NRC_GOODRET; | 
|  | adapter->recv_success++; | 
|  | } | 
|  | } | 
|  | error: | 
|  | TRACE("returning 0x%02x\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static UCHAR NetBTHangup(void *adapt, void *sess) | 
|  | { | 
|  | NetBTSession *session = sess; | 
|  |  | 
|  | TRACE("adapt %p, session %p\n", adapt, session); | 
|  |  | 
|  | if (!session) return NRC_SNUMOUT; | 
|  |  | 
|  | /* I don't lock the session, because NetBTRecv knows not to decrement | 
|  | * past 0, so if a receive completes after this it should still deal. | 
|  | */ | 
|  | closesocket(session->fd); | 
|  | session->fd = INVALID_SOCKET; | 
|  | session->bytesPending = 0; | 
|  | session->cs.DebugInfo->Spare[0] = 0; | 
|  | DeleteCriticalSection(&session->cs); | 
|  | HeapFree(GetProcessHeap(), 0, session); | 
|  |  | 
|  | return NRC_GOODRET; | 
|  | } | 
|  |  | 
|  | static void NetBTCleanupAdapter(void *adapt) | 
|  | { | 
|  | TRACE("adapt %p\n", adapt); | 
|  | if (adapt) | 
|  | { | 
|  | NetBTAdapter *adapter = adapt; | 
|  |  | 
|  | if (adapter->nameCache) | 
|  | NBNameCacheDestroy(adapter->nameCache); | 
|  | HeapFree(GetProcessHeap(), 0, adapt); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void NetBTCleanup(void) | 
|  | { | 
|  | TRACE("\n"); | 
|  | if (gNameCache) | 
|  | { | 
|  | NBNameCacheDestroy(gNameCache); | 
|  | gNameCache = NULL; | 
|  | } | 
|  | } | 
|  |  | 
|  | static UCHAR NetBTRegisterAdapter(const MIB_IPADDRROW *ipRow) | 
|  | { | 
|  | UCHAR ret; | 
|  | NetBTAdapter *adapter; | 
|  |  | 
|  | if (!ipRow) return NRC_BADDR; | 
|  |  | 
|  | adapter = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(NetBTAdapter)); | 
|  | if (adapter) | 
|  | { | 
|  | adapter->ipr = *ipRow; | 
|  | if (!NetBIOSRegisterAdapter(gTransportID, ipRow->dwIndex, adapter)) | 
|  | { | 
|  | NetBTCleanupAdapter(adapter); | 
|  | ret = NRC_SYSTEM; | 
|  | } | 
|  | else | 
|  | ret = NRC_GOODRET; | 
|  | } | 
|  | else | 
|  | ret = NRC_OSRESNOTAV; | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* Callback for NetBIOS adapter enumeration.  Assumes closure is a pointer to | 
|  | * a MIB_IPADDRTABLE containing all the IP adapters needed to be added to the | 
|  | * NetBIOS adapter table.  For each callback, checks if the passed-in adapt | 
|  | * has an entry in the table; if so, this adapter was enumerated previously, | 
|  | * and it's enabled.  As a flag, the table's dwAddr entry is changed to | 
|  | * INADDR_LOOPBACK, since this is an invalid address for a NetBT adapter. | 
|  | * The NetBTEnum function will add any remaining adapters from the | 
|  | * MIB_IPADDRTABLE to the NetBIOS adapter table. | 
|  | */ | 
|  | static BOOL NetBTEnumCallback(UCHAR totalLANAs, UCHAR lanaIndex, | 
|  | ULONG transport, const NetBIOSAdapterImpl *data, void *closure) | 
|  | { | 
|  | BOOL ret; | 
|  | PMIB_IPADDRTABLE table = closure; | 
|  |  | 
|  | if (table && data) | 
|  | { | 
|  | DWORD ndx; | 
|  |  | 
|  | ret = FALSE; | 
|  | for (ndx = 0; !ret && ndx < table->dwNumEntries; ndx++) | 
|  | { | 
|  | const NetBTAdapter *adapter = data->data; | 
|  |  | 
|  | if (table->table[ndx].dwIndex == adapter->ipr.dwIndex) | 
|  | { | 
|  | NetBIOSEnableAdapter(data->lana); | 
|  | table->table[ndx].dwAddr = INADDR_LOOPBACK; | 
|  | ret = TRUE; | 
|  | } | 
|  | } | 
|  | } | 
|  | else | 
|  | ret = FALSE; | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* Enumerates adapters by: | 
|  | * - retrieving the IP address table for the local machine | 
|  | * - eliminating loopback addresses from the table | 
|  | * - eliminating redundant addresses, that is, multiple addresses on the same | 
|  | *   subnet | 
|  | * Calls NetBIOSEnumAdapters, passing the resulting table as the callback | 
|  | * data.  The callback reenables each adapter that's already in the NetBIOS | 
|  | * table.  After NetBIOSEnumAdapters returns, this function adds any remaining | 
|  | * adapters to the NetBIOS table. | 
|  | */ | 
|  | static UCHAR NetBTEnum(void) | 
|  | { | 
|  | UCHAR ret; | 
|  | DWORD size = 0; | 
|  |  | 
|  | TRACE("\n"); | 
|  |  | 
|  | if (GetIpAddrTable(NULL, &size, FALSE) == ERROR_INSUFFICIENT_BUFFER) | 
|  | { | 
|  | PMIB_IPADDRTABLE ipAddrs, coalesceTable = NULL; | 
|  | DWORD numIPAddrs = (size - sizeof(MIB_IPADDRTABLE)) / | 
|  | sizeof(MIB_IPADDRROW) + 1; | 
|  |  | 
|  | ipAddrs = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size); | 
|  | if (ipAddrs) | 
|  | coalesceTable = HeapAlloc(GetProcessHeap(), | 
|  | HEAP_ZERO_MEMORY, sizeof(MIB_IPADDRTABLE) + | 
|  | (min(numIPAddrs, MAX_LANA + 1) - 1) * sizeof(MIB_IPADDRROW)); | 
|  | if (ipAddrs && coalesceTable) | 
|  | { | 
|  | if (GetIpAddrTable(ipAddrs, &size, FALSE) == ERROR_SUCCESS) | 
|  | { | 
|  | DWORD ndx; | 
|  |  | 
|  | for (ndx = 0; ndx < ipAddrs->dwNumEntries; ndx++) | 
|  | { | 
|  | if ((ipAddrs->table[ndx].dwAddr & | 
|  | ipAddrs->table[ndx].dwMask) != | 
|  | htonl((INADDR_LOOPBACK & IN_CLASSA_NET))) | 
|  | { | 
|  | BOOL newNetwork = TRUE; | 
|  | DWORD innerIndex; | 
|  |  | 
|  | /* make sure we don't have more than one entry | 
|  | * for a subnet */ | 
|  | for (innerIndex = 0; newNetwork && | 
|  | innerIndex < coalesceTable->dwNumEntries; innerIndex++) | 
|  | if ((ipAddrs->table[ndx].dwAddr & | 
|  | ipAddrs->table[ndx].dwMask) == | 
|  | (coalesceTable->table[innerIndex].dwAddr | 
|  | & coalesceTable->table[innerIndex].dwMask)) | 
|  | newNetwork = FALSE; | 
|  |  | 
|  | if (newNetwork) | 
|  | memcpy(&coalesceTable->table[ | 
|  | coalesceTable->dwNumEntries++], | 
|  | &ipAddrs->table[ndx], sizeof(MIB_IPADDRROW)); | 
|  | } | 
|  | } | 
|  |  | 
|  | NetBIOSEnumAdapters(gTransportID, NetBTEnumCallback, | 
|  | coalesceTable); | 
|  | ret = NRC_GOODRET; | 
|  | for (ndx = 0; ret == NRC_GOODRET && | 
|  | ndx < coalesceTable->dwNumEntries; ndx++) | 
|  | if (coalesceTable->table[ndx].dwAddr != INADDR_LOOPBACK) | 
|  | ret = NetBTRegisterAdapter(&coalesceTable->table[ndx]); | 
|  | } | 
|  | else | 
|  | ret = NRC_SYSTEM; | 
|  | HeapFree(GetProcessHeap(), 0, ipAddrs); | 
|  | HeapFree(GetProcessHeap(), 0, coalesceTable); | 
|  | } | 
|  | else | 
|  | ret = NRC_OSRESNOTAV; | 
|  | } | 
|  | else | 
|  | ret = NRC_SYSTEM; | 
|  | TRACE("returning 0x%02x\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static const WCHAR VxD_MSTCPW[] = { 'S','Y','S','T','E','M','\\','C','u','r', | 
|  | 'r','e','n','t','C','o','n','t','r','o','l','S','e','t','\\','S','e','r','v', | 
|  | 'i','c','e','s','\\','V','x','D','\\','M','S','T','C','P','\0' }; | 
|  | static const WCHAR NetBT_ParametersW[] = { 'S','Y','S','T','E','M','\\','C','u', | 
|  | 'r','r','e','n','t','C','o','n','t','r','o','l','S','e','t','\\','S','e','r', | 
|  | 'v','i','c','e','s','\\','N','e','t','B','T','\\','P','a','r','a','m','e','t', | 
|  | 'e','r','s','\0' }; | 
|  | static const WCHAR EnableDNSW[] = { 'E','n','a','b','l','e','D','N','S','\0' }; | 
|  | static const WCHAR BcastNameQueryCountW[] = { 'B','c','a','s','t','N','a','m', | 
|  | 'e','Q','u','e','r','y','C','o','u','n','t','\0' }; | 
|  | static const WCHAR BcastNameQueryTimeoutW[] = { 'B','c','a','s','t','N','a','m', | 
|  | 'e','Q','u','e','r','y','T','i','m','e','o','u','t','\0' }; | 
|  | static const WCHAR NameSrvQueryCountW[] = { 'N','a','m','e','S','r','v', | 
|  | 'Q','u','e','r','y','C','o','u','n','t','\0' }; | 
|  | static const WCHAR NameSrvQueryTimeoutW[] = { 'N','a','m','e','S','r','v', | 
|  | 'Q','u','e','r','y','T','i','m','e','o','u','t','\0' }; | 
|  | static const WCHAR ScopeIDW[] = { 'S','c','o','p','e','I','D','\0' }; | 
|  | static const WCHAR CacheTimeoutW[] = { 'C','a','c','h','e','T','i','m','e','o', | 
|  | 'u','t','\0' }; | 
|  | static const WCHAR Config_NetworkW[] = { 'S','o','f','t','w','a','r','e','\\', | 
|  | 'W','i','n','e','\\','N','e','t','w','o','r','k','\0' }; | 
|  |  | 
|  | /* Initializes global variables and registers the NetBT transport */ | 
|  | void NetBTInit(void) | 
|  | { | 
|  | HKEY hKey; | 
|  | NetBIOSTransport transport; | 
|  | LONG ret; | 
|  |  | 
|  | TRACE("\n"); | 
|  |  | 
|  | gEnableDNS = TRUE; | 
|  | gBCastQueries = BCAST_QUERIES; | 
|  | gBCastQueryTimeout = BCAST_QUERY_TIMEOUT; | 
|  | gWINSQueries = WINS_QUERIES; | 
|  | gWINSQueryTimeout = WINS_QUERY_TIMEOUT; | 
|  | gNumWINSServers = 0; | 
|  | memset(gWINSServers, 0, sizeof(gWINSServers)); | 
|  | gScopeID[0] = '\0'; | 
|  | gCacheTimeout = CACHE_TIMEOUT; | 
|  |  | 
|  | /* Try to open the Win9x NetBT configuration key */ | 
|  | ret = RegOpenKeyExW(HKEY_LOCAL_MACHINE, VxD_MSTCPW, 0, KEY_READ, &hKey); | 
|  | /* If that fails, try the WinNT NetBT configuration key */ | 
|  | if (ret != ERROR_SUCCESS) | 
|  | ret = RegOpenKeyExW(HKEY_LOCAL_MACHINE, NetBT_ParametersW, 0, KEY_READ, | 
|  | &hKey); | 
|  | if (ret == ERROR_SUCCESS) | 
|  | { | 
|  | DWORD dword, size; | 
|  |  | 
|  | size = sizeof(dword); | 
|  | if (RegQueryValueExW(hKey, EnableDNSW, NULL, NULL, | 
|  | (LPBYTE)&dword, &size) == ERROR_SUCCESS) | 
|  | gEnableDNS = dword; | 
|  | size = sizeof(dword); | 
|  | if (RegQueryValueExW(hKey, BcastNameQueryCountW, NULL, NULL, | 
|  | (LPBYTE)&dword, &size) == ERROR_SUCCESS && dword >= MIN_QUERIES | 
|  | && dword <= MAX_QUERIES) | 
|  | gBCastQueries = dword; | 
|  | size = sizeof(dword); | 
|  | if (RegQueryValueExW(hKey, BcastNameQueryTimeoutW, NULL, NULL, | 
|  | (LPBYTE)&dword, &size) == ERROR_SUCCESS && dword >= MIN_QUERY_TIMEOUT) | 
|  | gBCastQueryTimeout = dword; | 
|  | size = sizeof(dword); | 
|  | if (RegQueryValueExW(hKey, NameSrvQueryCountW, NULL, NULL, | 
|  | (LPBYTE)&dword, &size) == ERROR_SUCCESS && dword >= MIN_QUERIES | 
|  | && dword <= MAX_QUERIES) | 
|  | gWINSQueries = dword; | 
|  | size = sizeof(dword); | 
|  | if (RegQueryValueExW(hKey, NameSrvQueryTimeoutW, NULL, NULL, | 
|  | (LPBYTE)&dword, &size) == ERROR_SUCCESS && dword >= MIN_QUERY_TIMEOUT) | 
|  | gWINSQueryTimeout = dword; | 
|  | size = sizeof(gScopeID) - 1; | 
|  | if (RegQueryValueExW(hKey, ScopeIDW, NULL, NULL, (LPBYTE)gScopeID + 1, &size) | 
|  | == ERROR_SUCCESS) | 
|  | { | 
|  | /* convert into L2-encoded version, suitable for use by | 
|  | NetBTNameEncode */ | 
|  | char *ptr, *lenPtr; | 
|  |  | 
|  | for (ptr = gScopeID + 1, lenPtr = gScopeID; ptr - gScopeID < sizeof(gScopeID) && *ptr; ++ptr) | 
|  | { | 
|  | if (*ptr == '.') | 
|  | { | 
|  | lenPtr = ptr; | 
|  | *lenPtr = 0; | 
|  | } | 
|  | else | 
|  | { | 
|  | ++*lenPtr; | 
|  | } | 
|  | } | 
|  | } | 
|  | if (RegQueryValueExW(hKey, CacheTimeoutW, NULL, NULL, | 
|  | (LPBYTE)&dword, &size) == ERROR_SUCCESS && dword >= MIN_CACHE_TIMEOUT) | 
|  | gCacheTimeout = dword; | 
|  | RegCloseKey(hKey); | 
|  | } | 
|  | /* WINE-specific NetBT registry settings.  Because our adapter naming is | 
|  | * different than MS', we can't do per-adapter WINS configuration in the | 
|  | * same place.  Just do a global WINS configuration instead. | 
|  | */ | 
|  | /* @@ Wine registry key: HKCU\Software\Wine\Network */ | 
|  | if (RegOpenKeyW(HKEY_CURRENT_USER, Config_NetworkW, &hKey) == ERROR_SUCCESS) | 
|  | { | 
|  | static const char *nsValueNames[] = { "WinsServer", "BackupWinsServer" }; | 
|  | char nsString[16]; | 
|  | DWORD size, ndx; | 
|  |  | 
|  | for (ndx = 0; ndx < sizeof(nsValueNames) / sizeof(nsValueNames[0]); | 
|  | ndx++) | 
|  | { | 
|  | size = sizeof(nsString) / sizeof(char); | 
|  | if (RegQueryValueExA(hKey, nsValueNames[ndx], NULL, NULL, | 
|  | (LPBYTE)nsString, &size) == ERROR_SUCCESS) | 
|  | { | 
|  | unsigned long addr = inet_addr(nsString); | 
|  |  | 
|  | if (addr != INADDR_NONE && gNumWINSServers < MAX_WINS_SERVERS) | 
|  | gWINSServers[gNumWINSServers++] = addr; | 
|  | } | 
|  | } | 
|  | RegCloseKey(hKey); | 
|  | } | 
|  |  | 
|  | transport.enumerate      = NetBTEnum; | 
|  | transport.astat          = NetBTAstat; | 
|  | transport.findName       = NetBTFindName; | 
|  | transport.call           = NetBTCall; | 
|  | transport.send           = NetBTSend; | 
|  | transport.recv           = NetBTRecv; | 
|  | transport.hangup         = NetBTHangup; | 
|  | transport.cleanupAdapter = NetBTCleanupAdapter; | 
|  | transport.cleanup        = NetBTCleanup; | 
|  | memcpy(&gTransportID, TRANSPORT_NBT, sizeof(ULONG)); | 
|  | NetBIOSRegisterTransport(gTransportID, &transport); | 
|  | } |