/*
 * Copyright (c) 2000-2009 TeamDev Ltd. All rights reserved.
 * TeamDev PROPRIETARY and CONFIDENTIAL.
 * Use is subject to license terms.
 */
package com.jniwrapper.win32.security;

import com.jniwrapper.*;
import com.jniwrapper.win32.system.AdvApi32;
import com.jniwrapper.win32.system.LocalMemoryBlock;
import com.jniwrapper.win32.LastErrorException;

/**
 * This class is the wrapper for the SID structure.
 *
 * @see <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa379594(v=vs.85).aspx">
 * Security Identifier (SID) structure</a>
 *
 * @author Vadim Ridosh
 */

public class Sid extends Structure
{
    private static final String FUNCTION_LOOKUP_ACCOUNT_SID = "LookupAccountSidA";
    private static final String FUNCTION_IS_VALID_SID = "IsValidSid";
    private static final String FUNCTION_GET_LENGTH_SID = "GetLengthSid";
    private static final String FUNCTION_EQUAL_SID = "EqualSid";
    private static final String FUNCTION_INITIALIZE_SID = "InitializeSid";
    private static final String FUNCTION_GET_SID_SUB_AUTHORITY_COUNT = "GetSidSubAuthorityCount";
    private static final String FUNCTION_GET_SID_SUB_AUTHORITY = "GetSidSubAuthority";
    private static final String FUNCTION_CONVERT_SID_TO_STRING_SID = "ConvertSidToStringSidW";

    // Well-known RIDs here:
    public static final long SECURITY_DIALUP_RID = 0x00000001L;
    public static final long SECURITY_NETWORK_RID = 0x00000002L;
    public static final long SECURITY_BATCH_RID = 0x00000003L;
    public static final long SECURITY_INTERACTIVE_RID = 0x00000004L;
    public static final long SECURITY_LOGON_IDS_RID = 0x00000005L;
    public static final long SECURITY_SERVICE_RID = 0x00000006L;
    public static final long SECURITY_ANONYMOUS_LOGON_RID = 0x00000007L;
    public static final long SECURITY_PROXY_RID = 0x00000008L;
    public static final long SECURITY_ENTERPRISE_CONTROLLERS_RID = 0x00000009L;
    public static final long SECURITY_SERVER_LOGON_RID = SECURITY_ENTERPRISE_CONTROLLERS_RID;
    public static final long SECURITY_PRINCIPAL_SELF_RID = 0x0000000AL;
    public static final long SECURITY_AUTHENTICATED_USER_RID = 0x0000000BL;
    public static final long SECURITY_RESTRICTED_CODE_RID = 0x0000000CL;
    public static final long SECURITY_TERMINAL_SERVER_RID = 0x0000000DL;
    public static final long SECURITY_REMOTE_LOGON_RID = 0x0000000EL;
    public static final long SECURITY_THIS_ORGANIZATION_RID = 0x0000000FL;
    public static final long SECURITY_IUSER_RID = 0x00000011L;
    public static final long SECURITY_LOCAL_SYSTEM_RID = 0x00000012L;
    public static final long SECURITY_LOCAL_SERVICE_RID = 0x00000013L;
    public static final long SECURITY_NETWORK_SERVICE_RID = 0x00000014L;

    public static final long DOMAIN_ALIAS_RID_ADMINS = 0x00000220;
    public static final long DOMAIN_ALIAS_RID_USERS = 0x00000221;
    public static final long DOMAIN_ALIAS_RID_GUESTS = 0x00000222;
    public static final long DOMAIN_ALIAS_RID_POWER_USERS = 0x00000223;

    public static final long DOMAIN_ALIAS_RID_ACCOUNT_OPS = 0x00000224;
    public static final long DOMAIN_ALIAS_RID_SYSTEM_OPS = 0x00000225;
    public static final long DOMAIN_ALIAS_RID_PRINT_OPS = 0x00000226;
    public static final long DOMAIN_ALIAS_RID_BACKUP_OPS = 0x00000227;

    public static final long DOMAIN_ALIAS_RID_REPLICATOR = 0x00000228;
    public static final long DOMAIN_ALIAS_RID_RAS_SERVERS = 0x00000229;
    public static final long DOMAIN_ALIAS_RID_PREW2KCOMPACCESS = 0x0000022A;
    public static final long DOMAIN_ALIAS_RID_REMOTE_DESKTOP_USERS = 0x0000022B;
    public static final long DOMAIN_ALIAS_RID_NETWORK_CONFIGURATION_OPS = 0x0000022C;
    public static final long DOMAIN_ALIAS_RID_INCOMING_FOREST_TRUST_BUILDERS = 0x0000022D;

    public static final long DOMAIN_ALIAS_RID_MONITORING_USERS = 0x0000022E;
    public static final long DOMAIN_ALIAS_RID_LOGGING_USERS = 0x0000022F;
    public static final long DOMAIN_ALIAS_RID_AUTHORIZATIONACCESS = 0x00000230;
    public static final long DOMAIN_ALIAS_RID_TS_LICENSE_SERVERS = 0x00000231;
    public static final long DOMAIN_ALIAS_RID_DCOM_USERS = 0x00000232;
    public static final long DOMAIN_ALIAS_RID_IUSERS = 0x00000238;
    public static final long DOMAIN_ALIAS_RID_CRYPTO_OPERATORS = 0x00000239;
    public static final long DOMAIN_ALIAS_RID_CACHEABLE_PRINCIPALS_GROUP = 0x0000023B;
    public static final long DOMAIN_ALIAS_RID_NON_CACHEABLE_PRINCIPALS_GROUP = 0x0000023C;
    public static final long DOMAIN_ALIAS_RID_EVENT_LOG_READERS_GROUP = 0x0000023D;

    public static final long SECURITY_NULL_RID = 0x00000000L;
    public static final long SECURITY_WORLD_RID = 0x00000000L;
    public static final long SECURITY_LOCAL_RID = 0x00000000L;
    public static final long SECURITY_BUILTIN_DOMAIN_RID = 0x00000020L;
    public static final long SECURITY_WRITE_RESTRICTED_CODE_RID = 0x00000021L;

    public static final long SECURITY_CREATOR_OWNER_RID = 0x00000000L;
    public static final long SECURITY_CREATOR_GROUP_RID = 0x00000001L;
    public static final long SECURITY_CREATOR_OWNER_SERVER_RID = 0x00000002L;
    public static final long SECURITY_CREATOR_GROUP_SERVER_RID = 0x00000003L;
    public static final long SECURITY_CREATOR_OWNER_RIGHTS_RID = 0x00000004L;

    /**
     * Sub-authority size
     */
    private static final int SUB_AUTHORITY_SIZE = 5;

    private Int8 _revision = new Int8();
    private Int8 _subAuthorityCount = new Int8();
    private SidIdentifierAuthority _identifierAuthority = new SidIdentifierAuthority();
    private Pointer.Void _pSubAuthority = new Pointer.Void();
    private PrimitiveArray _subAuthority = new PrimitiveArray(UInt32.class, SUB_AUTHORITY_SIZE);

    public Sid()
    {
        init(new Parameter[]{
                _revision,
                _subAuthorityCount,
                _identifierAuthority,
                _subAuthority
        },  (short) 8);
    }

    public Sid(SidIdentifierAuthority authority, long[] subAuthRids)
    {
        this();
        initializeSid(this, authority, subAuthRids);
    }

    // Universal SIDs
    public static Sid Null()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_NULL_SID_AUTHORITY,
            new long[] {SECURITY_NULL_RID});
    }

    public static Sid World()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_WORLD_SID_AUTHORITY,
            new long[] {SECURITY_WORLD_RID});
    }

    public static Sid Local()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_LOCAL_SID_AUTHORITY,
            new long[] {SECURITY_LOCAL_RID});
    }

    public static Sid CreatorOwner()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_CREATOR_SID_AUTHORITY,
            new long[] {SECURITY_CREATOR_OWNER_RID});
    }

    public static Sid CreatorGroup()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_CREATOR_SID_AUTHORITY,
            new long[] {SECURITY_CREATOR_GROUP_RID});
    }

    public static Sid CreatorOwnerServer()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_CREATOR_SID_AUTHORITY,
            new long[] {SECURITY_CREATOR_OWNER_SERVER_RID});
    }

    public static Sid CreatorGroupServer()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_CREATOR_SID_AUTHORITY,
            new long[] {SECURITY_CREATOR_GROUP_SERVER_RID});
    }

    // NT Sids
    public static Sid Dialup()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_NT_AUTHORITY,
            new long[] {SECURITY_DIALUP_RID});
    }

    public static Sid Network()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_NT_AUTHORITY,
            new long[] {SECURITY_NETWORK_RID});
    }

    public static Sid Batch()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_NT_AUTHORITY,
            new long[] {SECURITY_BATCH_RID});
    }

    public static Sid Interactive()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_NT_AUTHORITY,
            new long[] {SECURITY_INTERACTIVE_RID});
    }

    public static Sid Service()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_NT_AUTHORITY,
            new long[] {SECURITY_SERVICE_RID});
    }

    public static Sid AnonymousLogon()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_NT_AUTHORITY,
            new long[] {SECURITY_ANONYMOUS_LOGON_RID});
    }

    public static Sid Proxy()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_NT_AUTHORITY,
            new long[] {SECURITY_PROXY_RID});
    }

    public static Sid ServerLogon()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_NT_AUTHORITY,
            new long[] {SECURITY_SERVER_LOGON_RID});
    }

    public static Sid Self()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_NT_AUTHORITY,
            new long[] {SECURITY_PRINCIPAL_SELF_RID});
    }

    public static Sid AuthenticatedUser()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_NT_AUTHORITY,
            new long[] {SECURITY_AUTHENTICATED_USER_RID});
    }

    public static Sid RestrictedCode()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_NT_AUTHORITY,
            new long[] {SECURITY_RESTRICTED_CODE_RID});
    }

    public static Sid TerminalServer()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_NT_AUTHORITY,
            new long[] {SECURITY_TERMINAL_SERVER_RID});
    }

    public static Sid System()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_NT_AUTHORITY,
            new long[] {SECURITY_LOCAL_SYSTEM_RID});
    }

    public static Sid NetworkService()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_NT_AUTHORITY,
            new long[] {SECURITY_NETWORK_SERVICE_RID});
    }

    // NT Authority\BUILTIN
    public static Sid Admins()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_NT_AUTHORITY,
            new long[] {SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS});
    }

    public static Sid Users()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_NT_AUTHORITY,
            new long[] {SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_USERS});
    }

    public static Sid Guests()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_NT_AUTHORITY,
            new long[] {SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_GUESTS});
    }

    public static Sid PowerUsers()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_NT_AUTHORITY,
            new long[] {SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_POWER_USERS});
    }

    public static Sid AccountOps()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_NT_AUTHORITY,
            new long[] {SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ACCOUNT_OPS});
    }

    public static Sid SystemOps()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_NT_AUTHORITY,
            new long[] {SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_SYSTEM_OPS});
    }

    public static Sid PrintOps()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_NT_AUTHORITY,
            new long[] {SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_PRINT_OPS});
    }

    public static Sid BackupOps()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_NT_AUTHORITY,
            new long[] {SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_BACKUP_OPS});
    }

    public static Sid Replicator()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_NT_AUTHORITY,
            new long[] {SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_REPLICATOR});
    }

    public static Sid RasServers()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_NT_AUTHORITY,
            new long[] {SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_RAS_SERVERS});
    }

    public static Sid PreW2KAccess()
    {
        return new Sid(SidIdentifierAuthority.SECURITY_NT_AUTHORITY,
            new long[] {SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_PREW2KCOMPACCESS});
    }

    public Int8 getRevision()
    {
        return _revision;
    }

    public void setRevision(Int8 revision)
    {
        _revision = revision;
    }

    public Int8 getSubAuthorityCount()
    {
        return _subAuthorityCount;
    }

    public void setSubAuthorityCount(Int8 subAuthorityCount)
    {
        _subAuthorityCount = subAuthorityCount;
    }

    public SidIdentifierAuthority getIdentifierAuthority()
    {
        return _identifierAuthority;
    }

    public void setIdentifierAuthority(SidIdentifierAuthority identifierAuthority)
    {
        _identifierAuthority = identifierAuthority;
    }

    public PrimitiveArray getSubAuthority()
    {
        return _subAuthority;
    }

    public void setSubAuthority(PrimitiveArray subAuthority)
    {
        _subAuthority = subAuthority;
    }

    public Pointer.Void getpSubAuthority()
    {
        return _pSubAuthority;
    }

    public void setpSubAuthority(Pointer.Void pSubAuthority)
    {
        _pSubAuthority = pSubAuthority;
    }

    public boolean lookupAccountSid(AnsiString name, AnsiString domainName)
    {
        if (!isValidSid(this))
        {
            return false;
        }
        Function lookupAccountSid = AdvApi32.get(FUNCTION_LOOKUP_ACCOUNT_SID);
        IntBool result = new IntBool();
        Int accountType = new Int();
        UInt32 nameSize = new UInt32(100);
        UInt32 domainNameSize = new UInt32(100);

        lookupAccountSid.invoke(result, new Parameter[]{new Pointer.Void(), new Pointer(this), name, new Pointer(nameSize), domainName, new Pointer(domainNameSize), new Pointer(accountType)});
        return result.getValue() != 0;
    }

    public boolean isValidSid(Sid sid)
    {
        Function isValidSid = AdvApi32.get(FUNCTION_IS_VALID_SID);
        IntBool result = new IntBool();
        isValidSid.invoke(result, new Pointer(sid));
        return result.getValue() != 0;
    }

    private long getLengthSid(Sid sid)
    {
        Function isValidSid = AdvApi32.get(FUNCTION_GET_LENGTH_SID);
        IntBool result = new IntBool();
        isValidSid.invoke(result, new Pointer(sid));
        return result.getValue();
    }

    public boolean equals(Object obj)
    {
        if (!(obj instanceof Sid))
        {
            return false;
        }

        return compareSIDs(this, obj);
    }

    /**
     * Compares two SIDs for equality
     * @param sid1 first SID to compare
     * @param sid2 second SID to compare
     * @return true for equal SIDs
     */
    public static boolean compareSIDs(Sid sid1, Object sid2)
    {
        Sid src = (Sid) sid2;
        if (!sid1.isValidSid(sid1) || !sid1.isValidSid(src))
        {
            throw new RuntimeException("Attempt to compare invalid sid");
        }

        Function equalSid = AdvApi32.get(FUNCTION_EQUAL_SID);
        IntBool res = new IntBool();
        equalSid.invoke(res, new Pointer.Const(sid1), new Pointer.Const(src));
        return res.getBooleanValue();
    }

    private static void initializeSid(Sid sid, SidIdentifierAuthority authority, long[] subAuthRids)
    {
        Function initializeSid = AdvApi32.get(FUNCTION_INITIALIZE_SID);
        IntBool res = new IntBool();
        long lastError = initializeSid.invoke(res,
                new Pointer(sid), new Pointer.Const(authority), new UInt8((short)subAuthRids.length));

        if (!res.getBooleanValue())
        {
            throw new LastErrorException(lastError, "Unable to initialize SID");
        }

        for (int i = 0; i < subAuthRids.length; i++)
        {
            UInt32 rid = getSidSubAuthority(sid, i);
            rid.setValue(subAuthRids[i]);
        }
    }

    private static UInt32 getSidSubAuthority(Sid sid, int index)
    {
        Function getSidSubAuthority = AdvApi32.get(FUNCTION_GET_SID_SUB_AUTHORITY);
        Pointer ptrRes = new Pointer(new UInt32(), true);
        long lastError = getSidSubAuthority.invoke(ptrRes, new Pointer.Const(sid), new UInt32(index));
        if (ptrRes.isNull())
        {
            throw new LastErrorException(lastError);
        }
        return (UInt32) ptrRes.getReferencedObject();
    }

    private static UInt8 getSidSubAuthorityCount(Sid sid)
    {
        Function getSidSubAuthority = AdvApi32.get(FUNCTION_GET_SID_SUB_AUTHORITY_COUNT);
        Pointer ptrRes = new Pointer(null, true);
        getSidSubAuthority.invoke(ptrRes, new Pointer.Const(sid));
        return (UInt8) ptrRes.getReferencedObject();
    }

    /**
     * Convert SID to it's string representation (f.e. "S-1-5-15")
     * @return string representation of SID
     */
    public String convertSidToStringSid()
    {
        Function convertSidToStringSid = AdvApi32.get(FUNCTION_CONVERT_SID_TO_STRING_SID);
        IntBool res = new IntBool();

        ExternalStringPointer str = new ExternalStringPointer(CharacterEncoding.Unicode);
        long lastError = convertSidToStringSid.invoke(res, new Pointer(this), new Pointer.OutOnly(str));
        if (!res.getBooleanValue())
        {
            throw new LastErrorException(lastError);
        }
        String result = str.readString();
        LocalMemoryBlock.localFree(str);
        return result;
    }

    public Object clone()
    {
        Sid sid = new Sid();
        sid.initFrom(this);
        return sid;
    }
}