/*++ Copyright (c) 1991 Microsoft Corporation Module Name: Registry.c Abstract: This contains all routines necessary to load device pathnames from the registry. Author: Jim Stewart (Jimst) October 9 1992 Revision History: Jiandong Ruan (jruan) April 6 2000 Add NbtReadRegistryCleanup Notes: --*/ #include "precomp.h" // // Local functions used to access the registry. // NTSTATUS NbtOpenRegistry( IN HANDLE NbConfigHandle, IN PWSTR String, OUT PHANDLE pHandle ); VOID NbtCloseRegistry( IN HANDLE LinkageHandle, IN HANDLE ParametersHandle ); NTSTATUS NbtReadLinkageInformation( IN PWSTR pName, IN HANDLE LinkageHandle, IN ULONG MaxBindings, OUT tDEVICES *pDevices, // place to put read in config data OUT ULONG *pNumDevices ); NTSTATUS OpenAndReadElement( IN PUNICODE_STRING pucRootPath, IN PWSTR pwsValueName, OUT PUNICODE_STRING pucString ); NTSTATUS GetIpAddressesList ( IN HANDLE ParametersHandle, IN PWSTR pwsKeyName, IN ULONG MaxAddresses, OUT tIPADDRESS *pAddrArray, OUT ULONG *pNumGoodAddresses ); NTSTATUS GetServerAddress ( IN HANDLE ParametersHandle, IN PWSTR KeyName, OUT PULONG pIpAddr ); NTSTATUS NbtAppendString ( IN PWSTR FirstString, IN PWSTR SecondString, OUT PUNICODE_STRING pucString ); NTSTATUS ReadStringRelative( IN PUNICODE_STRING pRegistryPath, IN PWSTR pRelativePath, IN PWSTR pValueName, OUT PUNICODE_STRING pOutString ); VOID NbtFindLastSlash( IN PUNICODE_STRING pucRegistryPath, OUT PWSTR *ppucLastElement, IN int *piLength ); NTSTATUS ReadSmbDeviceInfo( IN HANDLE NbConfigHandle ); //******************* Pageable Routine Declarations **************** #ifdef ALLOC_PRAGMA #pragma CTEMakePageable(PAGE, NbtReadRegistry) #pragma CTEMakePageable(PAGE, NbtReadRegistryCleanup) #pragma CTEMakePageable(PAGE, ReadNameServerAddresses) #pragma CTEMakePageable(PAGE, GetIpAddressesList) #pragma CTEMakePageable(PAGE, GetServerAddress) #pragma CTEMakePageable(PAGE, NTReadIniString) #pragma CTEMakePageable(PAGE, GetIPFromRegistry) #pragma CTEMakePageable(PAGE, NbtOpenRegistry) #pragma CTEMakePageable(PAGE, NbtParseMultiSzEntries) #pragma CTEMakePageable(PAGE, NbtReadLinkageInformation) #pragma CTEMakePageable(PAGE, NbtReadSingleParameter) #pragma CTEMakePageable(PAGE, OpenAndReadElement) #pragma CTEMakePageable(PAGE, ReadElement) #pragma CTEMakePageable(PAGE, NTGetLmHostPath) #pragma CTEMakePageable(PAGE, ReadStringRelative) #pragma CTEMakePageable(PAGE, NbtFindLastSlash) #pragma CTEMakePageable(PAGE, ReadSmbDeviceInfo) #endif //******************* Pageable Routine Declarations **************** //---------------------------------------------------------------------------- NTSTATUS NbtReadRegistry( OUT tDEVICES **ppBindDevices, OUT tDEVICES **ppExportDevices, OUT tADDRARRAY **ppAddrArray ) /*++ Routine Description: This routine is called to get information from the registry, starting at RegistryPath to get the parameters. This routine must be called with the NbtConfig.Resource lock HELD Arguments: Before calling this routine, the following Global parameters must have been initialized (in DriverEntry): NbtConfig.pRegistry Return Value: NTSTATUS - STATUS_SUCCESS if everything OK, STATUS_INSUFFICIENT_RESOURCES otherwise. --*/ { NTSTATUS OpenStatus; HANDLE LinkageHandle; HANDLE ParametersHandle; HANDLE NbtConfigHandle; NTSTATUS Status; ULONG Disposition; OBJECT_ATTRIBUTES TmpObjectAttributes; PWSTR LinkageString = L"Linkage"; PWSTR ParametersString = L"Parameters"; tDEVICES *pBindDevices; tDEVICES *pExportDevices; UNICODE_STRING ucString; ULONG NumBindings; CTEPagedCode(); *ppExportDevices = *ppBindDevices = NULL; *ppAddrArray = NULL; // // Open the registry. // InitializeObjectAttributes (&TmpObjectAttributes, &NbtConfig.pRegistry, // name OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, // attributes NULL, // root NULL); // security descriptor Status = ZwCreateKey (&NbtConfigHandle, KEY_READ, &TmpObjectAttributes, 0, // title index NULL, // class 0, // create options &Disposition); // disposition if (!NT_SUCCESS(Status)) { KdPrint (("Nbt.NbtReadRegistry: ZwCreateKey FAILed, status=<%x>\n", Status)); NbtLogEvent (EVENT_NBT_CREATE_DRIVER, Status, 0x114); return STATUS_UNSUCCESSFUL; } OpenStatus = NbtOpenRegistry (NbtConfigHandle, LinkageString, &LinkageHandle); if (NT_SUCCESS(OpenStatus)) { OpenStatus = NbtOpenRegistry (NbtConfigHandle, ParametersString, &ParametersHandle); if (NT_SUCCESS(OpenStatus)) { // // Read in the binding information (if none is present // the array will be filled with all known drivers). // if (pBindDevices = NbtAllocMem ((sizeof(tDEVICES)+2*NBT_MAXIMUM_BINDINGS*sizeof(UNICODE_STRING)), NBT_TAG2('25'))) { if (pExportDevices=NbtAllocMem((sizeof(tDEVICES)+2*NBT_MAXIMUM_BINDINGS*sizeof(UNICODE_STRING)), NBT_TAG2('26'))) { ReadParameters (&NbtConfig, ParametersHandle);// Read various parameters from the registry ReadSmbDeviceInfo (NbtConfigHandle); // Set the information for the SmbDevice // // From now on, the only failures we can encounter are in reading the // Bind, Export or Name Server address entries, hence if we fail here, // we will still return success, but will assume 0 devices configured! // pBindDevices->RegistryData = pExportDevices->RegistryData = NULL; Status = NbtReadLinkageInformation (NBT_BIND, LinkageHandle, 2*NBT_MAXIMUM_BINDINGS, pBindDevices, &NumBindings); if (!NT_SUCCESS(Status)) { KdPrint (("Nbt.NbtReadRegistry: NbtReadLinkageInformation FAILed - BIND <%x>\n", Status)); NbtLogEvent (EVENT_NBT_READ_BIND, Status, 0x115); } else // if (NT_SUCCESS(Status)) { IF_DBG(NBT_DEBUG_NTUTIL) KdPrint(("Binddevice = %ws\n",pBindDevices->Names[0].Buffer)); NbtConfig.uNumDevicesInRegistry = (USHORT) NumBindings; NumBindings = 0; // Read the EXPORT information as well. Status = NbtReadLinkageInformation (NBT_EXPORT, LinkageHandle, 2*NBT_MAXIMUM_BINDINGS, pExportDevices, &NumBindings); if (NT_SUCCESS(Status)) { // we want the lowest number for num devices in case there // are more bindings than exports or viceversa // // ASSERT (NumBindings == NbtConfig.uNumDevicesInRegistry); NbtConfig.uNumDevicesInRegistry = (USHORT) (NbtConfig.uNumDevicesInRegistry > NumBindings ? NumBindings : NbtConfig.uNumDevicesInRegistry); if (NbtConfig.uNumDevicesInRegistry == 0) { KdPrint (("Nbt.NbtReadRegistry: WARNING - NumDevicesInRegistry = 0\n")); } } else { KdPrint (("Nbt.NbtReadRegistry: NbtReadLinkageInformation FAILed - EXPORT <%x>\n", Status)); NbtLogEvent (EVENT_NBT_READ_EXPORT, Status, 0x116); } } if ((NT_SUCCESS(Status)) && (NbtConfig.uNumDevicesInRegistry)) { IF_DBG(NBT_DEBUG_NTUTIL) KdPrint(("Exportdevice = %ws\n",pExportDevices->Names[0].Buffer)); // // read in the NameServer IP address now // Status = ReadNameServerAddresses (NbtConfigHandle, pBindDevices, NbtConfig.uNumDevicesInRegistry, ppAddrArray); if (!NT_SUCCESS(Status)) { if (!(NodeType & BNODE)) // Post Warning! { NbtLogEvent (EVENT_NBT_NAME_SERVER_ADDRS, Status, 0x118); } KdPrint(("Nbt.NbtReadRegistry: ReadNameServerAddresses returned <%x>\n", Status)); } else // if (NT_SUCCESS(Status)) { // // check if any WINS servers have been configured change // to Hnode // if (NodeType & (BNODE | DEFAULT_NODE_TYPE)) { ULONG i; for (i=0; iRegistryData) { CTEMemFree(pBindDevices->RegistryData); } CTEMemFree(pBindDevices); if (pExportDevices->RegistryData) { CTEMemFree(pExportDevices->RegistryData); } CTEMemFree(pExportDevices); pBindDevices = pExportDevices = NULL; NbtConfig.uNumDevicesInRegistry = 0; Status = STATUS_SUCCESS; } // // we have done the check for default node so turn off // the flag // NodeType &= ~DEFAULT_NODE_TYPE; // // A Bnode cannot be a proxy too // if (NodeType & BNODE) { if (NodeType & PROXY) { NodeType &= ~PROXY; } } // keep the size around for allocating memory, so that when we run over // OSI, only this value should change (in theory at least) NbtConfig.SizeTransportAddress = sizeof(TDI_ADDRESS_IP); // fill in the node type value that is put into all name service Pdus // that go out identifying this node type switch (NodeType & NODE_MASK) { case BNODE: NbtConfig.PduNodeType = 0; break; case PNODE: NbtConfig.PduNodeType = 1 << 13; break; case MNODE: NbtConfig.PduNodeType = 1 << 14; break; case MSNODE: NbtConfig.PduNodeType = 3 << 13; break; } // read the name of the transport to bind to // if (NT_SUCCESS(ReadElement(ParametersHandle, WS_TRANSPORT_BIND_NAME, &ucString))) { UNICODE_STRING StreamsString; // // if there is already a bind string, free it before // allocating another // if (NbtConfig.pTcpBindName) { // // CreateDeviceString in tdicnct.c could access the pTcpBindName right // after it is freed. The right way is using a lock. But, ... // // Hack!!!: // Although this doesn't completely fix the problem, it has the minimum // side-effect. // // The value of WS_TRANSPORT_BIND_NAME won't change. By doing this, // we avoid the possible access-after-free problem in most cases. // RtlInitUnicodeString(&StreamsString, NbtConfig.pTcpBindName); if (RtlCompareUnicodeString(&ucString,&StreamsString,TRUE)) { CTEMemFree(NbtConfig.pTcpBindName); NbtConfig.pTcpBindName = ucString.Buffer; } else { CTEMemFree(ucString.Buffer); ucString = StreamsString; } } else { NbtConfig.pTcpBindName = ucString.Buffer; } // ********** REMOVE LATER *********** RtlInitUnicodeString(&StreamsString,NBT_TCP_BIND_NAME); if (RtlCompareUnicodeString(&ucString,&StreamsString,TRUE)) { StreamsStack = FALSE; } else { StreamsStack = TRUE; } } else { StreamsStack = TRUE; } ZwClose(ParametersHandle); ZwClose(LinkageHandle); ZwClose(NbtConfigHandle); *ppExportDevices = pExportDevices; *ppBindDevices = pBindDevices; return (Status); } else { KdPrint (("Nbt.NbtReadRegistry: FAILed to allocate pExportDevices\n")); } CTEMemFree(pBindDevices); } else { KdPrint (("Nbt.NbtReadRegistry: FAILed to allocate pBindDevices\n")); } ZwClose(ParametersHandle); } else { KdPrint (("Nbt.NbtReadRegistry: NbtOpenRegistry FAILed for PARAMETERS, status=<%x>\n", Status)); NbtLogEvent (EVENT_NBT_OPEN_REG_PARAMS, OpenStatus, 0x119); } ZwClose(LinkageHandle); } else { KdPrint (("Nbt.NbtReadRegistry: NbtOpenRegistry FAILed for LINKAGE, status=<%x>\n", Status)); NbtLogEvent (EVENT_NBT_OPEN_REG_LINKAGE, OpenStatus, 0x120); } ZwClose (NbtConfigHandle); return STATUS_UNSUCCESSFUL; } //---------------------------------------------------------------------------- VOID NbtReadRegistryCleanup( IN tDEVICES **ppBindDevices, IN tDEVICES **ppExportDevices, IN tADDRARRAY **ppAddrArray ) /*++ Routine Description: This routine is called to release resources allocated by NbtReadRegistry ++*/ { CTEPagedCode(); if (ppBindDevices[0]) { CTEMemFree((PVOID)ppBindDevices[0]->RegistryData); CTEMemFree((PVOID)ppBindDevices[0]); ppBindDevices[0] = NULL; } if (ppExportDevices[0]) { CTEMemFree((PVOID)ppExportDevices[0]->RegistryData); CTEMemFree((PVOID)ppExportDevices[0]); ppExportDevices[0] = NULL; } if (ppAddrArray[0]) { CTEMemFree((PVOID)ppAddrArray[0]); ppAddrArray[0] = NULL; } } NTSTATUS ReadSmbDeviceInfo( IN HANDLE NbtConfigHandle ) { HANDLE SmbHandle; NTSTATUS Status; CTEPagedCode(); Status = NbtOpenRegistry (NbtConfigHandle, WC_SMB_PARAMETERS_LOCATION, &SmbHandle); if (NT_SUCCESS(Status)) { NbtConfig.DefaultSmbSessionPort = (USHORT) CTEReadSingleIntParameter (SmbHandle, SESSION_PORT, NBT_SMB_SESSION_TCP_PORT, 1); NbtConfig.DefaultSmbDatagramPort = (USHORT) CTEReadSingleIntParameter (SmbHandle, DATAGRAM_PORT, NBT_SMB_DATAGRAM_UDP_PORT, 1); ZwClose (SmbHandle); } else { NbtConfig.DefaultSmbSessionPort = NBT_SMB_SESSION_TCP_PORT; NbtConfig.DefaultSmbDatagramPort = NBT_SMB_DATAGRAM_UDP_PORT; } return (Status); } //---------------------------------------------------------------------------- NTSTATUS ReadNameServerAddresses ( IN HANDLE NbtConfigHandle, IN tDEVICES *BindDevices, IN ULONG NumberDevices, OUT tADDRARRAY **ppAddrArray ) /*++ Routine Description: This routine is called to read the name server addresses from the registry. It stores them in a data structure that it allocates. This memory is subsequently freed in driver.c when the devices have been created. Arguments: ConfigurationInfo - A pointer to the configuration information structure. Return Value: None. --*/ { #define ADAPTER_SIZE_MAX 400 UNICODE_STRING ucString; NTSTATUS status = STATUS_UNSUCCESSFUL; HANDLE Handle; LONG i,j,Len; PWSTR pwsAdapter = L"Parameters\\Interfaces\\"; PWSTR BackSlash = L"\\"; tADDRARRAY *pAddrArray; ULONG LenAdapter; #ifdef _NETBIOSLESS ULONG Options; #endif ULONG NumNameServerAddresses = 0; CTEPagedCode(); *ppAddrArray = NULL; // this is large enough for 400 characters of adapter name. ucString.Buffer = NbtAllocMem (ADAPTER_SIZE_MAX, NBT_TAG2('27')); if (!ucString.Buffer) { return(STATUS_INSUFFICIENT_RESOURCES); } pAddrArray = NbtAllocMem (sizeof(tADDRARRAY)*NumberDevices, NBT_TAG2('28')); if (!pAddrArray) { CTEMemFree(ucString.Buffer); return(STATUS_INSUFFICIENT_RESOURCES); } CTEZeroMemory(pAddrArray,sizeof(tADDRARRAY)*NumberDevices); *ppAddrArray = pAddrArray; // get the adapter name out of the Bind string, and use it to open // a key by the same name, to get the name server addresses // for (i = 0;i < (LONG)NumberDevices ;i ++ ) { WCHAR *pBuffer; Len = BindDevices->Names[i].Length/sizeof(WCHAR); Len--; // // start at the end a work backwards looking for a '\' // j = Len; pBuffer = &BindDevices->Names[i].Buffer[j]; while (j) { if (*pBuffer != *BackSlash) { j--; pBuffer--; } else break; } // if we don't find a backslash or at least one // character name then continue around again, or the name // is longer than the buffer, then go to the next device in the // bind list // if ((j == 0) || (j == Len) || (j == Len -1) || ((Len - j) > ADAPTER_SIZE_MAX)) { continue; } // copy the string "Adapter\" to the buffer since the adapters all // appear under this key in the registery // LenAdapter = wcslen(pwsAdapter); CTEMemCopy(ucString.Buffer, pwsAdapter, LenAdapter*sizeof(WCHAR)); // // copy just the adapter name from the Bind string, since that is // the name of the key to open to find the name server ip addresses // CTEMemCopy(&ucString.Buffer[LenAdapter], ++pBuffer, (Len - j)*sizeof(WCHAR)); ucString.Buffer[Len - j + LenAdapter] = 0; pAddrArray->NameServerAddress = LOOP_BACK; pAddrArray->BackupServer = LOOP_BACK; #ifdef MULTIPLE_WINS pAddrArray->Others[0] = LOOP_BACK; // For Safety pAddrArray->NumOtherServers = 0; pAddrArray->LastResponsive = 0; #endif status = NbtOpenRegistry (NbtConfigHandle, ucString.Buffer, &Handle); if (NT_SUCCESS(status)) { status = GetIpAddressesList(Handle, // Generic routine to read in list of Ip addresses PWS_NAME_SERVER_LIST, 2+MAX_NUM_OTHER_NAME_SERVERS, pAddrArray->AllNameServers, &NumNameServerAddresses); if (!NT_SUCCESS(status) || (pAddrArray->NameServerAddress == LOOP_BACK)) { NumNameServerAddresses = 0; status = GetIpAddressesList(Handle, PWS_DHCP_NAME_SERVER_LIST, 2+MAX_NUM_OTHER_NAME_SERVERS, pAddrArray->AllNameServers, &NumNameServerAddresses); } // // Continue even if we failed to read in any IP addresses // if (NumNameServerAddresses > 2) { pAddrArray->NumOtherServers = (USHORT) NumNameServerAddresses - 2; } #ifdef _NETBIOSLESS // NbtReadSingle doesn't quite do what we want. In this case, if the non-dhcp- // decorated option is present but zero, we DO want to go on to the dhcp- // decorated one. So, try the dhcp-decorated one explicitly if we get back zero. Options = NbtReadSingleParameter( Handle, PWS_NETBIOS_OPTIONS, 0, 0 ); if (Options == 0) { Options = NbtReadSingleParameter( Handle, PWS_DHCP_NETBIOS_OPTIONS, 0, 0 ); } // Options is encoded as four bytes // Each byte can be an independent set of flags // The high order three bytes can be used for controlling other aspects // Enabled option, default is TRUE pAddrArray->NetbiosEnabled = ((Options & 0xff) != NETBT_MODE_NETBIOS_DISABLED); #endif pAddrArray->RasProxyFlags = NbtReadSingleParameter(Handle, PWS_RAS_PROXY_FLAGS, 0, 0); pAddrArray->EnableNagling = (NbtReadSingleParameter(Handle, PWS_ENABLE_NAGLING, 0, 0) != FALSE); // don't want to fail this routine just because the // name server address was not set status = STATUS_SUCCESS; ZwClose(Handle); } pAddrArray++; } CTEMemFree(ucString.Buffer); return(STATUS_SUCCESS); } //---------------------------------------------------------------------------- NTSTATUS GetIpAddressesList ( IN HANDLE ParametersHandle, IN PWSTR pwsKeyName, IN ULONG MaxAddresses, OUT tIPADDRESS *pAddrArray, OUT ULONG *pNumGoodAddresses ) /*++ Routine Description: This routine is called to read a list of Ip addresses from the registry. Arguments: Return Value: None. --*/ { ULONG NumEntriesRead, NumGoodAddresses, NumAddressesAttempted; tDEVICES *pucAddressList; NTSTATUS Status; STRING String; ULONG IpAddr; PWSTR DhcpName = L"Dhcp"; UNICODE_STRING DhcpKeyName; CTEPagedCode(); pucAddressList=NbtAllocMem((sizeof(tDEVICES)+2*NBT_MAXIMUM_BINDINGS*sizeof(UNICODE_STRING)),NBT_TAG('i')); if (!pucAddressList) { return STATUS_INSUFFICIENT_RESOURCES; } // // Since NbtReadLinkageInformation very conveniently reads in the values for // a MULTI_SZ registry entry, we will re-use this function here! // // NumEntriesRead = 0; Status = NbtReadLinkageInformation (pwsKeyName, ParametersHandle, 2*NBT_MAXIMUM_BINDINGS, pucAddressList, &NumEntriesRead); if ((STATUS_ILL_FORMED_SERVICE_ENTRY == Status) || (!NT_SUCCESS(Status))) { IF_DBG(NBT_DEBUG_NTUTIL) KdPrint(("GetIpAddressesList: ERROR -- NbtReadLinkageInformation=<%x>, <%ws>\n", Status, pwsKeyName)); CTEMemFree(pucAddressList); return STATUS_UNSUCCESSFUL; } String.Buffer = NbtAllocMem (REGISTRY_BUFF_SIZE, NBT_TAG2('29')); if (!String.Buffer) { KdPrint(("GetNameServerAddresses: Failed to Allocate memory\n")); CTEMemFree((PVOID)pucAddressList->RegistryData); CTEMemFree(pucAddressList); return STATUS_INSUFFICIENT_RESOURCES; } String.MaximumLength = REGISTRY_BUFF_SIZE; // // NumGoodAddresses will be bound by MaxAddresses, while // NumAddressesAttempted will be bound by NumEntriesRead // Also, we could have read NumEntriesRead > MaxAddresses // (some of the entries could be invalid), but we will not // attempt to read > 2*MaxAddresses entires // NumGoodAddresses = 0; NumAddressesAttempted = 0; while ((NumGoodAddresses < MaxAddresses) && (NumAddressesAttempted < NumEntriesRead) && (NumAddressesAttempted < (2*MaxAddresses))) { Status = RtlUnicodeStringToAnsiString(&String, &pucAddressList->Names[NumAddressesAttempted], FALSE); if (NT_SUCCESS(Status)) { Status = ConvertDottedDecimalToUlong((PUCHAR) String.Buffer, &IpAddr); if (NT_SUCCESS(Status) && IpAddr) { pAddrArray[NumGoodAddresses++] = IpAddr; } } NumAddressesAttempted++; } CTEMemFree ((PVOID)String.Buffer); CTEMemFree ((PVOID)pucAddressList->RegistryData); CTEMemFree ((PVOID)pucAddressList); // // If we were able to read in at least 1 good Ip address, // return success, otherwise return failure! // if (NumGoodAddresses) { Status = STATUS_SUCCESS; } else { Status = STATUS_INVALID_ADDRESS; } *pNumGoodAddresses = NumGoodAddresses; return(Status); } NTSTATUS GetServerAddress ( IN HANDLE ParametersHandle, IN PWSTR KeyName, OUT PULONG pIpAddr ) /*++ Routine Description: This routine is called to read the name server addresses from the registry. Arguments: ConfigurationInfo - A pointer to the configuration information structure. Return Value: None. --*/ { NTSTATUS status; ULONG IpAddr; PUCHAR NameServer; CTEPagedCode(); status = CTEReadIniString(ParametersHandle,KeyName,&NameServer); if (NT_SUCCESS(status)) { status = ConvertDottedDecimalToUlong(NameServer,&IpAddr); if (NT_SUCCESS(status) && IpAddr) { *pIpAddr = IpAddr; } else { if (IpAddr != 0) { NbtLogEvent (EVENT_NBT_BAD_PRIMARY_WINS_ADDR, 0, 0x121); } *pIpAddr = LOOP_BACK; } CTEMemFree((PVOID)NameServer); } else { *pIpAddr = LOOP_BACK; } return(status); } //---------------------------------------------------------------------------- NTSTATUS NbtAppendString ( IN PWSTR FirstString, IN PWSTR SecondString, OUT PUNICODE_STRING pucString ) /*++ Routine Description: This routine is called to append the second string to the first string. It allocates memory for this, so the caller must be sure to free it. Arguments: Return Value: None. --*/ { NTSTATUS status = STATUS_INSUFFICIENT_RESOURCES; ULONG Length; PWSTR pDhcpKeyName; CTEPagedCode(); Length = (wcslen(FirstString) + wcslen(SecondString) + 1)*sizeof(WCHAR); pDhcpKeyName = NbtAllocMem (Length, NBT_TAG2('30')); if (pDhcpKeyName) { pucString->Buffer = pDhcpKeyName; pucString->Length = (USHORT)0; pucString->MaximumLength = (USHORT)Length; pucString->Buffer[0] = UNICODE_NULL; status = RtlAppendUnicodeToString(pucString,FirstString); if (NT_SUCCESS(status)) { status = RtlAppendUnicodeToString(pucString,SecondString); if (NT_SUCCESS(status)) { return status; } } CTEMemFree(pDhcpKeyName); } return(status); } //---------------------------------------------------------------------------- NTSTATUS NTReadIniString ( IN HANDLE ParametersHandle, IN PWSTR KeyName, OUT PUCHAR *ppString ) /*++ Routine Description: This routine is called to read a string of configuration information from the registry. Arguments: ParametersHandle - handle to open key in registry KeyName - key to read ppString - returned string Return Value: None. --*/ { UNICODE_STRING ucString; STRING String; NTSTATUS status; PUCHAR pBuffer; PWSTR Dhcp = L"Dhcp"; CTEPagedCode(); // // read in the Scope Id // // if the key is not there or it is set to a null string try to read the // dhcp key // status = ReadElement (ParametersHandle, KeyName, &ucString); if (!NT_SUCCESS(status) || (ucString.Length == 0)) { UNICODE_STRING String; // free the string allocated in ReadElement if (NT_SUCCESS(status)) { CTEMemFree(ucString.Buffer); } // // try to read a similar string that is prefixed with "DHCP" // incase there is only the DHCP configuration information present // and not overrides keys. // status = NbtAppendString(Dhcp,KeyName,&String); if (NT_SUCCESS(status)) { status = ReadElement (ParametersHandle, String.Buffer, &ucString); CTEMemFree(String.Buffer); // Free the buffer allocated in NbtAppendString } } // the scope must be less than // 255-16 characters since the whole name is limited to 255 as per the // RFC // IF_DBG(NBT_DEBUG_NTUTIL) KdPrint(("Nbt: ReadIniString = %ws\n",ucString.Buffer)); if (NT_SUCCESS(status)) { if ((ucString.Length > 0) && (ucString.Length <= (255 - NETBIOS_NAME_SIZE)*sizeof(WCHAR))) { pBuffer = NbtAllocMem (ucString.MaximumLength/sizeof(WCHAR), NBT_TAG2('31')); if (pBuffer) { // convert to an ascii string and store in the config data structure // increment pBuffer to leave room for the length byte // String.Buffer = pBuffer; String.MaximumLength = ucString.MaximumLength/sizeof(WCHAR); status = RtlUnicodeStringToAnsiString (&String, &ucString, FALSE); if (NT_SUCCESS(status)) { *ppString = pBuffer; } else { CTEMemFree(pBuffer); } } else { status = STATUS_UNSUCCESSFUL; } } else if (NT_SUCCESS(status)) { // force the code to setup a null scope since the one in the // registry is null // status = STATUS_UNSUCCESSFUL; } // free the string allocated in ReadElement CTEMemFree(ucString.Buffer); } return(status); } VOID NbtFreeRegistryInfo ( ) /*++ Routine Description: This routine is called by Nbt to free any storage that was allocated by NbConfigureTransport in producing the specified CONFIG_DATA structure. Arguments: ConfigurationInfo - A pointer to the configuration information structure. Return Value: None. --*/ { } //---------------------------------------------------------------------------- NTSTATUS GetIPFromRegistry( IN PUNICODE_STRING pucBindDevice, OUT tIPADDRESS *pIpAddresses, OUT tIPADDRESS *pSubnetMask, IN ULONG MaxIpAddresses, OUT ULONG *pNumIpAddresses, IN enum eNbtIPAddressType IPAddressType ) /*++ Routine Description: This routine is called to get the IP address of an adapter from the Registry. The Registry path variable contains the path name for NBT's registry entries. The last element of this path name is removed to give the path to any card in the registry. The BindDevice path contains a Bind string for NBT. We remove the last element of this path (which is the adapter name \Elnkii01) and tack it onto the modified registry path from above. We then tack on \Parameters which will give the full path to the Tcpip key, which we open to get the Ip address. Arguments: Before calling this routine, the following Global parameters must have been initialized (in DriverEntry): NbtConfig.pRegistry Return Value: NTSTATUS - STATUS_SUCCESS if everything OK, STATUS_INSUFFICIENT_RESOURCES otherwise. --*/ { ULONG i, Len, Disposition; PVOID pBuffer; NTSTATUS Status = STATUS_UNSUCCESSFUL; // by default PWSTR pwsIpAddressName, pwsSubnetMask; PWSTR pwsAdapterGuid, pwsLastSlash; PWSTR pwsTcpParams = L"Tcpip\\Parameters\\Interfaces\\"; // key to open PWSTR pwsUnderScore = L"_"; UNICODE_STRING Path; HANDLE TcpGuidHandle; OBJECT_ATTRIBUTES TmpObjectAttributes; CTEPagedCode(); switch (IPAddressType) { case (NBT_IP_STATIC): pwsIpAddressName = STATIC_IPADDRESS_NAME; pwsSubnetMask = STATIC_IPADDRESS_SUBNET; break; case (NBT_IP_DHCP): pwsIpAddressName = DHCP_IPADDRESS_NAME; pwsSubnetMask = DHCP_IPADDRESS_SUBNET; break; case (NBT_IP_AUTOCONFIGURATION): pwsIpAddressName = DHCP_IPAUTOCONFIGURATION_NAME; pwsSubnetMask = DHCP_IPAUTOCONFIGURATION_SUBNET; break; default: IF_DBG(NBT_DEBUG_NTUTIL) KdPrint(("Invalid IP Address Type <%x>\n", IPAddressType)); return STATUS_INVALID_ADDRESS; } // Extract the Adapter Guid from the BindDevice name // pucBindDevice: \Device\TCPIP_ // Find the last back slash in the path name to the bind device NbtFindLastSlash (pucBindDevice, &pwsAdapterGuid, &Len); if (pwsAdapterGuid) { // // Now, search the string to find the first underscore in "TCPIP_" // Len = wcslen(pwsAdapterGuid); for(i=0; i\n", Status)); return STATUS_UNSUCCESSFUL; } Status = STATUS_INVALID_ADDRESS; *pNumIpAddresses = 0; if (NT_SUCCESS (GetIpAddressesList(TcpGuidHandle, pwsIpAddressName, MaxIpAddresses, pIpAddresses, pNumIpAddresses))) { // // DHCP may put a 0 Ip address in the registry - we don't want to // set the address under these conditions. // if ((*pNumIpAddresses) && (*pIpAddresses)) { i = 0; if (NT_SUCCESS (GetIpAddressesList(TcpGuidHandle, pwsSubnetMask, 1, pSubnetMask, &i))) { Status = STATUS_SUCCESS; } } } ZwClose (TcpGuidHandle); return Status; } // GetIPFromRegistry //---------------------------------------------------------------------------- NTSTATUS NbtOpenRegistry( IN HANDLE NbConfigHandle, IN PWSTR String, OUT PHANDLE pHandle ) /*++ Routine Description: This routine is called by Nbt to open the registry. If the registry tree for Nbt exists, then it opens it and returns TRUE. If not, it creates the appropriate keys in the registry, opens it, and returns FALSE. Arguments: NbConfigHandle - this is the root handle which String is relative to String - the name of the key to open below the root handle pHandle - returns the handle to the String key. Return Value: The status of the request. --*/ { NTSTATUS Status; UNICODE_STRING KeyName; OBJECT_ATTRIBUTES TmpObjectAttributes; CTEPagedCode(); // // Open the Nbt key. // RtlInitUnicodeString (&KeyName, String); InitializeObjectAttributes (&TmpObjectAttributes, &KeyName, // name OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, // attributes NbConfigHandle, // root NULL); // security descriptor Status = ZwOpenKey (pHandle, KEY_READ, &TmpObjectAttributes); return Status; } /* NbOpenRegistry */ NTSTATUS NbtParseMultiSzEntries( IN PWSTR StartBindValue, IN PWSTR EndBindValue, IN ULONG MaxBindings, OUT tDEVICES *pDevices, OUT ULONG *pNumDevices ) { USHORT ConfigBindings = 0; NTSTATUS status = STATUS_SUCCESS; CTEPagedCode(); try { while ((StartBindValue < EndBindValue) && (*StartBindValue != 0)) { if (ConfigBindings >= MaxBindings) { status = STATUS_BUFFER_OVERFLOW; break; } // this sets the buffer ptr in Names to point to CurBindValue, so // this value must be real memory and not stack, hence the need // to allocate memory above... RtlInitUnicodeString (&pDevices->Names[ConfigBindings], (PCWSTR)StartBindValue); ++ConfigBindings; // // Now advance the "Bind" value. // // wcslen => wide character string length for a unicode string StartBindValue += wcslen((PCWSTR)StartBindValue) + 1; } *pNumDevices = ConfigBindings; return (status); } except(EXCEPTION_EXECUTE_HANDLER) { KdPrint (("Nbt.NbtParseMultiSzEntries: Exception <0x%x>\n", GetExceptionCode())); for (ConfigBindings = 0; ConfigBindings < MaxBindings; ConfigBindings++) { pDevices->Names[ConfigBindings].Buffer = NULL; pDevices->Names[ConfigBindings].Length = pDevices->Names[ConfigBindings].MaximumLength = 0; } *pNumDevices = 0; return STATUS_ACCESS_VIOLATION; } } //---------------------------------------------------------------------------- NTSTATUS NbtReadLinkageInformation( IN PWSTR pName, IN HANDLE LinkageHandle, IN ULONG MaxBindings, OUT tDEVICES *pDevices, // place to put read in config data OUT ULONG *pNumDevices ) /*++ Routine Description: This routine is called by Nbt to read its linkage information from the registry. If there is none present, then ConfigData is filled with a list of all the adapters that are known to Nbt. Arguments: RegistryHandle - A pointer to the open registry. Return Value: Status --*/ { NTSTATUS RegistryStatus; UNICODE_STRING BindString; ULONG BytesWritten = 0; PKEY_VALUE_FULL_INFORMATION RegistryData; CTEPagedCode(); pDevices->RegistryData = NULL; RtlInitUnicodeString (&BindString, pName); // copy "Bind" or "Export" into the unicode string // // Determine how many bytes we need to allocate for the Read buffer RegistryStatus = ZwQueryValueKey (LinkageHandle, &BindString, // string to retrieve KeyValueFullInformation, NULL, 0, &BytesWritten); // # of bytes to read if ((RegistryStatus != STATUS_BUFFER_TOO_SMALL) || (BytesWritten == 0)) { return STATUS_ILL_FORMED_SERVICE_ENTRY; } if (!(RegistryData = (PKEY_VALUE_FULL_INFORMATION) NbtAllocMem (BytesWritten, NBT_TAG2('33')))) { return(STATUS_INSUFFICIENT_RESOURCES); } RegistryStatus = ZwQueryValueKey (LinkageHandle, &BindString, // string to retrieve KeyValueFullInformation, (PVOID) RegistryData, // returned info BytesWritten, &BytesWritten); // # of bytes valid data if (!NT_SUCCESS(RegistryStatus) || (RegistryStatus == STATUS_BUFFER_OVERFLOW)) { CTEMemFree(RegistryData); return RegistryStatus; } if (BytesWritten == 0) { CTEMemFree(RegistryData); return STATUS_ILL_FORMED_SERVICE_ENTRY; } pDevices->RegistryData = RegistryData; NbtParseMultiSzEntries ((PWCHAR)((PUCHAR)RegistryData+RegistryData->DataOffset), (PWSTR) ((PUCHAR)RegistryData+RegistryData->DataOffset+RegistryData->DataLength), MaxBindings, pDevices, pNumDevices); return STATUS_SUCCESS; } /* NbtReadLinkageInformation */ //---------------------------------------------------------------------------- ULONG NbtReadSingleParameter( IN HANDLE ParametersHandle, IN PWCHAR ValueName, IN ULONG DefaultValue, IN ULONG MinimumValue ) /*++ Routine Description: This routine is called by Nbt to read a single parameter from the registry. If the parameter is found it is stored in Data. Arguments: ParametersHandle - A pointer to the open registry. ValueName - The name of the value to search for. DefaultValue - The default value. Return Value: The value to use; will be the default if the value is not found or is not in the correct range. --*/ { static ULONG InformationBuffer[60]; PKEY_VALUE_FULL_INFORMATION Information = (PKEY_VALUE_FULL_INFORMATION)InformationBuffer; UNICODE_STRING ValueKeyName; ULONG InformationLength; ULONG ReturnValue=DefaultValue; NTSTATUS Status; ULONG Count=2; PWSTR Dhcp = L"Dhcp"; BOOLEAN FreeString = FALSE; CTEPagedCode(); RtlInitUnicodeString (&ValueKeyName, ValueName); while (Count--) { Status = ZwQueryValueKey( ParametersHandle, &ValueKeyName, KeyValueFullInformation, (PVOID)Information, sizeof (InformationBuffer), &InformationLength); if ((Status == STATUS_SUCCESS) && (Information->DataLength == sizeof(ULONG))) { RtlMoveMemory( (PVOID)&ReturnValue, ((PUCHAR)Information) + Information->DataOffset, sizeof(ULONG)); if (ReturnValue < MinimumValue) { ReturnValue = MinimumValue; } } else { // // try to read the Dhcp key instead if the first read failed. // Status = STATUS_SUCCESS; if (Count) { Status = NbtAppendString(Dhcp,ValueName,&ValueKeyName); } if (!NT_SUCCESS(Status)) { Count = 0; ReturnValue = DefaultValue; } else FreeString = TRUE; } } // of while // nbt append string allocates memory. if (FreeString) { CTEMemFree(ValueKeyName.Buffer); } return ReturnValue; } /* NbtReadSingleParameter */ //---------------------------------------------------------------------------- NTSTATUS OpenAndReadElement( IN PUNICODE_STRING pucRootPath, IN PWSTR pwsValueName, OUT PUNICODE_STRING pucString ) /*++ Routine Description: This routine is called by Nbt to read in the Ip address appearing in the registry at the path pucRootPath, with a key of pwsKeyName Arguments: pucRootPath - the registry path to the key to read pwsKeyName - the key to open (i.e. Tcpip) pwsValueName- the name of the value to read (i.e. IPAddress) Return Value: pucString - the string returns the string read from the registry --*/ { NTSTATUS Status; HANDLE hRootKey; OBJECT_ATTRIBUTES TmpObjectAttributes; CTEPagedCode(); InitializeObjectAttributes (&TmpObjectAttributes, pucRootPath, // name OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, // attributes NULL, // root NULL); // security descriptor Status = ZwOpenKey (&hRootKey, KEY_READ, &TmpObjectAttributes); if (!NT_SUCCESS(Status)) { return STATUS_UNSUCCESSFUL; } Status = ReadElement(hRootKey,pwsValueName,pucString); ZwClose (hRootKey); return(Status); } //---------------------------------------------------------------------------- NTSTATUS ReadElement( IN HANDLE HandleToKey, IN PWSTR pwsValueName, OUT PUNICODE_STRING pucString ) /*++ Routine Description: This routine is will read a string value given by pwsValueName, under a given Key (which must be open) - given by HandleToKey. This routine allocates memory for the buffer in the returned pucString, so the caller must deallocate that. Arguments: pwsValueName- the name of the value to read (i.e. IPAddress) Return Value: pucString - the string returns the string read from the registry --*/ { ULONG ReadStorage[150]; // 600 bytes ULONG BytesRead; NTSTATUS Status; PWSTR pwsSrcString; PKEY_VALUE_FULL_INFORMATION ReadValue = (PKEY_VALUE_FULL_INFORMATION)ReadStorage; CTEPagedCode(); // now put the name of the value to read into a unicode string RtlInitUnicodeString(pucString,pwsValueName); // this read the value of IPAddress under the key opened above Status = ZwQueryValueKey( HandleToKey, pucString, // string to retrieve KeyValueFullInformation, (PVOID)ReadValue, // returned info sizeof(ReadStorage), &BytesRead // # of bytes returned ); if ( Status == STATUS_BUFFER_OVERFLOW ) { ReadValue = (PKEY_VALUE_FULL_INFORMATION) NbtAllocMem (BytesRead, NBT_TAG2('35')); if (ReadValue == NULL) { IF_DBG(NBT_DEBUG_NTUTIL) KdPrint(("ReadElement: failed to allocate %d bytes for element\n",BytesRead)); Status = STATUS_INSUFFICIENT_RESOURCES; goto ReadElement_Return; } Status = ZwQueryValueKey( HandleToKey, pucString, // string to retrieve KeyValueFullInformation, (PVOID)ReadValue, // returned info BytesRead, &BytesRead // # of bytes returned ); } if (!NT_SUCCESS(Status)) { IF_DBG(NBT_DEBUG_NTUTIL) KdPrint(("failed to Query Value Status = %X\n",Status)); goto ReadElement_Return; } if ( BytesRead == 0 ) { Status = STATUS_ILL_FORMED_SERVICE_ENTRY; goto ReadElement_Return; } else if (ReadValue->DataLength == 0) { Status = STATUS_UNSUCCESSFUL; goto ReadElement_Return; } // create the pucString and copy the data returned to it // assumes that the ReadValue string ends in a UNICODE_NULL //bStatus = RtlCreateUnicodeString(pucString,pwSrcString); pwsSrcString = (PWSTR)NbtAllocMem ((USHORT)ReadValue->DataLength, NBT_TAG2('36')); if (!pwsSrcString) { ASSERTMSG((PVOID)pwsSrcString, (PCHAR)"Unable to allocate memory for a Unicode string"); Status = STATUS_INSUFFICIENT_RESOURCES; } else { // move the read in data from the stack to the memory allocated // from the nonpaged pool RtlMoveMemory( (PVOID)pwsSrcString, ((PUCHAR)ReadValue) + ReadValue->DataOffset, ReadValue->DataLength); RtlInitUnicodeString(pucString,pwsSrcString); // if there isn't a null on the end of the pwsSrcString, then // it will not work correctly. - a null string comes out with a // length of 1!! since the null is counted therefore use // rtlinitunicode string afterall. // pucString->MaximumLength = ReadValue->DataLength; // pucString->Length = ReadValue->DataLength; // pucString->Buffer = pwsSrcString; } ReadElement_Return: if ((ReadValue != (PKEY_VALUE_FULL_INFORMATION)ReadStorage) && (ReadValue != NULL)) { CTEMemFree(ReadValue); } return(Status); } //---------------------------------------------------------------------------- NTSTATUS NTGetLmHostPath( OUT PUCHAR *ppPath ) /*++ Routine Description: This routine will read the DataBasePath from under ...\tcpip\parameters\databasepath Arguments: pPath - ptr to a buffer containing the path name. Return Value: --*/ { NTSTATUS status; UNICODE_STRING ucDataBase; STRING StringPath; STRING LmhostsString; ULONG StringMax; PWSTR LmHosts = L"lmhosts"; PWSTR TcpIpParams = L"TcpIp\\Parameters"; PWSTR TcpParams = L"Tcp\\Parameters"; PWSTR DataBase = L"DataBasePath"; PCHAR ascLmhosts="\\lmhosts"; PCHAR pBuffer; CTEPagedCode(); *ppPath = NULL; status = ReadStringRelative(&NbtConfig.pRegistry, TcpIpParams, DataBase, &ucDataBase); if (!NT_SUCCESS(status)) { // check for the new TCP stack which a slightly different registry // key name. // status = ReadStringRelative(&NbtConfig.pRegistry, TcpParams, DataBase, &ucDataBase); if (!NT_SUCCESS(status)) { return STATUS_UNSUCCESSFUL; } } StringMax = ucDataBase.Length/sizeof(WCHAR) + strlen(ascLmhosts) + 1; pBuffer = NbtAllocMem (StringMax, NBT_TAG2('37')); if (!pBuffer) { return(STATUS_INSUFFICIENT_RESOURCES); } StringPath.Buffer = (PCHAR)pBuffer; StringPath.MaximumLength = (USHORT)StringMax; StringPath.Length = (USHORT)StringMax; // convert to ascii from unicode status = RtlUnicodeStringToAnsiString(&StringPath, &ucDataBase, FALSE); CTEMemFree(ucDataBase.Buffer); // this memory was allocated in OpenAndReadElement if (!NT_SUCCESS(status)) { CTEMemFree(StringPath.Buffer); return(STATUS_UNSUCCESSFUL); } // now put the "\lmhosts" name on the end of the string // RtlInitString(&LmhostsString, ascLmhosts); status = RtlAppendStringToString(&StringPath, &LmhostsString); if (NT_SUCCESS(status)) { // // is the first part of the directory "%SystemRoot%" ? // // If so, it must be changed to "\\SystemRoot\\". // // 0123456789 123456789 1 // %SystemRoot%\somewhere // // if (strncmp(StringPath.Buffer, "%SystemRoot%", 12) == 0) { StringPath.Buffer[0] = '\\'; StringPath.Buffer[11] = '\\'; if (StringPath.Buffer[12] == '\\') { ASSERT(StringPath.Length >= 13); if (StringPath.Length > 13) { // overlapped copy RtlMoveMemory (&(StringPath.Buffer[12]), // Destination &(StringPath.Buffer[13]), // Source (ULONG) StringPath.Length - 13); // Length StringPath.Buffer[StringPath.Length - 1] = (CHAR) NULL; } StringPath.Length--; } } *ppPath = (PCHAR)StringPath.Buffer; } else { CTEMemFree(StringPath.Buffer); } return(status); } //---------------------------------------------------------------------------- NTSTATUS ReadStringRelative( IN PUNICODE_STRING pRegistryPath, IN PWSTR pRelativePath, IN PWSTR pValueName, OUT PUNICODE_STRING pOutString ) /*++ Routine Description: This routine reads a string from a registry key parallel to the Netbt key - such as ..\tcpip\parameters\database Arguments: pRegistryPath = ptr to the Netbt Registry path pRelativePath = path to value relative to same root as nbt. pValueName = value to read Return Value: The length of the path up to and including the last slash and a ptr to the first character of the last element of the string. --*/ { NTSTATUS status; UNICODE_STRING RegistryPath; UNICODE_STRING RelativePath; ULONG StringMax; PVOID pBuffer; PWSTR pLastElement; ULONG Length; CTEPagedCode(); StringMax = (pRegistryPath->MaximumLength + wcslen(pRelativePath)*sizeof(WCHAR)+2); // // allocate some memory for the registry path so that it is large enough // to append a string on to, for the relative key to be read // if (!(pBuffer = NbtAllocMem (StringMax, NBT_TAG2('38')))) { return(STATUS_INSUFFICIENT_RESOURCES); } RegistryPath.MaximumLength = (USHORT)StringMax; RegistryPath.Buffer = pBuffer; RtlCopyUnicodeString(&RegistryPath,pRegistryPath); // // find the last backslash and truncate the string NbtFindLastSlash(&RegistryPath,&pLastElement,&Length); RegistryPath.Length = (USHORT)Length; if (pLastElement) { *pLastElement = UNICODE_NULL; RtlInitUnicodeString(&RelativePath,pRelativePath); status = RtlAppendUnicodeStringToString(&RegistryPath,&RelativePath); if (NT_SUCCESS(status)) { status = OpenAndReadElement(&RegistryPath,pValueName,pOutString); if (NT_SUCCESS(status)) { // free the registry path // CTEMemFree(pBuffer); return(status); } } } else { status = STATUS_UNSUCCESSFUL; } CTEMemFree(pBuffer); return(status); } //---------------------------------------------------------------------------- VOID NbtFindLastSlash( IN PUNICODE_STRING pucRegistryPath, OUT PWSTR *ppucLastElement, IN int *piLength ) /*++ Routine Description: This routine is called by Nbt to find the last slash in a registry path name. Arguments: Return Value: The length of the path up to and including the last slash and a ptr to the first character of the last element of the string. --*/ { int i; PWSTR pwsSlash = L"\\"; int iStart; CTEPagedCode(); // search starting at the end of the string for the last back slash iStart = wcslen(pucRegistryPath->Buffer)-1; for(i=iStart;i>=0 ;i-- ) { if (pucRegistryPath->Buffer[i] == *pwsSlash) { if (i==pucRegistryPath->Length-1) { // name ends a back slash... this is an error break; } // increase one to allow for the slash *piLength = (i+1)*sizeof(WCHAR); if (ppucLastElement != NULL) { // want ptr to point at character after the slash *ppucLastElement = &pucRegistryPath->Buffer[i+1]; } return; } } // null the pointer if one is passed in if (ppucLastElement != NULL) { *ppucLastElement = NULL; } *piLength = 0; return; }