1. Gregory Rusak
  2. PowerBuilder
  3. Monday, 11 July 2022 22:35 PM UTC

Hello,

PowerBuilder 2021 Build 1288

We're in the process of trying to get the remote IP Address of the client machine when running our application through an RDP session.

We're successful at using the Windows WTSQuerySessionInformation API and get values for WTSClientName, WTSUserName, etc. but currently we're not able to get a value for WTSClientAddress.

It appears that according to the MSDN docs (https://docs.microsoft.com/en-us/windows/win32/api/wtsapi32/ne-wtsapi32-wts_info_class?redirectedfrom=MSDN):


"The IP address is offset by two bytes from the start of the Address member of the WTS_CLIENT_ADDRESS
structure."

Here's a snippet of code we're using to get the session info:

If WTSQuerySessionInformation(aul_hserver, aul_sessionid, aul_infoclass, lul_ppBuffer, lul_pBytesReturned) Then

    If lul_pBytesReturned > 0 Then
        ls_infovalue = String(lul_ppBuffer, 'address')
    end if

end if

How do we get the value out of the memory pointer skipping the first 2 bytes?

Thanks for your help in advance.

Greg

 

 

Accepted Answer
John Fauss Accepted Answer Pending Moderation
  1. Tuesday, 12 July 2022 02:04 AM UTC
  2. PowerBuilder
  3. # Permalink

Hi, Greg - 

Without seeing more of your code that calls the WTSQuerySessionInformationW WinAPI function, it's a little difficult to be certain, but if you can obtain the address of the WTS_CLIENT_ADDRESS structure into a Longptr variable and you have a PB equivalent to that structure, it shouldn't be hard. I very much recommend using Longptr variables whenever a memory address or a Windows handle are to be stored in the variable, as this datatype will be either 32-bits or 64-bits in length to correspond with the process bitness that the application is executing in.

For example, here's the structure (as exported source):

global type s_wts_client_address from structure
   long   l_addressfamily
   byte   b_address[20]
end type

You'll need an External Function Declaration to copy the structure from Windows memory to a PB instance of the structure:

SUBROUTINE CopyWTSClientAddress ( &
   REF s_wts_client_address   pStructure, &
   Longptr                    pSource, &
   Long                       ByteLen &
   ) LIBRARY "kernel32.dll" ALIAS FOR "RtlMoveMemory"

Then, copy the structure:

Constant Long AF_INET    = 2
Constant Long AF_INET6   = 23
Constant Long AF_IPX     = 6
Constant Long AF_NETBIOS = 17
Constant Long AF_UNSPEC  = 0

Longptr llptr_address
s_wts_client_address lstr_wtsca

// Assume llptr_address contains the address of the WTS_CLIENT_ADDRESS structure...

// The structure length is 24 bytes in either 32/64 bit processes.
CopyWTSClientAddress(lstr_wtsca,llptr_address,24)

// Examine the address family in the structure and bytes in the address array.
If lstr_wtsca.l_addressfamily = AF_INET Then
   // IPv4...
   // lstr_wtsca.b_address[3] is 2 bytes past the start of the start of the array.
ElseIf lstr_wtsca.l_addressfamily = AF_INET6 Then
   // IPv6...
  .
  .
  .
End If

I did a content search of all WinAPI header files to find the #define statements for the AF_xxxx constants mentioned in the WTS_CLIENT_ADDRESS documentation.

Disclaimer: I have not tried executing any of this code. And, I figure if you can get the local copy of the structure populated, you can figure out what to do with it.

Good luck! - John

Comment
  1. Gregory Rusak
  2. Tuesday, 12 July 2022 16:32 PM UTC
John,

This is shear brilliance!

Most appreciated.

Warm Regards,

Greg
  1. Helpful
There are no comments made yet.
Gregory Rusak Accepted Answer Pending Moderation
  1. Thursday, 14 July 2022 16:50 PM UTC
  2. PowerBuilder
  3. # 1

Not sure this will work, but here is an example of a C++ implementation that uses the API to get the Public IP Address. Can anyone shed some light on what this would look like in our PowerBuilder world?

typedef enum _WINSTATIONINFOCLASS {
    // ...
    WinStationRemoteAddress = 29,
    // ...
} WINSTATIONINFOCLASS;

#define LOGONID_CURRENT     ((ULONG)-1)

typedef struct {
    unsigned short sin_family;
    union {
        struct {
            USHORT sin_port;
            ULONG in_addr;
            UCHAR sin_zero[8];
        } ipv4;
        struct {
            USHORT sin6_port;
            ULONG sin6_flowinfo;
            USHORT sin6_addr[8];
            ULONG sin6_scope_id;
        } ipv6;
    };
} WINSTATIONREMOTEADDRESS,
*PWINSTATIONREMOTEADDRESS;

EXTERN_C
DECLSPEC_IMPORT
BOOLEAN
WINAPI
WinStationQueryInformationW(
                            _In_opt_ HANDLE hServer,
                            _In_ ULONG SessionId,
                            _In_ WINSTATIONINFOCLASS WinStationInformationClass,
                            _Out_writes_bytes_(WinStationInformationLength) PVOID pWinStationInformation,
                            _In_ ULONG WinStationInformationLength,
                            _Out_ PULONG pReturnLength
                            );

ULONG GetRdpClientAddressFromServerView()
{
    ULONG dwError = NOERROR;
    ULONG cb;

    union {
        SOCKADDR sa;
        SOCKADDR_IN sa4;
        SOCKADDR_IN6 sa6;
    };

    WINSTATIONREMOTEADDRESS ra;

    if (WinStationQueryInformationW(0, LOGONID_CURRENT, WinStationRemoteAddress, &ra, sizeof(ra), &cb))
    {
        switch (sa.sa_family = ra.sin_family)
        {
        case AF_INET:
            sa4.sin_port = ra.ipv4.sin_port;
            sa4.sin_addr.S_un.S_addr = ra.ipv4.in_addr;
            RtlZeroMemory(sa4.sin_zero, sizeof(sa4.sin_zero));
            cb = sizeof(SOCKADDR_IN);
            break;
        case AF_INET6:
            sa6.sin6_port = ra.ipv6.sin6_port;
            sa6.sin6_flowinfo = ra.ipv6.sin6_flowinfo;
            memcpy(&sa6.sin6_addr, &ra.ipv6.sin6_addr, sizeof(in6_addr));
            sa6.sin6_scope_id = ra.ipv6.sin6_scope_id;
            cb = sizeof(SOCKADDR_IN6);
            break;
        default:
            dwError = ERROR_GEN_FAILURE;
        }

        if (dwError == NOERROR)
        {
            // assume that WSAStartup already called
            // WSADATA wd;
            // WSAStartup(WINSOCK_VERSION, &wd);

            char AddressString[64];
            ULONG dwAddressStringLength = _countof(AddressString);
            if (WSAAddressToStringA(&sa, cb, 0, AddressString, &dwAddressStringLength) == NOERROR)
            {
                DbgPrint("client ip is %s\n", AddressString);
            }
            else
            {
                dwError = WSAGetLastError();
            }
        }
    }
    else
    {
        dwError = GetLastError();
    }

    return dwError;
}

Thanks for your help in advance.

Kind Regards,

Greg

Comment
There are no comments made yet.
Ronnie Po Accepted Answer Pending Moderation
  1. Thursday, 14 July 2022 15:53 PM UTC
  2. PowerBuilder
  3. # 2

Hi Greg,

I don't know if this is helpful, but if your DBMS is Sybase ASA or SQL Anywhere, there is a system procedure called sa_conn_properties() that you can use to get the IP address associated with a connection.

Example:

select Value from sa_conn_properties(<connection id>) where PropName = 'NodeAddress';

If you leave out the connection id parameter, the procedure returns information for all connections.

Comment
There are no comments made yet.
Gregory Rusak Accepted Answer Pending Moderation
  1. Wednesday, 13 July 2022 20:28 PM UTC
  2. PowerBuilder
  3. # 3

Hello John,

So what we've tried so far is to use your same call but with the WinStationRemoteAddress (29) info class. The issue I suppose we're having is what that structure looks like in PowerBuilder. I believe the WinStationRemoteAddress structure is as follows:

typedef struct {
    unsigned short sin_family;
    union {
        struct {
            USHORT sin_port;
            ULONG in_addr;
            UCHAR sin_zero[8];
        } ipv4;
        struct {
            USHORT sin6_port;
            ULONG sin6_flowinfo;
            USHORT sin6_addr[8];
            ULONG sin6_scope_id;
        } ipv6;
    };
}

So we've tried to create this same structure as follows:

global type s_winstationremoteaddress from structure
	unsignedinteger		sin_family
	s_ipv4		ipv4
	s_ipv6		ipv6
end type

global type s_ipv4 from structure
	unsignedinteger		sin_port
	unsignedlong		in_addr
	byte		sin_zero[8]
end type

global type s_ipv6 from structure
	unsignedinteger		sin6_port
	unsignedlong		sin6_flowinfo
	unsignedinteger		sin6_addr[8]
	unsignedlong		sin6_scope_id
end type

... but that doesn't appear to work. We guessed at a sizeof() this structure as 42 (2 bytes for uint, 4 for ulong, etc.)

Any insight? Are we approaching this the right way or missing the mark?

Regards,

Greg

Comment
  1. John Fauss
  2. Thursday, 14 July 2022 13:57 PM UTC
I think the WinStationQueryInformationW API function is not going to be able to accomplish what you want. What capabilities this API may have had at soime point in the past, from all the Microsoft documentation I can find, this API now does only one thing.

Admittedly, I've never attempted to interrogate a remote server for information, but everything I see in MS docs indicate it takes multiple steps and special permissions. There is the RpcWinStationQueryInformation API looks like it MIGHT work, but you need to pass it a handle to a server. To get the handle to the necessary server, you have to use RpcWinStationOpenServer. To call that API, you need a handle to a remote binding. That's where I stopped looking. The structure you've asked about appears to be used only with the RPC-flavor of this API function,



The nested structure layout you proposed will not work, regardless. Unions are memory-map overlays... a means of using the same chunk of memory in more than one way. PB does not directly support this, so you would have to come up with a single, alternative structure layout that allows you to access all of the memory locations (the storage utilized by the members of both/all union'd structures). You need to understand how structure members and nested structures are laid out in memory; Long/ULongs must begin on a memory address that is evenly divisible by 4, since they are 4 bytes in length. LongLongs must begin on a memory address that is evenly divisible by 8. In a 32-bit process, every structure (including a nested structure) begins AND ENDS on a memory address that is evenly divisble by 4 (32-bites = 4 bytes). In a 64-bit process, this becomes 8-bute alignment. Hidden, unusable bytes of "padding" are used between structure members and at the end of a structure to ensure these rules are followed.



If you want to learn more, please work through my four-part tutorial on interfacing PB applications with the Windows API in the PowerBuilder section of the Tech Articles area of the Appeon Community. There is a LOT of information and details in this tutorial, so it will take some time and effort to go through it all, but if you're going to be using the Windows API from PB, you really need to understand how it all works.



We who participate here, do so on a volunteer basis in an effort to help others, and I'm glad to do my part... to a point. The several hours I've already devoted on this issue is all I can do for you. You don't really need PB expertise at this point... you need expertise on how to interface with remote server(s) using the Windows API, and that person is not me. Once you find out HOW to do what you want to do in the Windows API's, then the information in the tutorial will help you along. Best wishes and good luck!



  1. Helpful 1
  1. Gregory Rusak
  2. Thursday, 14 July 2022 16:53 PM UTC
Thanks again John. Appreciate all you do for our community.

Kind Regards,

Greg
  1. Helpful
There are no comments made yet.
John Fauss Accepted Answer Pending Moderation
  1. Wednesday, 13 July 2022 02:01 AM UTC
  2. PowerBuilder
  3. # 4

I'm not sure the WinStationQueryInformationW API function will help you gain the information you wanting to collect. Nevertheless, I'll allow you to make that determination.

You'll need a WINSTATIONINFORMATIONW structure. Here's the exported source:

global type s_winstationinformationw from structure
   byte          b_reserved2[70]
   unsignedlong  ul_logonid
   byte          b_reserved3[1140]
end type

The structure length is 1244 (70+2+4+1140) bytes. The additional 2 bytes is needed to ensure that the ul_logonid member aligns with a doubleword.

You'll need the following two External Function Declarations:

FUNCTION Boolean WinStationQueryInformation ( &
   Longptr                      hServer, &
   UnsignedLong                 LogonId, &
   Long                         WinStationInformationClass, &
   REF s_winstationinformationw pWinStationInformation, &
   UnsignedLong                 WinStationInformationLength, &
   REF UnsignedLong             pReturnLength &
   ) LIBRARY "winsta.dll" ALIAS FOR "WinStationQueryInformationW"

FUNCTION Long GetLastError ( &
   ) LIBRARY "kernel32.dll"

Finally, here's code I put in a command button's Clicked event:

Boolean lb_rc
ULong   lul_logonid = 4294967295 // 0xffffffff
ULong   lul_return_length
ULong   lul_winstationinfoclass = 8
ULong   lul_wsi_length = 1216
Long    ll_error_code
Longptr llptr_hserver = 0
s_winstationinformationw lstr_wsi

lb_rc = WinStationQueryInformation(llptr_hserver,lul_logonid, &
           lul_winstationinfoclass,lstr_wsi,lul_wsi_length,lul_return_length)
If lb_rc Then
   MessageBox("WinStationQueryInformation","API function successful.")
Else
   ll_error_code = GetLastError()
   MessageBox("WinStationQueryInformation","API function unsuccessful. Error Code = " + &
      String(ll_error_code))
End If

Good luck! -John

 

Comment
  1. Gregory Rusak
  2. Wednesday, 13 July 2022 19:00 PM UTC
Hello John,

I find myself both amazed what you come up with and confused what to do with it.

Running with what you have provided does in fact return SUCCESS and populates the s_winstationinformationw structure.

What I'm confused about is how to go about now getting the Public IP address from this.



From what I can see, you are referencing the _WINSTATIONINFOCLASS class for WinStationInformation (8) but the remote address should be in WinStationRemoteAddress (29) which has a totally different structure and obviously length too.



Or is the IP Address information somehow contained within the s_winstationinformationw structure itself (http://pinvoke.net/default.aspx/Structures/WINSTATIONINFORMATIONW.html) and I'm missing this totally?



Appreciate any and all feedback.



Warm Reagrds,

Greg
  1. Helpful
  1. John Fauss
  2. Wednesday, 13 July 2022 20:18 PM UTC
I did what the MS docs for the WinStationQueryInformationW API function stated.

https://docs.microsoft.com/en-us/previous-versions/aa383827(v=vs.85)

This states that the third argument "Must be the WinStationInformation value of the WINSTATIONINFOCLASS enumeration." A search of all of the header files in Windows SDK show that the ONLY enumeration for WINSTATIONINFOCLASS is "WinStationInformation = 8" (winternl.h was the only hit I found), so that is what I used. The layout for the structure immediately follows this enumeration definition.

Until now, I've seen no references to any other value, such as the WinStationRemoteAddress (29) you've referred to. A search return this documentation:

https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tsts/f333c223-de8a-46e1-a83e-79cbdab92371

Please note this is to be used with the RpcWinStationQueryInformation API (a remote procedure call), not WinStationQueryInformation.

https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tsts/1bba9ff2-71d3-49a3-bb26-2e5f6fcab3ee

The doc explains that the caller needs permission to be able to execute this API function.



Since the information you are wanting appears to be difficult to obtain and highly specialized, perhaps you might have more success asking how to obtain the IP address of a remote desktop via WinAPI/C++ calls (not using .NET) on a Windows development forum, such as StackOverflow. I would NOT mention that you are needing to accomplish this from PowerBuilder, as this will limit your responses. Just a suggestion.
  1. Helpful
  1. Gregory Rusak
  2. Wednesday, 13 July 2022 23:32 PM UTC
Hello John,

Thanks for the quick feedback.

In fact I've already referenced a C++ implementation (and that's how I arrived at the WinStationRemoteAddress (29) idea)

https://stackoverflow.com/questions/63493633/getting-public-ip-address-of-a-remotes-desktop-client

Unfortunately no matter what I try (even though the call returns SUCCESS) the returned length is always 0 and the structure I built is not populated

I'm running out of things to throw at this. Any ideas are appreciated.

Regards,

Greg
  1. Helpful
There are no comments made yet.
Gregory Rusak Accepted Answer Pending Moderation
  1. Tuesday, 12 July 2022 20:22 PM UTC
  2. PowerBuilder
  3. # 5

Hello John,

Well, as it turns out the Windows API call WTSQuerySessionInformation is NOT going to work for us as it appears to return the local IP and not the public one.

From what we can tell, we may be able to use WinStationQueryInformation instead (https://docs.microsoft.com/en-us/previous-versions//aa383827(v=vs.85)?redirectedfrom=MSDN) albeit it is not supported and may be changed in the future, as of now we really don't have any other options.

However, we're not able to construct the PowerScript calls to get it to work and hoping your "shear brilliance" can shed some light on an approach to consume that API call in PowerBuilder.

Any help or insight is once again very much appreciated.

Kind Regards,

Greg

Comment
There are no comments made yet.
  • Page :
  • 1


There are no replies made for this question yet.
However, you are not allowed to reply to this question.