rodrigocfd / winsafe

Windows API and GUI in safe, idiomatic Rust.
https://crates.io/crates/winsafe
MIT License
518 stars 30 forks source link

[Question] - Calling windows APIs twice best practice #104

Closed lischach closed 10 months ago

lischach commented 1 year ago

There are some Windows APIs that need to be called twice in order to get them to work. For example - GetTokenInformation with TokenGroups as TokenInformationClass parameter. First time we call it with empty [out, optional] TokenInformation in order to get the correct [out] ReturnLength. Second time, we call it after we initialized the TOKEN_GROUPS struct with the correct size.

What is the best practice for achieving this behavior with winsafe crate? I took a look at GetTokenInformation implementation, but it looks like there is no way of using it with TokenGroups as input.

Am I missing anything? Is the recommended way for using these sorts of API calls, to fallback to using the windows or windows-sys crates?

rodrigocfd commented 1 year ago

WinSafe does perform both calls when needed, like in RegQueryValueEx. The problem with GetTokenInformation appears to be simply my lack of knowledge about how to properly use this function. I thought it received predetermined structures, not dynamically allocated ones, so the implementation is lacking, as far as I can see...

Can you show me an example in C, so I can fix it?

lischach commented 1 year ago

Sure. This stack overflow answer contains c++ code that calls GetTokenInformation twice.

You can ignore the code there that calls ConvertSidToStringSid since it's not relevant for the use case.

Attaching the relevant code from that post:

#define MAX_NAME 256
BOOL RetriveGroupSid(VOID)
{
    DWORD i, dwSize = 0, dwResult = 0;
    HANDLE hToken;
    PTOKEN_GROUPS pGroupInfo;
    SID_NAME_USE SidType;
    char lpName[MAX_NAME];
    char lpDomain[MAX_NAME];
    SID_IDENTIFIER_AUTHORITY SIDAuth = SECURITY_NT_AUTHORITY;

    // Open a handle to the access token for the calling process.

    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
    {
        printf("OpenProcessToken Error %u\n", GetLastError());
        return FALSE;
    }

    // Call GetTokenInformation to get the buffer size.

    if (!GetTokenInformation(hToken, TokenGroups, NULL, dwSize, &dwSize))
    {
        dwResult = GetLastError();
        if (dwResult != ERROR_INSUFFICIENT_BUFFER) {
            printf("GetTokenInformation Error %u\n", dwResult);
            return FALSE;
        }
    }

    // Allocate the buffer.

    pGroupInfo = (PTOKEN_GROUPS)GlobalAlloc(GPTR, dwSize);

    // Call GetTokenInformation again to get the group information.

    if (!GetTokenInformation(hToken, TokenGroups, pGroupInfo,
        dwSize, &dwSize))
    {
        printf("GetTokenInformation Error %u\n", GetLastError());
        return FALSE;
    }

    for (i = 0; i < pGroupInfo->GroupCount; i++)
    {
        dwSize = MAX_NAME;
        LPSTR sid;

...
rodrigocfd commented 10 months ago

This was way harder to implement that I thought it would be.

The function accepts only the co::TOKEN_INFORMATION_CLASS, then automatically allocs the proper struct, retrieves it, and returns it inside the enum, which will have the same variant. All the hard work is done inside the function, the API looks very clean to me.

An usage example can be found in the docs.

Let me know if this works for you.