Sie sind auf Seite 1von 8

Copyright © 2000, The Mandelbrot Set (International) Limited

Your Network’
s Who’
s Who:

Patrick Long

patl@themandelbrotset.com

Introduction

Most developers take advantage of NT’ s integrated security without even


noticing it. As developers, maybe you’ ve seen the integrated security
option in the SQL Server Security Configuration dialog. Integrated security
gives users the advantage of not having to continuously enter their
account details. In this article, I’
ll show you how to implement this
functionality in your apps using Microsoft’ s Network Application
Programming Interface (API). You implement the API’ s functionality with
two Active X components to handle local and domain-wide information,
respectively.

The first component, a local ActiveX DLL, gives you read-only access to
local network information such as the logged-on user’ s name and the
logon server (see Figure 1). This component provides limited functionality
on Win9x machines, due to the operating system’ s lack of Unicode support
and consequently lack of support for this API. The second component, an
ActiveX EXE, returns a collection of users with the ability to add and delete
them. Although this component runs only on NT machines, you can call it
remotely using the Distributed Component Object Model (DCOM) from a
Win9x machine. Later I’ ll cover FormatMessage, a function which allows
you to get the error descriptions for the errors returned by the API.
Who’ s Currently Logged On?

Knowing who’ s logged on is the key to unlocking the integrated security


door. I currently use the NT userid and an application name as a key to a
company User and Systems database to verify the user is allowed to
logon. The userid of the currently logged-on user can also be useful in
database applications, where you need to record which user is reading
what data or who last updated a row.

The NetWkstaUserGetInfo function returns a user-defined type (UDT)


describing the currently logged-on user. This function, like many other
Network API functions, allows you to specify the level of information to
return. Some functions have levels that are only accessible when the
function is running under an account with Administrator authority. Each
level has a different UDT structure to hold the returned data. For this
example, level 1 is all you need.

Before running the function, check that the code is running on an NT


machine. I use Karl E. Peterson’ s COpSysInfo class to wrap the version-
checking code. This class is available with the full code from Karl’
s site

Page 1 of 1
Copyright © 1999, The Mandelbrot Set (International) Limited

http://www.mvps.org/vb/. If you aren’ t running on NT, you can still get


some functionality by using the WIN32 function GetUserName (see Listing
1 for the code to call NetWkstaUserGetInfo).

The UDT, like all the UDTs returned by this API, contains the memory
addresses (pointers) of the Unicode strings instead of the actual strings.
You can convert these pointers to VB strings using the PointerToStringW
function:

Public Function PointerToStringW(lpStringW As Long) _


As String

Dim yBuffer() As Byte


Dim lLen As Long

If lpStringW Then
lLen = lstrlenW(lpStringW) * 2
If lLen Then
ReDim yBuffer _
(0 To (lLen - 1)) As Byte
CopyMem yBuffer(0), ByVal _
lpStri ngW, lLen
PointerToStringW = yBuffer
End If
End If

End Function

After receiving data from the function, you need to use the
NetApiBufferFree function to clear up the memory allocated to it. You need
to clear up memory, using NetApiBufferFree, after calling any of this API’
s
functions that return data.

Get The Error Description


As well as Visual Basic errors, you need to cater for errors returned by the
API. The API returns an error in the function’s return value. Non-zero
return codes signal an error. The only exception to this rule is 234 -
ERR_MORE_DATA which the API’ s enumerating functions, such as
NetUserEnum and NetGroupEnum, return. You shouldn’ t treat this as an
error but as a signal that you need to call the function again using the
provided resume handle. You should include this handle in subsequent
calls until all data is received.

You can handle and/or return other non-zero codes to the client. The
message table file netmsg.dll, found in %systemroot%system32, holds
the text for these errors. Use this file, along with the return codes with the
Win32 API function FormatMessage, to return the respective text (see
Listing 2). The FormatMessage function needs a message table resource
to search. The function can either have the table passed into it; search the
system’ s own table; or, as in our code, accept the handle of an already
loaded module.

Page 2 of 2
Copyright © 1999, The Mandelbrot Set (International) Limited

Windows ships with localised versions of these standard resources. For


example, French Windows gets French messages and US English Windows
gets English messages. Microsoft’s Internet API Wininet.dll is another
example. This DLL contains all the error messages for the Internet API
with the code ranging from 12,000 to 12,171 inclusive. The error range
for the Network API is from 2,100 to 2,999 inclusive.

Visual Studio now ships with a small app that acts as a user interface to
the FormatMessage function. It’ s called Error Lookup and it can be found
under Microsoft Visual Studio 6 Tools in your Start menu. It’s the ideal
companion for any API programmer, because it provides you the
description for any API error code.

For the sake of space, I have hard-coded the code in this article to use the
netmsg.dll module. You may want to create a COM wrapper around
FormatMessage that all your apps could call, loading whatever module
they liked.

I have implemented the functionality of NetWkstaUserGetInfo and the


computer equivalent NetWkstaGetInfo as two GlobalMultiUse classes.
Using GlobalMultiUse means you don’ t need to explicitly declare and set
the classes. The properties and methods appear as standard routines,
such as NetworkUserName instead of LocalUser.NetworkUserName.
However, this type of class has a downside. The ability to extend VB’ s
namespace with the global functions can also mean clashes with existing
functions. Also, because no one owns the class instance, it’ s useless for
holding private states. It works in this example because there can only be
one locally logged on user and one physical workstation.

Domain Roll Call


Now that you have the locally logged on user’ s details, you can get the
details of all user accounts on a computer. A list of all a domain’
s users
can be useful when creating new accounts internal to your applications.

The Network API function to get all the users is called NetUserEnum. As its
name suggests, this function enumerates through all user accounts on a
computer. The function NetServerEnum works in the same way for
machines in a domain.

The function’s first argument is the machine to run the function on. This
means that although the code is executing on your machine, you can tell
the API to run against another machine. The argument expects a pointer
to a Unicode string. See the sidebar “Tackle Unicode in Byte-Sized Pieces”
for information on how to deal with the Unicode strings.

To get a domain listing of all users on a domain, you must run the
function against a Primary or Backup domain controller. These server
types hold the domain’ s user database. To get a domain controller name,
call the NetGetDCName. The Machine name to run on argument is a
Unicode string. If a Null pointer such as vbNullChar, ByVal 0&, is passed
for the “machine to run on”argument, the function is run on the local

Page 3 of 3
Copyright © 1999, The Mandelbrot Set (International) Limited

machine. This rule is the same for all Network API functions with this
parameter.

NetUserEnum has seven information levels. For example, level 0 returns


only the user’ s name. Level 2, the level used in this article, returns a full
set of information ranging from users’names to the date they last logged
off, and from their password’ s age to the number of times they have used
it. The Microsoft documentation says that only administrators and account
operators can execute NetUserEnum when an information level of 1 or 2 is
specified— my experience has proved this to be incorrect. I tried the code
with this article, which uses level 2, as a standard user and as a guest,
and it worked for either.

As was necessary for NetWkstaUserGetInfo, the returned string data


needs to be retrieved using the PointerToStringW passing in the returned
pointers. Because enumerating through NT’ s user database can take a
considerable amount of time, I make enumerator calls asynchronous. The
GetUsers method of the DomainUsers class doesn’ t actually do the work of
getting the users, it just adds itself to a Job Queue of lists to be built. It
then creates a Windows timer that calls the real function BuildUserList.

One of the update functions available with the API,


NetUserChangePassword, changes a user’ s password. I have implemented
this in the ChangePassword method of DomainUser. For security reasons,
none of the Network API calls return a user’s password, so you need to
supply the current password as well as the new one. You need to assign
the current and new passwords to byte arrays just like you would the
machine name and domain.

Tackle Unicode in Byte-Sized Pieces


The Network API works exclusively with Unicode strings and therefore only
works on NT. This means you need to fiddle around with the code to make
it work because although VB holds strings as Unicode internally (since
version 4), when passed to external APIs they are converted to ANSI. To
get around this you need to pack the string, with an appended null
terminator (VbNullChar), into a Byte array. When calling the function,
pass the first element in the array, Byref. This results in a pointer to the
beginning of the Unicode string being passed to the API.

In this example of the code needed to call a NetAPI function, note the
appended vbNullChar and the passing of the first element:

yUserName = sPiName & vbNullChar


yPassword = OldPassword & vbNullChar
yNewPassword = NewPassword & _
vbNullChar

yDomain = sPiEnumedFrom & vbNullChar


lAPIReturnCode =
NetUserChangePassword _
(yDomain(0), yUserName(0), _
yPassword(0), yNewPassword(0))

Page 4 of 4
Copyright © 1999, The Mandelbrot Set (International) Limited

Enumerate Through the User’s Groups


You can also enumerate through all groups to get a list of all groups and
those assigned to a particular user. The code to get all groups is similar to
that of NetUserEnum; the only real difference is that I’ve included a
scoping option to say whether you want GlobalGroups or LocalGroups. The
respective API functions are NetGroupEnum and NetLocalGroupEnum.

Getting the user’ s groups requires a bit more work. First, you need to call
either NetUserGetGroups or NetUserGetLocalGroups. The code needed to
call these functions is similar to NetUserEnum except you don’ t need to
loop around getting the data; it’ s all returned at the same time.
Unfortunately neither of these functions returns the group’ s description.
The functions to return this information are NetGroupGetInfo and
NetLocalGroupGetInfo. Both are called with level 1 information. The code
online (see the Download Free Code box for details) shows the full
implementation.

This article has only scratched the surface of an API with which, if you
were crazy enough, you could replace NT’ s User Manager with your own.
The code and techniques shown in these components is an ideal starting
place from which to explore the rest of the Network API.

Figure 1
Get Local Network Information. The local DLL returns this information on an NT workstation.

Listing 1
VB4/32, VB5, VB6
Get the Local User Information. The RefreshLocalUser Sub is called on Class_Initialize. If you’re
running on a non-NT Machine, just get the username.

' In Declarations
Public oOSInfo As OpSysInfo
Private oPiErrLookUp As ErrLookup

Public Sub RefreshLocalUser()

Page 5 of 5
Copyright © 1999, The Mandelbrot Set (International) Limited

Dim lpBuffer As Long


Dim lAPIReturnCode As Long

Const InformationLevel As Long = 1&

If oOSInfo.IsWinNT Then

lAPIReturnCode = NetWkstaUserGetInfo(0, _
InformationLevel, lpBuffer)

If lAPIReturnCode = NERR_Success Then

CopyMem tPIUser_API, ByVal lpBuffer, _


Len(tPIUser_API)
sPiUserName = _
PointerToStringW(tPIUser_API.lUsername)
sPiLogonDomain = _
PointerToStringW(tPIUser_API.lLogonDomain)
sPiOtherDomains = _
PointerToStringW(tPIUser_API.lOtherDomains)
sPiOtherDomainsDelimited = _
DelimitList(sPiOtherDomains)
sPiLogonServer = _
PointerToStringW(tPIUser_API.lLogonServer)

If lpBuffer Then
Call NetApiBufferFree(lpBuffer)
End If

Else
On Error GoTo 0
Err.Raise lAPIReturnCode, _
TypeName(Me) & PROC_NAME, _
oPiErrLookUp.LookUp(CStr(lAPIReturnCode))
End If

Else
Dim sBuffer As String
Dim lSize As Long
lSize = 16
sBuffer = String$(lSize, 0)

If GetUserName(sBuffer, lSize) Then


sPiUserName = Left$(sBuffer, lSize)
End If
sPiLogonDomain = "Unknown"
sPiLogonServer = "Unknown"
sPiOtherDomains = "Unknown"

End If

Listing 2
VB4/32, VB5, VB6
Call FormatMessage. FormatMessage accepts the handle of a loaded module as an extra place to
search for the message.

Public Function LookUp(ErrorID As String) As String

Page 6 of 6
Copyright © 1999, The Mandelbrot Set (International) Limited

'This function accepts an error id and returns an Error


'Message For it.
'If no message is found return nothing

Dim sErrMessage As String


Dim lErrorID As Long
Dim lRC As Long

If Not IsNumeric(ErrorID) Then Exit Function

'Recognize leading & as a hex specifier


'without following H
If Left$(ErrorID, 1) = "&" _
And UCase$(Mid$(ErrorID, 2, 1)) <> "H" Then
ErrorID = "&H" & Mid$(ErrorID, 2)
End If

lErrorID = Val(ErrorID)

'Lookup error in system table

sErrMessage = String$(256, 0)
lRC = FormatMessage( _
FORMAT_MESSAGE_FROM_SYSTEM Or _
FORMAT_MESSAGE_IGNORE_INSE RTS, _
0&, lErrorID, 0&, sErrMessage, _
Len(sErrMessage), ByVal 0&)

If lRC = 0 Then

'Message not found. If any extra modules have been


'specified check for the error in them
Dim iModuleIndex As Integer

For iModuleIndex = 0 To iPiModuleCount - 1


Dim hModule As Long

hModule = _
LoadLibraryEx(asPiModules(iModuleIndex), _
ByVal 0&, LOAD_LIBRARY_AS_DATAFILE)

If hModule Then
'Module loaded so let’s have a look
sErrMessage = String$(256, 0)
lRC = FormatMessage( _
FORMAT_MESSAGE_FROM_SYSTEM Or _
FORMAT_MESSAGE_IGNORE_INSERTS Or _
FORMAT_MESSAGE_FROM_HMODULE, _
ByVal hModule, lErrorID, 0&, _
sErrMessage, Len(sErrMessage), _
ByVal 0&)

If lRC <> 0 Then


'We have found the message
Exit For
End If

Call FreeLibrary(hModule)
End If

Next iModuleIndex

Page 7 of 7
Copyright © 1999, The Mandelbrot Set (International) Limited

If lRC <> 0 Then


LookUp = Left$(sErrMessage, _
InStr(sErrMessage, vbCrLf) - 1)
Else
LookUp = "Message string not fo und"
End If
Else
LookUp = Left$(sErrMessage, _
InStr(sErrMessage, vbCrLf) - 1)
End If

End Function

About - Patrick Long patrickl@themandelbrotset.com

Patrick Long is a Senior Developer with The Mandelbrot Set (International) Limited
(TMS).

Page 8 of 8

Das könnte Ihnen auch gefallen