/* 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); }