mirror of https://github.com/dahall/Vanara.git
Untested: Complete translation of all Authz and Secur32 functions and structs
parent
0cc1d914d0
commit
539f56d373
|
@ -342,6 +342,7 @@ namespace Vanara.PInvoke
|
||||||
/// more information, see LSA Policy Function Return Values. You can use the LsaNtStatusToWinError function to convert the NTSTATUS
|
/// more information, see LSA Policy Function Return Values. You can use the LsaNtStatusToWinError function to convert the NTSTATUS
|
||||||
/// code to a Windows error code.
|
/// code to a Windows error code.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
|
[PInvokeData("ntlsa.h")]
|
||||||
[DllImport(Lib.AdvApi32, ExactSpelling = true)]
|
[DllImport(Lib.AdvApi32, ExactSpelling = true)]
|
||||||
public static extern uint LsaCreateAccount(LSA_HANDLE PolicyHandle, PSID AccountSid, LsaAccountAccessMask DesiredAccess, out SafeLSA_HANDLE AccountHandle);
|
public static extern uint LsaCreateAccount(LSA_HANDLE PolicyHandle, PSID AccountSid, LsaAccountAccessMask DesiredAccess, out SafeLSA_HANDLE AccountHandle);
|
||||||
|
|
||||||
|
@ -501,6 +502,7 @@ namespace Vanara.PInvoke
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// <para>If the function succeeds, the function returns one of the following <c>NTSTATUS</c> values.</para>
|
/// <para>If the function succeeds, the function returns one of the following <c>NTSTATUS</c> values.</para>
|
||||||
/// </returns>
|
/// </returns>
|
||||||
|
[PInvokeData("ntlsa.h")]
|
||||||
[DllImport(Lib.AdvApi32, ExactSpelling = true)]
|
[DllImport(Lib.AdvApi32, ExactSpelling = true)]
|
||||||
public static extern uint LsaGetSystemAccessAccount(LSA_HANDLE AccountHandle, out int SystemAccess);
|
public static extern uint LsaGetSystemAccessAccount(LSA_HANDLE AccountHandle, out int SystemAccess);
|
||||||
|
|
||||||
|
@ -853,6 +855,7 @@ namespace Vanara.PInvoke
|
||||||
/// <param name="DesiredAccess">The desired access.</param>
|
/// <param name="DesiredAccess">The desired access.</param>
|
||||||
/// <param name="AccountHandle">The account handle.</param>
|
/// <param name="AccountHandle">The account handle.</param>
|
||||||
/// <returns>NTSTATUS</returns>
|
/// <returns>NTSTATUS</returns>
|
||||||
|
[PInvokeData("ntlsa.h")]
|
||||||
[DllImport(Lib.AdvApi32, ExactSpelling = true)]
|
[DllImport(Lib.AdvApi32, ExactSpelling = true)]
|
||||||
public static extern uint LsaOpenAccount(LSA_HANDLE PolicyHandle, PSID AccountSid, LsaAccountAccessMask DesiredAccess, out SafeLSA_HANDLE AccountHandle);
|
public static extern uint LsaOpenAccount(LSA_HANDLE PolicyHandle, PSID AccountSid, LsaAccountAccessMask DesiredAccess, out SafeLSA_HANDLE AccountHandle);
|
||||||
|
|
||||||
|
@ -1087,6 +1090,7 @@ namespace Vanara.PInvoke
|
||||||
/// If the function succeeds, the return value is STATUS_SUCCESS. If the function fails, the return value is an NTSTATUS code, which
|
/// If the function succeeds, the return value is STATUS_SUCCESS. If the function fails, the return value is an NTSTATUS code, which
|
||||||
/// can be one of the following values or one of the LSA Policy Function Return Values.
|
/// can be one of the following values or one of the LSA Policy Function Return Values.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
|
[PInvokeData("ntlsa.h")]
|
||||||
[DllImport(Lib.AdvApi32, ExactSpelling = true)]
|
[DllImport(Lib.AdvApi32, ExactSpelling = true)]
|
||||||
public static extern uint LsaSetSystemAccessAccount(LSA_HANDLE AccountHandle, int SystemAccess);
|
public static extern uint LsaSetSystemAccessAccount(LSA_HANDLE AccountHandle, int SystemAccess);
|
||||||
|
|
||||||
|
@ -1239,6 +1243,83 @@ namespace Vanara.PInvoke
|
||||||
// public static extern _IRQL_requires_same_ NTSTATUS LsaFreeReturnBuffer(IntPtr Buffer);
|
// public static extern _IRQL_requires_same_ NTSTATUS LsaFreeReturnBuffer(IntPtr Buffer);
|
||||||
private static extern uint LsaFreeReturnBuffer(IntPtr Buffer);
|
private static extern uint LsaFreeReturnBuffer(IntPtr Buffer);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <para>
|
||||||
|
/// The <c>DOMAIN_PASSWORD_INFORMATION</c> structure contains information about a domain's password policy, such as the minimum
|
||||||
|
/// length for passwords and how unique passwords must be.
|
||||||
|
/// </para>
|
||||||
|
/// <para>It is used in the MSV1_0_CHANGEPASSWORD_RESPONSE structure.</para>
|
||||||
|
/// </summary>
|
||||||
|
// https://docs.microsoft.com/en-us/windows/desktop/api/ntsecapi/ns-ntsecapi-_domain_password_information typedef struct
|
||||||
|
// _DOMAIN_PASSWORD_INFORMATION { USHORT MinPasswordLength; USHORT PasswordHistoryLength; ULONG PasswordProperties; #if ...
|
||||||
|
// OLD_LARGE_INTEGER MaxPasswordAge; #if ... OLD_LARGE_INTEGER MinPasswordAge; #else LARGE_INTEGER MaxPasswordAge; #endif #else
|
||||||
|
// LARGE_INTEGER MinPasswordAge; #endif } DOMAIN_PASSWORD_INFORMATION, *PDOMAIN_PASSWORD_INFORMATION;
|
||||||
|
[PInvokeData("ntsecapi.h", MSDNShortId = "7dceaf70-d8de-47c0-b940-f0d6a0cca101")]
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct DOMAIN_PASSWORD_INFORMATION
|
||||||
|
{
|
||||||
|
/// <summary>Specifies the minimum length, in characters, of a valid password.</summary>
|
||||||
|
public ushort MinPasswordLength;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates the number of previous passwords saved in the history list. A user cannot reuse a password in the history list.
|
||||||
|
/// </summary>
|
||||||
|
public ushort PasswordHistoryLength;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <para>Flags that describe the password properties. They can be one or more of the following values.</para>
|
||||||
|
/// <list type="table">
|
||||||
|
/// <listheader>
|
||||||
|
/// <term>Value</term>
|
||||||
|
/// <term>Meaning</term>
|
||||||
|
/// </listheader>
|
||||||
|
/// <item>
|
||||||
|
/// <term>DOMAIN_PASSWORD_COMPLEX 0x00000001L</term>
|
||||||
|
/// <term>The password must have a mix of at least two of the following types of characters:</term>
|
||||||
|
/// </item>
|
||||||
|
/// <item>
|
||||||
|
/// <term>DOMAIN_PASSWORD_NO_ANON_CHANGE 0x00000002L</term>
|
||||||
|
/// <term>
|
||||||
|
/// The password cannot be changed without logging on. Otherwise, if your password has expired, you can change your password and
|
||||||
|
/// then log on.
|
||||||
|
/// </term>
|
||||||
|
/// </item>
|
||||||
|
/// <item>
|
||||||
|
/// <term>DOMAIN_PASSWORD_NO_CLEAR_CHANGE 0x00000004L</term>
|
||||||
|
/// <term>Forces the client to use a protocol that does not allow the domain controller to get the plaintext password.</term>
|
||||||
|
/// </item>
|
||||||
|
/// <item>
|
||||||
|
/// <term>DOMAIN_LOCKOUT_ADMINS 0x00000008L</term>
|
||||||
|
/// <term>Allows the built-in administrator account to be locked out from network logons.</term>
|
||||||
|
/// </item>
|
||||||
|
/// <item>
|
||||||
|
/// <term>DOMAIN_PASSWORD_STORE_CLEARTEXT 0x00000010L</term>
|
||||||
|
/// <term>The directory service is storing a plaintext password for all users instead of a hash function of the password.</term>
|
||||||
|
/// </item>
|
||||||
|
/// <item>
|
||||||
|
/// <term>DOMAIN_REFUSE_PASSWORD_CHANGE 0x00000020L</term>
|
||||||
|
/// <term>
|
||||||
|
/// Removes the requirement that the machine account password be automatically changed every week. This value should not be used
|
||||||
|
/// as it can weaken security.
|
||||||
|
/// </term>
|
||||||
|
/// </item>
|
||||||
|
/// </list>
|
||||||
|
/// </summary>
|
||||||
|
public uint PasswordProperties;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A 64-bit value, with delta time syntax, indicating the policy setting for the maximum time allowed before a password reset or
|
||||||
|
/// change is required.
|
||||||
|
/// </summary>
|
||||||
|
public long MaxPasswordAge;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A 64-bit value, with delta time syntax, indicating the policy setting for the minimum time allowed before a password change
|
||||||
|
/// operation is allowed.
|
||||||
|
/// </summary>
|
||||||
|
public long MinPasswordAge;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <para>The <c>LSA_AUTH_INFORMATION</c> structure contains authentication information for a trusted domain.</para>
|
/// <para>The <c>LSA_AUTH_INFORMATION</c> structure contains authentication information for a trusted domain.</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Vanara.InteropServices;
|
||||||
|
|
||||||
|
namespace Vanara.PInvoke
|
||||||
|
{
|
||||||
|
public static partial class Authz
|
||||||
|
{
|
||||||
|
/// <summary>The <c>AUDIT_PARAM_TYPE</c> enumeration defines the type of audit parameters that are available.</summary>
|
||||||
|
// https://docs.microsoft.com/en-us/windows/desktop/api/adtgen/ne-adtgen-_audit_param_type typedef enum _AUDIT_PARAM_TYPE { APT_None,
|
||||||
|
// APT_String, APT_Ulong, APT_Pointer, APT_Sid, APT_LogonId, APT_ObjectTypeList, APT_Luid, APT_Guid, APT_Time, APT_Int64,
|
||||||
|
// APT_IpAddress, APT_LogonIdWithSid } AUDIT_PARAM_TYPE;
|
||||||
|
[PInvokeData("adtgen.h", MSDNShortId = "1ECC866A-2DD3-4EE4-B2CC-7F5ADF7FFC99")]
|
||||||
|
public enum AUDIT_PARAM_TYPE
|
||||||
|
{
|
||||||
|
/// <summary>No audit options.</summary>
|
||||||
|
APT_None = 1,
|
||||||
|
|
||||||
|
/// <summary>A string that terminates with NULL.</summary>
|
||||||
|
APT_String,
|
||||||
|
|
||||||
|
/// <summary>An unsigned long.</summary>
|
||||||
|
APT_Ulong,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A pointer that is used to specify handles and pointers. These are 32-bit on 32-bit systems and 64-bit on 64-bit systems. Use
|
||||||
|
/// this option when you are interested in the absolute value of the pointer. The memory to which the pointer points is not
|
||||||
|
/// marshaled when using this type.
|
||||||
|
/// </summary>
|
||||||
|
APT_Pointer,
|
||||||
|
|
||||||
|
/// <summary>The security identifier (SID).</summary>
|
||||||
|
APT_Sid,
|
||||||
|
|
||||||
|
/// <summary>The logon identifier (LUID) that results in three output parameters:</summary>
|
||||||
|
APT_LogonId,
|
||||||
|
|
||||||
|
/// <summary>Object type list.</summary>
|
||||||
|
APT_ObjectTypeList,
|
||||||
|
|
||||||
|
/// <summary>LUID that is not translated to LogonId.</summary>
|
||||||
|
APT_Luid,
|
||||||
|
|
||||||
|
/// <summary>GUID.</summary>
|
||||||
|
APT_Guid,
|
||||||
|
|
||||||
|
/// <summary>Time as FILETIME.</summary>
|
||||||
|
APT_Time,
|
||||||
|
|
||||||
|
/// <summary>ULONGLONG.</summary>
|
||||||
|
APT_Int64,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// IP Address (IPv4 and IPv6). This logs the address as the first parameter and the port as the second parameter. You must
|
||||||
|
/// ensure that two entries are added in the event message file. You should ensure that the buffer size is 128 bytes.
|
||||||
|
/// </summary>
|
||||||
|
APT_IpAddress,
|
||||||
|
|
||||||
|
/// <summary>Logon ID with SID that results in four output parameters:</summary>
|
||||||
|
APT_LogonIdWithSid,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Structure that defines a single audit parameter. LsaGenAuditEvent accepts an array of such elements to represent the parameters
|
||||||
|
/// of the audit to be generated. It is best to initialize this structure using AdtInitParams function. This will ensure
|
||||||
|
/// compatibility with any future changes to this structure.
|
||||||
|
/// </summary>
|
||||||
|
[PInvokeData("adtgen.h")]
|
||||||
|
[StructLayout(LayoutKind.Explicit, Size = 32, CharSet = CharSet.Unicode)]
|
||||||
|
public struct AUDIT_PARAM
|
||||||
|
{
|
||||||
|
[FieldOffset(0)]
|
||||||
|
public AUDIT_PARAM_TYPE Type;
|
||||||
|
|
||||||
|
[FieldOffset(4)]
|
||||||
|
public uint Length;
|
||||||
|
|
||||||
|
[FieldOffset(8)]
|
||||||
|
public uint Flags;
|
||||||
|
|
||||||
|
[FieldOffset(16)]
|
||||||
|
public IntPtr Data0;
|
||||||
|
|
||||||
|
[FieldOffset(16)]
|
||||||
|
public StrPtrUni String;
|
||||||
|
|
||||||
|
[FieldOffset(16)]
|
||||||
|
public IntPtr u;
|
||||||
|
|
||||||
|
[FieldOffset(16)]
|
||||||
|
public PSID psid;
|
||||||
|
|
||||||
|
[FieldOffset(16)]
|
||||||
|
public IntPtr pguid;
|
||||||
|
|
||||||
|
[FieldOffset(16)]
|
||||||
|
public int LogonId_LowPart;
|
||||||
|
|
||||||
|
[FieldOffset(16)]
|
||||||
|
public IntPtr pObjectTypes;
|
||||||
|
|
||||||
|
[FieldOffset(16)]
|
||||||
|
public IntPtr pIpAddress;
|
||||||
|
|
||||||
|
[FieldOffset(24)]
|
||||||
|
public IntPtr Data1;
|
||||||
|
|
||||||
|
[FieldOffset(24)]
|
||||||
|
public int LogonId_HighPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Audit parameters passed to LsaGenAuditEvent.</summary>
|
||||||
|
[PInvokeData("adtgen.h")]
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct AUDIT_PARAMS
|
||||||
|
{
|
||||||
|
public uint Length;
|
||||||
|
|
||||||
|
public uint Flags;
|
||||||
|
|
||||||
|
public ushort Count;
|
||||||
|
|
||||||
|
public IntPtr Parameters;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -171,60 +171,5 @@ namespace Vanara.PInvoke
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override bool InternalReleaseHandle() => LsaDeregisterLogonProcess(this).Succeeded;
|
protected override bool InternalReleaseHandle() => LsaDeregisterLogonProcess(this).Succeeded;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
AuditComputeEffectivePolicyBySid function
|
|
||||||
AuditComputeEffectivePolicyByToken function
|
|
||||||
AuditEnumerateCategories function
|
|
||||||
AuditEnumeratePerUserPolicy function
|
|
||||||
AuditEnumerateSubCategories function
|
|
||||||
AuditFree function
|
|
||||||
AuditLookupCategoryGuidFromCategoryId function
|
|
||||||
AuditLookupCategoryIdFromCategoryGuid function
|
|
||||||
AuditLookupCategoryNameA function
|
|
||||||
AuditLookupCategoryNameW function
|
|
||||||
AuditLookupSubCategoryNameA function
|
|
||||||
AuditLookupSubCategoryNameW function
|
|
||||||
AuditQueryGlobalSaclA function
|
|
||||||
AuditQueryGlobalSaclW function
|
|
||||||
AuditQueryPerUserPolicy function
|
|
||||||
AuditQuerySecurity function
|
|
||||||
AuditQuerySystemPolicy function
|
|
||||||
AuditSetGlobalSaclA function
|
|
||||||
AuditSetGlobalSaclW function
|
|
||||||
AuditSetPerUserPolicy function
|
|
||||||
AuditSetSecurity function
|
|
||||||
AuditSetSystemPolicy function
|
|
||||||
KERB_CRYPTO_KEY structure
|
|
||||||
LsaCallAuthenticationPackage function
|
|
||||||
LsaCreateTrustedDomainEx function
|
|
||||||
LsaDeleteTrustedDomain function
|
|
||||||
LsaEnumerateLogonSessions function
|
|
||||||
LsaEnumerateTrustedDomains function
|
|
||||||
LsaEnumerateTrustedDomainsEx function
|
|
||||||
LsaGetLogonSessionData function
|
|
||||||
LsaLogonUser function
|
|
||||||
LsaLookupNames function
|
|
||||||
LsaLookupSids function
|
|
||||||
LsaOpenTrustedDomainByName function
|
|
||||||
LsaQueryDomainInformationPolicy function
|
|
||||||
LsaQueryForestTrustInformation function
|
|
||||||
LsaQueryInformationPolicy function
|
|
||||||
LsaQueryTrustedDomainInfoByName function
|
|
||||||
LsaRegisterPolicyChangeNotification function
|
|
||||||
LsaRetrievePrivateData function
|
|
||||||
LsaSetDomainInformationPolicy function
|
|
||||||
LsaSetForestTrustInformation function
|
|
||||||
LsaSetInformationPolicy function
|
|
||||||
LsaSetTrustedDomainInfoByName function
|
|
||||||
LsaStorePrivateData function
|
|
||||||
LsaUnregisterPolicyChangeNotification function
|
|
||||||
PSAM_INIT_NOTIFICATION_ROUTINE callback function
|
|
||||||
PSAM_PASSWORD_FILTER_ROUTINE callback function
|
|
||||||
PSAM_PASSWORD_NOTIFICATION_ROUTINE callback function
|
|
||||||
RtlDecryptMemory function
|
|
||||||
RtlEncryptMemory function
|
|
||||||
RtlGenRandom function
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,69 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Vanara.PInvoke
|
||||||
|
{
|
||||||
|
public static partial class Secur32
|
||||||
|
{
|
||||||
|
/// <summary>The <c>CREDSPP_SUBMIT_TYPE</c> enumeration specifies the type of credentials specified by a CREDSSP_CRED structure.</summary>
|
||||||
|
// https://docs.microsoft.com/en-us/windows/desktop/api/credssp/ne-credssp-_credssp_submit_type typedef enum _CREDSSP_SUBMIT_TYPE {
|
||||||
|
// CredsspPasswordCreds, CredsspSchannelCreds, CredsspCertificateCreds, CredsspSubmitBufferBoth, CredsspSubmitBufferBothOld,
|
||||||
|
// CredsspCredEx } CREDSPP_SUBMIT_TYPE;
|
||||||
|
[PInvokeData("credssp.h", MSDNShortId = "d30e219b-ea39-41da-b714-3ceb13a5614d")]
|
||||||
|
public enum CREDSSP_SUBMIT_TYPE
|
||||||
|
{
|
||||||
|
/// <summary>The credentials are a user name and password.</summary>
|
||||||
|
CredsspPasswordCreds = 2,
|
||||||
|
|
||||||
|
/// <summary>The credentials are Schannel credentials.</summary>
|
||||||
|
CredsspSchannelCreds = 4,
|
||||||
|
|
||||||
|
/// <summary>The credentials are in a certificate.</summary>
|
||||||
|
CredsspCertificateCreds = 13,
|
||||||
|
|
||||||
|
/// <summary>The credentials contain both certificate and Schannel credentials.</summary>
|
||||||
|
CredsspSubmitBufferBoth = 50,
|
||||||
|
|
||||||
|
/// <summary/>
|
||||||
|
CredsspSubmitBufferBothOld = 51,
|
||||||
|
|
||||||
|
/// <summary/>
|
||||||
|
CredsspCredEx = 100,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>The <c>CREDSSP_CRED</c> structure specifies authentication data for both Schannel and Negotiate security packages.</summary>
|
||||||
|
// https://docs.microsoft.com/en-us/windows/desktop/api/credssp/ns-credssp-_credssp_cred typedef struct _CREDSSP_CRED {
|
||||||
|
// CREDSPP_SUBMIT_TYPE Type; PVOID pSchannelCred; PVOID pSpnegoCred; } CREDSSP_CRED, *PCREDSSP_CRED;
|
||||||
|
[PInvokeData("credssp.h", MSDNShortId = "b22bd22c-e6e1-4817-b5cf-ab49f574e75f")]
|
||||||
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||||
|
public struct CREDSSP_CRED
|
||||||
|
{
|
||||||
|
/// <summary>The CREDSPP_SUBMIT_TYPE enumeration value that specifies the type of credentials contained in this structure.</summary>
|
||||||
|
public CREDSSP_SUBMIT_TYPE Type;
|
||||||
|
|
||||||
|
/// <summary>A pointer to a set of Schannel credentials.</summary>
|
||||||
|
public IntPtr pSchannelCred;
|
||||||
|
|
||||||
|
/// <summary>A pointer to a set of Negotiate credentials.</summary>
|
||||||
|
public IntPtr pSpnegoCred;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>The <c>CREDSSP_CRED_EX</c> structure specifies authentication data for both Schannel and Negotiate security packages.</summary>
|
||||||
|
// https://docs.microsoft.com/en-us/windows/desktop/api/credssp/ns-credssp-_credssp_cred typedef struct _CREDSSP_CRED {
|
||||||
|
[PInvokeData("credssp.h")]
|
||||||
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||||
|
public struct CREDSSP_CRED_EX
|
||||||
|
{
|
||||||
|
/// <summary>The CREDSPP_SUBMIT_TYPE enumeration value that specifies the type of credentials contained in this structure.</summary>
|
||||||
|
public CREDSSP_SUBMIT_TYPE Type;
|
||||||
|
|
||||||
|
public uint Version;
|
||||||
|
|
||||||
|
public uint Flags;
|
||||||
|
|
||||||
|
public uint Reserved;
|
||||||
|
|
||||||
|
public CREDSSP_CRED Cred;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue