1555 lines
53 KiB
C
1555 lines
53 KiB
C
/* 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 = (NetBTNameQueryData *)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 = (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 = (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 = (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, (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 = (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 = (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 = (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 = (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 = (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;
|
|
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 = (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(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 = (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 = 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
|
|
&& dword <= MAX_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
|
|
&& dword <= MAX_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; ptr - gScopeID < sizeof(gScopeID) && *ptr; )
|
|
{
|
|
for (lenPtr = ptr - 1, *lenPtr = 0;
|
|
ptr - gScopeID < sizeof(gScopeID) && *ptr && *ptr != '.';
|
|
ptr++)
|
|
*lenPtr += 1;
|
|
ptr++;
|
|
}
|
|
}
|
|
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);
|
|
}
|