| /* Copyright (c) 2003 Juan Lang | |
| * | |
| * This library is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU Lesser General Public | |
| * License as published by the Free Software Foundation; either | |
| * version 2.1 of the License, or (at your option) any later version. | |
| * | |
| * This library is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| * Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; if not, write to the Free Software | |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
| * | |
| * 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 (NCBNAMSZ * 2 + MAX_DOMAIN_NAME_LEN + 2) | |
| #define SIMPLE_NAME_QUERY_PKT_SIZE 26 + MAX_NBT_NAME_SZ | |
| #define DEFAULT_NBT_SESSIONS 16 | |
| #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_DOMAIN_NAME_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, (PUCHAR)&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 = 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(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), 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 = (NetBTNameQueryData *)pVoid; | |
| BOOL ret; | |
| if (queryData) | |
| { | |
| if (queryData->cacheEntry == NULL) | |
| { | |
| queryData->cacheEntry = (NBNameCacheEntry *)HeapAlloc( | |
| GetProcessHeap(), 0, sizeof(NBNameCacheEntry) + | |
| (answerCount - 1) * sizeof(DWORD)); | |
| if (queryData->cacheEntry) | |
| queryData->cacheEntry->numAddresses = 0; | |
| else | |
| { | |
| ret = FALSE; | |
| queryData->ret = NRC_OSRESNOTAV; | |
| } | |
| } | |
| if (rLen == 6 && queryData->cacheEntry && | |
| queryData->cacheEntry->numAddresses < answerCount) | |
| { | |
| queryData->cacheEntry->addresses[queryData->cacheEntry-> | |
| numAddresses++] = *(PDWORD)(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(NetBTAdapter *adapter, SOCKET fd, PNCB 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)) | |
| { | |
| UCHAR 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 = (NBNameCacheEntry *)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 = (NBNameCacheEntry *)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 = (NetBTNodeQueryData *)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 = (NetBTAdapter *)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 = (NetBTAdapter *)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 = 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 = (NetBTAdapter *)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, (PUCHAR)&timeout, | |
| sizeof(timeout)); | |
| } | |
| if (ncb->ncb_rto > 0) | |
| { | |
| timeout = ncb->ncb_sto * 500; | |
| setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (PUCHAR)&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 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 = (NetBTSession *)HeapAlloc( | |
| GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(NetBTSession)); | |
| if (session) | |
| { | |
| session->fd = fd; | |
| InitializeCriticalSection(&session->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 = (NetBTAdapter *)adapt; | |
| NetBTSession *session = (NetBTSession *)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 = buffer; | |
| wsaBufs[1].len = ncb->ncb_length; | |
| wsaBufs[1].buf = 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 %ld 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 = (NetBTAdapter *)adapt; | |
| NetBTSession *session = (NetBTSession *)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 = buffer; | |
| } | |
| wsaBufs[bufferCount].len = ncb->ncb_length; | |
| wsaBufs[bufferCount].buf = 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; | |
| } | |
| else if (buffer[0] != NBSS_MSG) | |
| { | |
| LeaveCriticalSection(&session->cs); | |
| FIXME("Received unexpected session msg type %d\n", buffer[0]); | |
| NetBIOSHangupSession(ncb); | |
| ret = NRC_SABORT; | |
| } | |
| 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; | |
| } | |
| 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++; | |
| } | |
| } | |
| TRACE("returning 0x%02x\n", ret); | |
| return ret; | |
| } | |
| static UCHAR NetBTHangup(void *adapt, void *sess) | |
| { | |
| NetBTSession *session = (NetBTSession *)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; | |
| DeleteCriticalSection(&session->cs); | |
| HeapFree(GetProcessHeap(), 0, session); | |
| return NRC_GOODRET; | |
| } | |
| static void NetBTCleanupAdapter(void *adapt) | |
| { | |
| TRACE("adapt %p\n", adapt); | |
| if (adapt) | |
| { | |
| NetBTAdapter *adapter = (NetBTAdapter *)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(PMIB_IPADDRROW ipRow) | |
| { | |
| UCHAR ret; | |
| NetBTAdapter *adapter; | |
| if (!ipRow) return NRC_BADDR; | |
| adapter = (NetBTAdapter *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, | |
| sizeof(NetBTAdapter)); | |
| if (adapter) | |
| { | |
| memcpy(&adapter->ipr, ipRow, sizeof(MIB_IPADDRROW)); | |
| 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 = (PMIB_IPADDRTABLE)closure; | |
| if (table && data) | |
| { | |
| DWORD ndx; | |
| ret = FALSE; | |
| for (ndx = 0; !ret && ndx < table->dwNumEntries; ndx++) | |
| { | |
| const NetBTAdapter *adapter = (const NetBTAdapter *)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 = (PMIB_IPADDRTABLE)HeapAlloc(GetProcessHeap(), | |
| HEAP_ZERO_MEMORY, size); | |
| if (ipAddrs) | |
| coalesceTable = (PMIB_IPADDRTABLE)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; | |
| } | |
| /* 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 = RegOpenKeyExA(HKEY_LOCAL_MACHINE, | |
| "SYSTEM\\CurrentControlSet\\Services\\VxD\\MSTCP", 0, KEY_READ, &hKey); | |
| /* If that fails, try the WinNT NetBT configuration key */ | |
| if (ret != ERROR_SUCCESS) | |
| ret = RegOpenKeyExA(HKEY_LOCAL_MACHINE, | |
| "SYSTEM\\CurrentControlSet\\Services\\NetBT\\Parameters", 0, | |
| KEY_READ, &hKey); | |
| if (ret == ERROR_SUCCESS) | |
| { | |
| DWORD dword, size; | |
| size = sizeof(dword); | |
| if (RegQueryValueExA(hKey, "EnableDNS", NULL, NULL, | |
| (LPBYTE)&dword, &size) == ERROR_SUCCESS) | |
| gEnableDNS = dword; | |
| size = sizeof(dword); | |
| if (RegQueryValueExA(hKey, "BcastNameQueryCount", NULL, NULL, | |
| (LPBYTE)&dword, &size) == ERROR_SUCCESS && dword >= MIN_QUERIES | |
| && dword <= MAX_QUERIES) | |
| gBCastQueries = dword; | |
| size = sizeof(dword); | |
| if (RegQueryValueExA(hKey, "BcastNameQueryTimeout", NULL, NULL, | |
| (LPBYTE)&dword, &size) == ERROR_SUCCESS && dword >= MIN_QUERY_TIMEOUT | |
| && dword <= MAX_QUERY_TIMEOUT) | |
| gBCastQueryTimeout = dword; | |
| size = sizeof(dword); | |
| if (RegQueryValueExA(hKey, "NameSrvQueryCount", NULL, NULL, | |
| (LPBYTE)&dword, &size) == ERROR_SUCCESS && dword >= MIN_QUERIES | |
| && dword <= MAX_QUERIES) | |
| gWINSQueries = dword; | |
| size = sizeof(dword); | |
| if (RegQueryValueExA(hKey, "NameSrvQueryTimeout", NULL, NULL, | |
| (LPBYTE)&dword, &size) == ERROR_SUCCESS && dword >= MIN_QUERY_TIMEOUT | |
| && dword <= MAX_QUERY_TIMEOUT) | |
| gWINSQueryTimeout = dword; | |
| size = MAX_DOMAIN_NAME_LEN - 1; | |
| if (RegQueryValueExA(hKey, "ScopeID", NULL, NULL, gScopeID + 1, &size) | |
| == ERROR_SUCCESS) | |
| { | |
| /* convert into L2-encoded version, suitable for use by | |
| NetBTNameEncode */ | |
| char *ptr, *lenPtr; | |
| for (ptr = gScopeID + 1; *ptr && | |
| ptr - gScopeID < MAX_DOMAIN_NAME_LEN; ) | |
| { | |
| for (lenPtr = ptr - 1, *lenPtr = 0; *ptr && *ptr != '.' && | |
| ptr - gScopeID < MAX_DOMAIN_NAME_LEN; ptr++) | |
| *lenPtr += 1; | |
| ptr++; | |
| } | |
| } | |
| if (RegQueryValueExA(hKey, "CacheTimeout", 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. | |
| */ | |
| if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, | |
| "Software\\Wine\\Wine\\Config\\Network", 0, KEY_READ, &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); | |
| } |