mirror of
https://github.com/reactos/reactos.git
synced 2025-08-03 14:25:52 +00:00
SAMLIB: Implement SamQueryInformatioGroup and SamSetInformationGroup.
SAMSRV: Implement SamrQueryInformatioGroup and SamrSetInformationGroup. svn path=/trunk/; revision=56901
This commit is contained in:
parent
c27b9de07c
commit
069a20926a
3 changed files with 389 additions and 6 deletions
|
@ -685,6 +685,33 @@ SamQueryInformationDomain(IN SAM_HANDLE DomainHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
NTSTATUS
|
||||||
|
NTAPI
|
||||||
|
SamQueryInformationGroup(IN SAM_HANDLE GroupHandle,
|
||||||
|
IN GROUP_INFORMATION_CLASS GroupInformationClass,
|
||||||
|
OUT PVOID *Buffer)
|
||||||
|
{
|
||||||
|
NTSTATUS Status;
|
||||||
|
|
||||||
|
TRACE("SamQueryInformationGroup(%p %lu %p)\n",
|
||||||
|
GroupHandle, GroupInformationClass, Buffer);
|
||||||
|
|
||||||
|
RpcTryExcept
|
||||||
|
{
|
||||||
|
Status = SamrQueryInformationGroup((SAMPR_HANDLE)GroupHandle,
|
||||||
|
GroupInformationClass,
|
||||||
|
(PSAMPR_GROUP_INFO_BUFFER *)Buffer);
|
||||||
|
}
|
||||||
|
RpcExcept(EXCEPTION_EXECUTE_HANDLER)
|
||||||
|
{
|
||||||
|
Status = I_RpcMapWin32Status(RpcExceptionCode());
|
||||||
|
}
|
||||||
|
RpcEndExcept;
|
||||||
|
|
||||||
|
return Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
NTSTATUS
|
NTSTATUS
|
||||||
NTAPI
|
NTAPI
|
||||||
SamQueryInformationUser(IN SAM_HANDLE UserHandle,
|
SamQueryInformationUser(IN SAM_HANDLE UserHandle,
|
||||||
|
@ -766,6 +793,33 @@ SamSetInformationDomain(IN SAM_HANDLE DomainHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
NTSTATUS
|
||||||
|
NTAPI
|
||||||
|
SamSetInformationGroup(IN SAM_HANDLE GroupHandle,
|
||||||
|
IN GROUP_INFORMATION_CLASS GroupInformationClass,
|
||||||
|
IN PVOID Buffer)
|
||||||
|
{
|
||||||
|
NTSTATUS Status;
|
||||||
|
|
||||||
|
TRACE("SamSetInformationGroup(%p %lu %p)\n",
|
||||||
|
GroupHandle, GroupInformationClass, Buffer);
|
||||||
|
|
||||||
|
RpcTryExcept
|
||||||
|
{
|
||||||
|
Status = SamrSetInformationGroup((SAMPR_HANDLE)GroupHandle,
|
||||||
|
GroupInformationClass,
|
||||||
|
Buffer);
|
||||||
|
}
|
||||||
|
RpcExcept(EXCEPTION_EXECUTE_HANDLER)
|
||||||
|
{
|
||||||
|
Status = I_RpcMapWin32Status(RpcExceptionCode());
|
||||||
|
}
|
||||||
|
RpcEndExcept;
|
||||||
|
|
||||||
|
return Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
NTSTATUS
|
NTSTATUS
|
||||||
NTAPI
|
NTAPI
|
||||||
SamSetInformationUser(IN SAM_HANDLE UserHandle,
|
SamSetInformationUser(IN SAM_HANDLE UserHandle,
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
@ stub SamQueryDisplayInformation
|
@ stub SamQueryDisplayInformation
|
||||||
@ stdcall SamQueryInformationAlias(ptr long ptr)
|
@ stdcall SamQueryInformationAlias(ptr long ptr)
|
||||||
@ stdcall SamQueryInformationDomain(ptr long ptr)
|
@ stdcall SamQueryInformationDomain(ptr long ptr)
|
||||||
@ stub SamQueryInformationGroup
|
@ stdcall SamQueryInformationGroup(ptr long ptr)
|
||||||
@ stdcall SamQueryInformationUser(ptr long ptr)
|
@ stdcall SamQueryInformationUser(ptr long ptr)
|
||||||
@ stub SamQuerySecurityObject
|
@ stub SamQuerySecurityObject
|
||||||
@ stub SamRemoveMemberFromAlias
|
@ stub SamRemoveMemberFromAlias
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
@ stub SamRidToSid
|
@ stub SamRidToSid
|
||||||
@ stdcall SamSetInformationAlias(ptr long ptr)
|
@ stdcall SamSetInformationAlias(ptr long ptr)
|
||||||
@ stdcall SamSetInformationDomain(ptr long ptr)
|
@ stdcall SamSetInformationDomain(ptr long ptr)
|
||||||
@ stub SamSetInformationGroup
|
@ stdcall SamSetInformationGroup(ptr long ptr)
|
||||||
@ stdcall SamSetInformationUser(ptr long ptr)
|
@ stdcall SamSetInformationUser(ptr long ptr)
|
||||||
@ stub SamSetMemberAttributesOfGroup
|
@ stub SamSetMemberAttributesOfGroup
|
||||||
@ stub SamSetSecurityObject
|
@ stub SamSetSecurityObject
|
||||||
|
|
|
@ -2550,6 +2550,217 @@ SamrOpenGroup(IN SAMPR_HANDLE DomainHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static NTSTATUS
|
||||||
|
SampQueryGroupGeneral(PSAM_DB_OBJECT GroupObject,
|
||||||
|
PSAMPR_GROUP_INFO_BUFFER *Buffer)
|
||||||
|
{
|
||||||
|
PSAMPR_GROUP_INFO_BUFFER InfoBuffer = NULL;
|
||||||
|
HANDLE MembersKeyHandle = NULL;
|
||||||
|
SAM_GROUP_FIXED_DATA FixedData;
|
||||||
|
ULONG Length = 0;
|
||||||
|
NTSTATUS Status;
|
||||||
|
|
||||||
|
*Buffer = NULL;
|
||||||
|
|
||||||
|
InfoBuffer = midl_user_allocate(sizeof(SAMPR_GROUP_INFO_BUFFER));
|
||||||
|
if (InfoBuffer == NULL)
|
||||||
|
return STATUS_INSUFFICIENT_RESOURCES;
|
||||||
|
|
||||||
|
Status = SampGetObjectAttributeString(GroupObject,
|
||||||
|
L"Name",
|
||||||
|
&InfoBuffer->General.Name);
|
||||||
|
if (!NT_SUCCESS(Status))
|
||||||
|
{
|
||||||
|
TRACE("Status 0x%08lx\n", Status);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status = SampGetObjectAttributeString(GroupObject,
|
||||||
|
L"Description",
|
||||||
|
&InfoBuffer->General.AdminComment);
|
||||||
|
if (!NT_SUCCESS(Status))
|
||||||
|
{
|
||||||
|
TRACE("Status 0x%08lx\n", Status);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
Length = sizeof(SAM_GROUP_FIXED_DATA);
|
||||||
|
Status = SampGetObjectAttribute(GroupObject,
|
||||||
|
L"F",
|
||||||
|
NULL,
|
||||||
|
(PVOID)&FixedData,
|
||||||
|
&Length);
|
||||||
|
if (!NT_SUCCESS(Status))
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
InfoBuffer->General.Attributes = FixedData.Attributes;
|
||||||
|
|
||||||
|
/* Open the Members subkey */
|
||||||
|
Status = SampRegOpenKey(GroupObject->KeyHandle,
|
||||||
|
L"Members",
|
||||||
|
KEY_READ,
|
||||||
|
&MembersKeyHandle);
|
||||||
|
if (!NT_SUCCESS(Status))
|
||||||
|
{
|
||||||
|
TRACE("Status 0x%08lx\n", Status);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Retrieve the number of members of the alias */
|
||||||
|
Status = SampRegQueryKeyInfo(MembersKeyHandle,
|
||||||
|
NULL,
|
||||||
|
&InfoBuffer->General.MemberCount);
|
||||||
|
if (!NT_SUCCESS(Status))
|
||||||
|
{
|
||||||
|
TRACE("Status 0x%08lx\n", Status);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
*Buffer = InfoBuffer;
|
||||||
|
|
||||||
|
done:
|
||||||
|
if (MembersKeyHandle != NULL)
|
||||||
|
SampRegCloseKey(MembersKeyHandle);
|
||||||
|
|
||||||
|
if (!NT_SUCCESS(Status))
|
||||||
|
{
|
||||||
|
if (InfoBuffer != NULL)
|
||||||
|
{
|
||||||
|
if (InfoBuffer->General.Name.Buffer != NULL)
|
||||||
|
midl_user_free(InfoBuffer->General.Name.Buffer);
|
||||||
|
|
||||||
|
if (InfoBuffer->General.AdminComment.Buffer != NULL)
|
||||||
|
midl_user_free(InfoBuffer->General.AdminComment.Buffer);
|
||||||
|
|
||||||
|
midl_user_free(InfoBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static NTSTATUS
|
||||||
|
SampQueryGroupName(PSAM_DB_OBJECT GroupObject,
|
||||||
|
PSAMPR_GROUP_INFO_BUFFER *Buffer)
|
||||||
|
{
|
||||||
|
PSAMPR_GROUP_INFO_BUFFER InfoBuffer = NULL;
|
||||||
|
NTSTATUS Status;
|
||||||
|
|
||||||
|
*Buffer = NULL;
|
||||||
|
|
||||||
|
InfoBuffer = midl_user_allocate(sizeof(SAMPR_GROUP_INFO_BUFFER));
|
||||||
|
if (InfoBuffer == NULL)
|
||||||
|
return STATUS_INSUFFICIENT_RESOURCES;
|
||||||
|
|
||||||
|
Status = SampGetObjectAttributeString(GroupObject,
|
||||||
|
L"Name",
|
||||||
|
&InfoBuffer->Name.Name);
|
||||||
|
if (!NT_SUCCESS(Status))
|
||||||
|
{
|
||||||
|
TRACE("Status 0x%08lx\n", Status);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
*Buffer = InfoBuffer;
|
||||||
|
|
||||||
|
done:
|
||||||
|
if (!NT_SUCCESS(Status))
|
||||||
|
{
|
||||||
|
if (InfoBuffer != NULL)
|
||||||
|
{
|
||||||
|
if (InfoBuffer->Name.Name.Buffer != NULL)
|
||||||
|
midl_user_free(InfoBuffer->Name.Name.Buffer);
|
||||||
|
|
||||||
|
midl_user_free(InfoBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static NTSTATUS
|
||||||
|
SampQueryGroupAttribute(PSAM_DB_OBJECT GroupObject,
|
||||||
|
PSAMPR_GROUP_INFO_BUFFER *Buffer)
|
||||||
|
{
|
||||||
|
PSAMPR_GROUP_INFO_BUFFER InfoBuffer = NULL;
|
||||||
|
SAM_GROUP_FIXED_DATA FixedData;
|
||||||
|
ULONG Length = 0;
|
||||||
|
NTSTATUS Status;
|
||||||
|
|
||||||
|
*Buffer = NULL;
|
||||||
|
|
||||||
|
InfoBuffer = midl_user_allocate(sizeof(SAMPR_GROUP_INFO_BUFFER));
|
||||||
|
if (InfoBuffer == NULL)
|
||||||
|
return STATUS_INSUFFICIENT_RESOURCES;
|
||||||
|
|
||||||
|
Length = sizeof(SAM_GROUP_FIXED_DATA);
|
||||||
|
Status = SampGetObjectAttribute(GroupObject,
|
||||||
|
L"F",
|
||||||
|
NULL,
|
||||||
|
(PVOID)&FixedData,
|
||||||
|
&Length);
|
||||||
|
if (!NT_SUCCESS(Status))
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
InfoBuffer->Attribute.Attributes = FixedData.Attributes;
|
||||||
|
|
||||||
|
*Buffer = InfoBuffer;
|
||||||
|
|
||||||
|
done:
|
||||||
|
if (!NT_SUCCESS(Status))
|
||||||
|
{
|
||||||
|
if (InfoBuffer != NULL)
|
||||||
|
{
|
||||||
|
midl_user_free(InfoBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static NTSTATUS
|
||||||
|
SampQueryGroupAdminComment(PSAM_DB_OBJECT GroupObject,
|
||||||
|
PSAMPR_GROUP_INFO_BUFFER *Buffer)
|
||||||
|
{
|
||||||
|
PSAMPR_GROUP_INFO_BUFFER InfoBuffer = NULL;
|
||||||
|
NTSTATUS Status;
|
||||||
|
|
||||||
|
*Buffer = NULL;
|
||||||
|
|
||||||
|
InfoBuffer = midl_user_allocate(sizeof(SAMPR_GROUP_INFO_BUFFER));
|
||||||
|
if (InfoBuffer == NULL)
|
||||||
|
return STATUS_INSUFFICIENT_RESOURCES;
|
||||||
|
|
||||||
|
Status = SampGetObjectAttributeString(GroupObject,
|
||||||
|
L"Description",
|
||||||
|
&InfoBuffer->AdminComment.AdminComment);
|
||||||
|
if (!NT_SUCCESS(Status))
|
||||||
|
{
|
||||||
|
TRACE("Status 0x%08lx\n", Status);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
*Buffer = InfoBuffer;
|
||||||
|
|
||||||
|
done:
|
||||||
|
if (!NT_SUCCESS(Status))
|
||||||
|
{
|
||||||
|
if (InfoBuffer != NULL)
|
||||||
|
{
|
||||||
|
if (InfoBuffer->AdminComment.AdminComment.Buffer != NULL)
|
||||||
|
midl_user_free(InfoBuffer->AdminComment.AdminComment.Buffer);
|
||||||
|
|
||||||
|
midl_user_free(InfoBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Function 20 */
|
/* Function 20 */
|
||||||
NTSTATUS
|
NTSTATUS
|
||||||
NTAPI
|
NTAPI
|
||||||
|
@ -2557,10 +2768,81 @@ SamrQueryInformationGroup(IN SAMPR_HANDLE GroupHandle,
|
||||||
IN GROUP_INFORMATION_CLASS GroupInformationClass,
|
IN GROUP_INFORMATION_CLASS GroupInformationClass,
|
||||||
OUT PSAMPR_GROUP_INFO_BUFFER *Buffer)
|
OUT PSAMPR_GROUP_INFO_BUFFER *Buffer)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED;
|
PSAM_DB_OBJECT GroupObject;
|
||||||
return STATUS_NOT_IMPLEMENTED;
|
NTSTATUS Status;
|
||||||
|
|
||||||
|
TRACE("SamrQueryInformationGroup(%p %lu %p)\n",
|
||||||
|
GroupHandle, GroupInformationClass, Buffer);
|
||||||
|
|
||||||
|
/* Validate the group handle */
|
||||||
|
Status = SampValidateDbObject(GroupHandle,
|
||||||
|
SamDbGroupObject,
|
||||||
|
GROUP_READ_INFORMATION,
|
||||||
|
&GroupObject);
|
||||||
|
if (!NT_SUCCESS(Status))
|
||||||
|
return Status;
|
||||||
|
|
||||||
|
switch (GroupInformationClass)
|
||||||
|
{
|
||||||
|
case GroupGeneralInformation:
|
||||||
|
Status = SampQueryGroupGeneral(GroupObject,
|
||||||
|
Buffer);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GroupNameInformation:
|
||||||
|
Status = SampQueryGroupName(GroupObject,
|
||||||
|
Buffer);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GroupAttributeInformation:
|
||||||
|
Status = SampQueryGroupAttribute(GroupObject,
|
||||||
|
Buffer);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GroupAdminCommentInformation:
|
||||||
|
Status = SampQueryGroupAdminComment(GroupObject,
|
||||||
|
Buffer);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Status = STATUS_INVALID_INFO_CLASS;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static NTSTATUS
|
||||||
|
SampSetGroupAttribute(PSAM_DB_OBJECT GroupObject,
|
||||||
|
PSAMPR_GROUP_INFO_BUFFER Buffer)
|
||||||
|
{
|
||||||
|
SAM_GROUP_FIXED_DATA FixedData;
|
||||||
|
ULONG Length = 0;
|
||||||
|
NTSTATUS Status;
|
||||||
|
|
||||||
|
Length = sizeof(SAM_GROUP_FIXED_DATA);
|
||||||
|
Status = SampGetObjectAttribute(GroupObject,
|
||||||
|
L"F",
|
||||||
|
NULL,
|
||||||
|
(PVOID)&FixedData,
|
||||||
|
&Length);
|
||||||
|
if (!NT_SUCCESS(Status))
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
FixedData.Attributes = Buffer->Attribute.Attributes;
|
||||||
|
|
||||||
|
Status = SampSetObjectAttribute(GroupObject,
|
||||||
|
L"F",
|
||||||
|
REG_BINARY,
|
||||||
|
&FixedData,
|
||||||
|
Length);
|
||||||
|
|
||||||
|
done:
|
||||||
|
return Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Function 21 */
|
/* Function 21 */
|
||||||
NTSTATUS
|
NTSTATUS
|
||||||
NTAPI
|
NTAPI
|
||||||
|
@ -2568,10 +2850,52 @@ SamrSetInformationGroup(IN SAMPR_HANDLE GroupHandle,
|
||||||
IN GROUP_INFORMATION_CLASS GroupInformationClass,
|
IN GROUP_INFORMATION_CLASS GroupInformationClass,
|
||||||
IN PSAMPR_GROUP_INFO_BUFFER Buffer)
|
IN PSAMPR_GROUP_INFO_BUFFER Buffer)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED;
|
PSAM_DB_OBJECT GroupObject;
|
||||||
return STATUS_NOT_IMPLEMENTED;
|
NTSTATUS Status;
|
||||||
|
|
||||||
|
TRACE("SamrSetInformationGroup(%p %lu %p)\n",
|
||||||
|
GroupHandle, GroupInformationClass, Buffer);
|
||||||
|
|
||||||
|
/* Validate the group handle */
|
||||||
|
Status = SampValidateDbObject(GroupHandle,
|
||||||
|
SamDbGroupObject,
|
||||||
|
GROUP_WRITE_ACCOUNT,
|
||||||
|
&GroupObject);
|
||||||
|
if (!NT_SUCCESS(Status))
|
||||||
|
return Status;
|
||||||
|
|
||||||
|
switch (GroupInformationClass)
|
||||||
|
{
|
||||||
|
case GroupNameInformation:
|
||||||
|
Status = SampSetObjectAttribute(GroupObject,
|
||||||
|
L"Name",
|
||||||
|
REG_SZ,
|
||||||
|
Buffer->Name.Name.Buffer,
|
||||||
|
Buffer->Name.Name.Length + sizeof(WCHAR));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GroupAttributeInformation:
|
||||||
|
Status = SampSetGroupAttribute(GroupObject,
|
||||||
|
Buffer);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GroupAdminCommentInformation:
|
||||||
|
Status = SampSetObjectAttribute(GroupObject,
|
||||||
|
L"Description",
|
||||||
|
REG_SZ,
|
||||||
|
Buffer->AdminComment.AdminComment.Buffer,
|
||||||
|
Buffer->AdminComment.AdminComment.Length + sizeof(WCHAR));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Status = STATUS_INVALID_INFO_CLASS;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Function 22 */
|
/* Function 22 */
|
||||||
NTSTATUS
|
NTSTATUS
|
||||||
NTAPI
|
NTAPI
|
||||||
|
@ -2623,6 +2947,7 @@ SamrSetMemberAttributesOfGroup(IN SAMPR_HANDLE GroupHandle,
|
||||||
return STATUS_NOT_IMPLEMENTED;
|
return STATUS_NOT_IMPLEMENTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Function 27 */
|
/* Function 27 */
|
||||||
NTSTATUS
|
NTSTATUS
|
||||||
NTAPI
|
NTAPI
|
||||||
|
@ -2875,6 +3200,7 @@ SamrQueryInformationAlias(IN SAMPR_HANDLE AliasHandle,
|
||||||
return Status;
|
return Status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Function 29 */
|
/* Function 29 */
|
||||||
NTSTATUS
|
NTSTATUS
|
||||||
NTAPI
|
NTAPI
|
||||||
|
@ -2922,6 +3248,7 @@ SamrSetInformationAlias(IN SAMPR_HANDLE AliasHandle,
|
||||||
return Status;
|
return Status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Function 30 */
|
/* Function 30 */
|
||||||
NTSTATUS
|
NTSTATUS
|
||||||
NTAPI
|
NTAPI
|
||||||
|
@ -2931,6 +3258,7 @@ SamrDeleteAlias(IN OUT SAMPR_HANDLE *AliasHandle)
|
||||||
return STATUS_NOT_IMPLEMENTED;
|
return STATUS_NOT_IMPLEMENTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Function 31 */
|
/* Function 31 */
|
||||||
NTSTATUS
|
NTSTATUS
|
||||||
NTAPI
|
NTAPI
|
||||||
|
@ -3018,6 +3346,7 @@ done:
|
||||||
return Status;
|
return Status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Function 32 */
|
/* Function 32 */
|
||||||
NTSTATUS
|
NTSTATUS
|
||||||
NTAPI
|
NTAPI
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue