#include <windows.h>
#include <stdio.h>
#include <accctrl.h>
#include <aclapi.h>
#include <fwpmu.h>

#pragma comment(lib, "fwpuclnt.lib")
#pragma comment(lib, "advapi32.lib")

#define EXIT_ON_ERROR(err) if((err) != ERROR_SUCCESS) {goto CLEANUP;}

#define EXIT_ON_LAST_ERROR(success, fnName) \
   if (!(success)) \
   { \
      result = GetLastError(); \
      printf(#fnName " = 0x%08X\n", result); \
      goto CLEANUP; \
   }

#define EXIT_ON_ALLOC_FAILURE(ptr, fnName) \
   if ((ptr) == NULL) \
   { \
      result = ERROR_NOT_ENOUGH_MEMORY; \
      printf(#fnName " = ERROR_NOT_ENOUGH_MEMORY\n"); \
      goto CLEANUP; \
   }

// Code from https://learn.microsoft.com/en-us/windows/win32/fwp/forcing-filter-deletion

DWORD ForceFilterDeletion(__in const GUID* filterKey)
{
    DWORD result = ERROR_SUCCESS;
    BOOL success;
    HANDLE token = NULL;
    DWORD userLen, oldPrivLen;
    TOKEN_USER* user = NULL;
    TOKEN_PRIVILEGES oldPriv, newPriv;
    BOOL takeOwnershipEnabled = FALSE;
    HANDLE engine = NULL;
    EXPLICIT_ACCESS_W access;
    PACL acl = NULL;

    // This assumes that the current thread is not impersonating. If it is
    // impersonating, you need to call OpenThreadToken instead.
    success = OpenProcessToken(
        GetCurrentProcess(),
        (TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY),
        &token
    );
    EXIT_ON_LAST_ERROR(success, OpenProcessToken);

    // Get our SID -- we'll use this to make ourselves owner.
    success = GetTokenInformation(
        token,
        TokenUser,
        NULL,
        0,
        &userLen
    );
    result = success ? ERROR_INVALID_DATA : GetLastError();
    if (result != ERROR_INSUFFICIENT_BUFFER)
    {
        EXIT_ON_ERROR(result);
    }

    user = (TOKEN_USER*)HeapAlloc(GetProcessHeap(), 0, userLen);
    EXIT_ON_ALLOC_FAILURE(user, HeapAlloc);

    success = GetTokenInformation(
        token,
        TokenUser,
        user,
        userLen,
        &userLen
    );
    EXIT_ON_LAST_ERROR(success, GetTokenInformation);

    newPriv.PrivilegeCount = 1;
    success = LookupPrivilegeValueW(
        NULL,
        SE_TAKE_OWNERSHIP_NAME,
        &(newPriv.Privileges[0].Luid)
    );
    EXIT_ON_LAST_ERROR(success, LookupPrivilegeValueW);
    newPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

    // Enable the TakeOwnership privilege. This must be done before the call to
    // FwpmEngineOpen0. The token used for authentication during the call to
    // FwpmEngineOpen0 is used for all future access checks as well.
    success = AdjustTokenPrivileges(
        token,
        FALSE,
        &newPriv,
        sizeof(oldPriv),
        &oldPriv,
        &oldPrivLen
    );
    EXIT_ON_LAST_ERROR(success, AdjustTokenPrivileges);

    // Check if anything changed, so we can restore the privilege to its
    // previous state.
    if (oldPriv.PrivilegeCount == 1)
    {
        takeOwnershipEnabled = TRUE;
    }

    // The authentication service should always be RPC_C_AUTHN_DEFAULT.
    result = FwpmEngineOpen0(
        NULL,
        RPC_C_AUTHN_DEFAULT,
        NULL,
        NULL,
        &engine
    );
    EXIT_ON_ERROR(result);
    printf("FwpmEngineOpen0 OK \n");

    // Make ourselves owner. This will succeed since anyone with the
    // TakeOwnership privilege enabled automatically has WRITE_OWNER access.
    result = FwpmProviderSetSecurityInfoByKey0(
        engine,
        filterKey,
        OWNER_SECURITY_INFORMATION,
        (const SID*)user->User.Sid,
        NULL,
        NULL,
        NULL
    );
    EXIT_ON_ERROR(result);
    printf("FwpmProviderSetSecurityInfoByKey0 OK to become OWNER\n");

    access.grfAccessPermissions = READ_ATTRIBUTES; // || READ_CONTROL
    access.grfAccessMode = GRANT_ACCESS;
    access.grfInheritance = 0;
    BuildTrusteeWithSid(&(access.Trustee), user->User.Sid);

    result = SetEntriesInAcl(1, &access, NULL, &acl);
    EXIT_ON_ERROR(result);

    // Grant ourselves access. This will succeed since the owner
    // automatically has WRITE_DAC access.

    // MECZ
    result = FwpmProviderSetSecurityInfoByKey0(
        engine,
        filterKey,
        DACL_SECURITY_INFORMATION,
        NULL,
        NULL,
        acl,
        NULL
    );
    EXIT_ON_ERROR(result);
    printf("FwpmProviderSetSecurityInfoByKey0 OK to get READ_ATTRIBUTES access\n");

CLEANUP:
    LocalFree(acl);
    // FwpmEngineClose0 accepts null engine handles, so we needn't precheck for
    // null.
    FwpmEngineClose0(engine);
    if (takeOwnershipEnabled)
    {
        newPriv.Privileges[0].Attributes = 0;
        AdjustTokenPrivileges(
            token,
            FALSE,
            &newPriv,
            0,
            NULL,
            0
        );
        printf("takeOwnershipEnabled \n");
    }
    HeapFree(GetProcessHeap(), 0, user);
    if (token != NULL)
    {
        CloseHandle(token);
        printf("Token != NULL\n");
    }
    printf("FwpmEngineClose0 cleaning \n");
    return result;
}

DWORD wmain(int argc,
    wchar_t* argv[])
{
    UNREFERENCED_PARAMETER(argc);
    UNREFERENCED_PARAMETER(argv);

    static const GUID guid = { 0xc15c0408, 0x2049, 0x43ff, { 0x92, 0xad, 0x26, 0xbf, 0x40, 0xf4, 0xd0, 0xaf } }; // Cisco Provider GUID

    // Open a session to the filter engine
    HANDLE engineHandle = 0;

    // Use dynamic sessions for efficiency and safety:
    //  - All objects associated with the dynamic session are deleted with one call.
    //  - Filtering policy objects are deleted even when the application crashes. 
    FWPM_SESSION0 session;
    memset(&session, 0, sizeof(session));
    session.flags = FWPM_SESSION_FLAG_DYNAMIC;
    //session.sessionKey = guid;

    DWORD result = FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, &engineHandle);
    EXIT_ON_ERROR(result);
    printf("FwpmEngineOpen0 OK \n");

    // Delete the newly added filter
    result = ForceFilterDeletion(&guid);
    EXIT_ON_ERROR(result);

    printf("Force becoming Owner of the Provider OK ! \n");

CLEANUP:
    FwpmEngineClose0(engineHandle);
    return result;
    printf("FwpmEngineClose0 OK ! \n");
}
