* syscalls.cc (seteuid): Set default dacl in process token.
Replace in-line code by call to verify_token(). (setegid): Reverse change from 2002-01-21. Add call to RevertToSelf and set primary group in impersonation token. * security.cc (create_token): Store pgrpsid in token security descriptor, except if it already appears in my_grps. Use sec_acl() in place of get_dacl(). (verify_token): Create from code in seteuid(), with tighter checks. (get_dacl) Deleted. (get_group_sidlist): Add argument to indicate if pgrpsid is already in the groups. * security.h: Define verify_token(). * autoload.cc: Load GetKernelObjectSecurity().
This commit is contained in:
parent
a68fa57101
commit
ebbdc7034b
|
@ -1,3 +1,19 @@
|
||||||
|
2002-05-13 Pierre Humblet <pierre.humblet@ieee.org>
|
||||||
|
|
||||||
|
* syscalls.cc (seteuid): Set default dacl in process token.
|
||||||
|
Replace in-line code by call to verify_token().
|
||||||
|
(setegid): Reverse change from 2002-01-21. Add call to
|
||||||
|
RevertToSelf and set primary group in impersonation token.
|
||||||
|
* security.cc (create_token): Store pgrpsid in token security
|
||||||
|
descriptor, except if it already appears in my_grps.
|
||||||
|
Use sec_acl() in place of get_dacl().
|
||||||
|
(verify_token): Create from code in seteuid(), with tighter checks.
|
||||||
|
(get_dacl) Deleted.
|
||||||
|
(get_group_sidlist): Add argument to indicate if pgrpsid is already
|
||||||
|
in the groups.
|
||||||
|
* security.h: Define verify_token().
|
||||||
|
* autoload.cc: Load GetKernelObjectSecurity().
|
||||||
|
|
||||||
2002-05-13 Mark Bradshaw <bradshaw@staff.crosswalk.com>
|
2002-05-13 Mark Bradshaw <bradshaw@staff.crosswalk.com>
|
||||||
|
|
||||||
* cygwin.din: Add strlcat and strlcpy.
|
* cygwin.din: Add strlcat and strlcpy.
|
||||||
|
|
|
@ -316,6 +316,7 @@ LoadDLLfuncEx (DuplicateTokenEx, 24, advapi32, 1)
|
||||||
LoadDLLfunc (EqualSid, 8, advapi32)
|
LoadDLLfunc (EqualSid, 8, advapi32)
|
||||||
LoadDLLfunc (GetAce, 12, advapi32)
|
LoadDLLfunc (GetAce, 12, advapi32)
|
||||||
LoadDLLfunc (GetFileSecurityA, 20, advapi32)
|
LoadDLLfunc (GetFileSecurityA, 20, advapi32)
|
||||||
|
LoadDLLfunc (GetKernelObjectSecurity, 20, advapi32)
|
||||||
LoadDLLfunc (GetLengthSid, 4, advapi32)
|
LoadDLLfunc (GetLengthSid, 4, advapi32)
|
||||||
LoadDLLfunc (GetSecurityDescriptorDacl, 16, advapi32)
|
LoadDLLfunc (GetSecurityDescriptorDacl, 16, advapi32)
|
||||||
LoadDLLfunc (GetSecurityDescriptorGroup, 12, advapi32)
|
LoadDLLfunc (GetSecurityDescriptorGroup, 12, advapi32)
|
||||||
|
|
|
@ -476,7 +476,8 @@ get_supplementary_group_sidlist (const char *username, cygsidlist &grp_list)
|
||||||
static BOOL
|
static BOOL
|
||||||
get_group_sidlist (const char *logonserver, cygsidlist &grp_list,
|
get_group_sidlist (const char *logonserver, cygsidlist &grp_list,
|
||||||
cygsid &usersid, cygsid &pgrpsid,
|
cygsid &usersid, cygsid &pgrpsid,
|
||||||
PTOKEN_GROUPS my_grps, LUID auth_luid, int &auth_pos)
|
PTOKEN_GROUPS my_grps, LUID auth_luid, int &auth_pos,
|
||||||
|
BOOL * special_pgrp)
|
||||||
{
|
{
|
||||||
WCHAR wserver[INTERNET_MAX_HOST_NAME_LENGTH + 1];
|
WCHAR wserver[INTERNET_MAX_HOST_NAME_LENGTH + 1];
|
||||||
char user[INTERNET_MAX_HOST_NAME_LENGTH + 1];
|
char user[INTERNET_MAX_HOST_NAME_LENGTH + 1];
|
||||||
|
@ -533,19 +534,25 @@ get_group_sidlist (const char *logonserver, cygsidlist &grp_list,
|
||||||
auth_pos = grp_list.count - 1;
|
auth_pos = grp_list.count - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* special_pgrp true if pgrpsid is not null and not in normal groups */
|
||||||
if (!pgrpsid)
|
if (!pgrpsid)
|
||||||
get_user_primary_group (wserver, user, usersid, pgrpsid);
|
{
|
||||||
|
* special_pgrp = FALSE;
|
||||||
|
get_user_primary_group (wserver, user, usersid, pgrpsid);
|
||||||
|
}
|
||||||
|
else * special_pgrp = TRUE;
|
||||||
if (!get_user_groups (wserver, grp_list, user) ||
|
if (!get_user_groups (wserver, grp_list, user) ||
|
||||||
!get_user_local_groups (wserver, logonserver, grp_list, usersid))
|
!get_user_local_groups (wserver, logonserver, grp_list, usersid))
|
||||||
return FALSE;
|
return FALSE;
|
||||||
if (!grp_list.contains (pgrpsid))
|
|
||||||
grp_list += pgrpsid;
|
|
||||||
if (get_supplementary_group_sidlist (user, sup_list))
|
if (get_supplementary_group_sidlist (user, sup_list))
|
||||||
{
|
{
|
||||||
for (int i = 0; i < sup_list.count; ++i)
|
for (int i = 0; i < sup_list.count; ++i)
|
||||||
if (!grp_list.contains (sup_list.sids[i]))
|
if (!grp_list.contains (sup_list.sids[i]))
|
||||||
grp_list += sup_list.sids[i];
|
grp_list += sup_list.sids[i];
|
||||||
}
|
}
|
||||||
|
if (!grp_list.contains (pgrpsid))
|
||||||
|
grp_list += pgrpsid;
|
||||||
|
else * special_pgrp = FALSE;
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -663,38 +670,56 @@ get_priv_list (LSA_HANDLE lsa, cygsid &usersid, cygsidlist &grp_list)
|
||||||
return privs;
|
return privs;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define token_acl_size (sizeof (ACL) + \
|
BOOL
|
||||||
2 * (sizeof (ACCESS_ALLOWED_ACE) + MAX_SID_LEN))
|
verify_token (HANDLE token, cygsid &usersid, cygsid &pgrpsid, BOOL * pintern)
|
||||||
|
|
||||||
static BOOL
|
|
||||||
get_dacl (PACL acl, cygsid usersid, cygsidlist &grp_list)
|
|
||||||
{
|
{
|
||||||
if (!InitializeAcl(acl, token_acl_size, ACL_REVISION))
|
DWORD size;
|
||||||
|
BOOL intern = FALSE;
|
||||||
|
|
||||||
|
if (pintern)
|
||||||
{
|
{
|
||||||
__seterrno ();
|
TOKEN_SOURCE ts;
|
||||||
return FALSE;
|
if (!GetTokenInformation (cygheap->user.token, TokenSource,
|
||||||
|
&ts, sizeof ts, &size))
|
||||||
|
debug_printf ("GetTokenInformation(): %E");
|
||||||
|
else *pintern = intern = !memcmp (ts.SourceName, "Cygwin.1", 8);
|
||||||
}
|
}
|
||||||
if (grp_list.contains (well_known_admins_sid))
|
/* Verify usersid */
|
||||||
|
cygsid tok_usersid = NO_SID;
|
||||||
|
if (!GetTokenInformation (token, TokenUser,
|
||||||
|
&tok_usersid, sizeof tok_usersid, &size))
|
||||||
|
debug_printf ("GetTokenInformation(): %E");
|
||||||
|
if (usersid != tok_usersid) return FALSE;
|
||||||
|
|
||||||
|
/* In an internal token, if the sd group is not well_known_null_sid,
|
||||||
|
it must match pgrpsid */
|
||||||
|
if (intern)
|
||||||
{
|
{
|
||||||
if (!AddAccessAllowedAce(acl, ACL_REVISION, GENERIC_ALL,
|
char sd_buf[MAX_SID_LEN + sizeof (SECURITY_DESCRIPTOR)];
|
||||||
well_known_admins_sid))
|
PSID gsid = NO_SID;
|
||||||
{
|
if (!GetKernelObjectSecurity(token, GROUP_SECURITY_INFORMATION,
|
||||||
__seterrno ();
|
(PSECURITY_DESCRIPTOR) sd_buf,
|
||||||
return FALSE;
|
sizeof sd_buf, &size))
|
||||||
}
|
debug_printf ("GetKernelObjectSecurity(): %E");
|
||||||
|
else if (!GetSecurityDescriptorGroup((PSECURITY_DESCRIPTOR) sd_buf,
|
||||||
|
&gsid, (BOOL *) &size))
|
||||||
|
debug_printf ("GetSecurityDescriptorGroup(): %E");
|
||||||
|
if (well_known_null_sid != gsid) return pgrpsid == gsid;
|
||||||
}
|
}
|
||||||
else if (!AddAccessAllowedAce(acl, ACL_REVISION, GENERIC_ALL, usersid))
|
/* See if the pgrpsid is in the token groups */
|
||||||
{
|
PTOKEN_GROUPS my_grps = NULL;
|
||||||
__seterrno ();
|
BOOL ret = FALSE;
|
||||||
return FALSE;
|
|
||||||
}
|
if (!GetTokenInformation (token, TokenGroups, NULL, 0, &size) &&
|
||||||
if (!AddAccessAllowedAce(acl, ACL_REVISION, GENERIC_ALL,
|
GetLastError () != ERROR_INSUFFICIENT_BUFFER)
|
||||||
well_known_system_sid))
|
debug_printf ("GetTokenInformation(token, TokenGroups): %E\n");
|
||||||
{
|
else if (!(my_grps = (PTOKEN_GROUPS) malloc (size)))
|
||||||
__seterrno ();
|
debug_printf ("malloc (my_grps) failed.");
|
||||||
return FALSE;
|
else if (!GetTokenInformation (token, TokenGroups, my_grps, size, &size))
|
||||||
}
|
debug_printf ("GetTokenInformation(my_token, TokenGroups): %E\n");
|
||||||
return TRUE;
|
else ret = sid_in_token_groups (my_grps, pgrpsid);
|
||||||
|
if (my_grps) free (my_grps);
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
HANDLE
|
HANDLE
|
||||||
|
@ -711,6 +736,8 @@ create_token (cygsid &usersid, cygsid &pgrpsid)
|
||||||
{ sizeof sqos, SecurityImpersonation, SECURITY_STATIC_TRACKING, FALSE };
|
{ sizeof sqos, SecurityImpersonation, SECURITY_STATIC_TRACKING, FALSE };
|
||||||
OBJECT_ATTRIBUTES oa =
|
OBJECT_ATTRIBUTES oa =
|
||||||
{ sizeof oa, 0, 0, 0, 0, &sqos };
|
{ sizeof oa, 0, 0, 0, 0, &sqos };
|
||||||
|
PSECURITY_ATTRIBUTES psa;
|
||||||
|
BOOL special_pgrp;
|
||||||
char sa_buf[1024];
|
char sa_buf[1024];
|
||||||
LUID auth_luid = SYSTEM_LUID;
|
LUID auth_luid = SYSTEM_LUID;
|
||||||
LARGE_INTEGER exp = { QuadPart:0x7fffffffffffffffLL };
|
LARGE_INTEGER exp = { QuadPart:0x7fffffffffffffffLL };
|
||||||
|
@ -720,7 +747,7 @@ create_token (cygsid &usersid, cygsid &pgrpsid)
|
||||||
PTOKEN_PRIVILEGES privs = NULL;
|
PTOKEN_PRIVILEGES privs = NULL;
|
||||||
TOKEN_OWNER owner;
|
TOKEN_OWNER owner;
|
||||||
TOKEN_PRIMARY_GROUP pgrp;
|
TOKEN_PRIMARY_GROUP pgrp;
|
||||||
char acl_buf[token_acl_size];
|
char acl_buf[MAX_DACL_LEN(5)];
|
||||||
TOKEN_DEFAULT_DACL dacl;
|
TOKEN_DEFAULT_DACL dacl;
|
||||||
TOKEN_SOURCE source;
|
TOKEN_SOURCE source;
|
||||||
TOKEN_STATISTICS stats;
|
TOKEN_STATISTICS stats;
|
||||||
|
@ -786,7 +813,7 @@ create_token (cygsid &usersid, cygsid &pgrpsid)
|
||||||
/* Create list of groups, the user is member in. */
|
/* Create list of groups, the user is member in. */
|
||||||
int auth_pos;
|
int auth_pos;
|
||||||
if (!get_group_sidlist (logonserver, grpsids, usersid, pgrpsid,
|
if (!get_group_sidlist (logonserver, grpsids, usersid, pgrpsid,
|
||||||
my_grps, auth_luid, auth_pos))
|
my_grps, auth_luid, auth_pos, &special_pgrp))
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
/* Primary group. */
|
/* Primary group. */
|
||||||
|
@ -811,7 +838,8 @@ create_token (cygsid &usersid, cygsid &pgrpsid)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
/* Create default dacl. */
|
/* Create default dacl. */
|
||||||
if (!get_dacl ((PACL) acl_buf, usersid, grpsids))
|
if (!sec_acl((PACL) acl_buf, FALSE,
|
||||||
|
grpsids.contains (well_known_admins_sid)?well_known_admins_sid:usersid))
|
||||||
goto out;
|
goto out;
|
||||||
dacl.DefaultDacl = (PACL) acl_buf;
|
dacl.DefaultDacl = (PACL) acl_buf;
|
||||||
|
|
||||||
|
@ -826,11 +854,22 @@ create_token (cygsid &usersid, cygsid &pgrpsid)
|
||||||
__seterrno ();
|
__seterrno ();
|
||||||
debug_printf ("Loading NtCreateToken failed.");
|
debug_printf ("Loading NtCreateToken failed.");
|
||||||
}
|
}
|
||||||
|
else
|
||||||
/* Convert to primary token. */
|
{
|
||||||
if (!DuplicateTokenEx (token, MAXIMUM_ALLOWED, sec_user (sa_buf, usersid),
|
/* Set security descriptor and primary group */
|
||||||
SecurityImpersonation, TokenPrimary, &primary_token))
|
psa = sec_user (sa_buf, usersid);
|
||||||
__seterrno ();
|
if (!SetSecurityDescriptorGroup (
|
||||||
|
(PSECURITY_DESCRIPTOR) psa->lpSecurityDescriptor,
|
||||||
|
special_pgrp?pgrpsid:well_known_null_sid, FALSE))
|
||||||
|
debug_printf ("SetSecurityDescriptorGroup %E");
|
||||||
|
/* Convert to primary token. */
|
||||||
|
if (!DuplicateTokenEx (token, MAXIMUM_ALLOWED, psa,
|
||||||
|
SecurityImpersonation, TokenPrimary, &primary_token))
|
||||||
|
{
|
||||||
|
__seterrno ();
|
||||||
|
debug_printf ("DuplicateTokenEx %E");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
out:
|
out:
|
||||||
if (old_priv_state >= 0)
|
if (old_priv_state >= 0)
|
||||||
|
|
|
@ -181,6 +181,8 @@ void set_security_attribute (int attribute, PSECURITY_ATTRIBUTES psa,
|
||||||
HANDLE subauth (struct passwd *pw);
|
HANDLE subauth (struct passwd *pw);
|
||||||
/* Try creating a token directly. */
|
/* Try creating a token directly. */
|
||||||
HANDLE create_token (cygsid &usersid, cygsid &pgrpsid);
|
HANDLE create_token (cygsid &usersid, cygsid &pgrpsid);
|
||||||
|
/* Verify an existing token */
|
||||||
|
BOOL verify_token (HANDLE token, cygsid &usersid, cygsid &pgrpsid, BOOL * pintern = NULL);
|
||||||
|
|
||||||
/* Extract U-domain\user field from passwd entry. */
|
/* Extract U-domain\user field from passwd entry. */
|
||||||
void extract_nt_dom_user (const struct passwd *pw, char *domain, char *user);
|
void extract_nt_dom_user (const struct passwd *pw, char *domain, char *user);
|
||||||
|
|
|
@ -2007,7 +2007,7 @@ seteuid (__uid16_t uid)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
cygsid usersid, pgrpsid, tok_pgrpsid;
|
cygsid usersid, pgrpsid, origsid;
|
||||||
HANDLE sav_token = INVALID_HANDLE_VALUE;
|
HANDLE sav_token = INVALID_HANDLE_VALUE;
|
||||||
BOOL sav_impersonation;
|
BOOL sav_impersonation;
|
||||||
BOOL current_token_is_internal_token = FALSE;
|
BOOL current_token_is_internal_token = FALSE;
|
||||||
|
@ -2024,31 +2024,8 @@ seteuid (__uid16_t uid)
|
||||||
- if reasonable - new pgrp == pgrp of impersonation token. */
|
- if reasonable - new pgrp == pgrp of impersonation token. */
|
||||||
if (allow_ntsec && cygheap->user.token != INVALID_HANDLE_VALUE)
|
if (allow_ntsec && cygheap->user.token != INVALID_HANDLE_VALUE)
|
||||||
{
|
{
|
||||||
if (!GetTokenInformation (cygheap->user.token, TokenUser,
|
if (!verify_token(cygheap->user.token, usersid, pgrpsid,
|
||||||
&tok_usersid, sizeof tok_usersid, &siz))
|
& current_token_is_internal_token))
|
||||||
{
|
|
||||||
debug_printf ("GetTokenInformation(): %E");
|
|
||||||
tok_usersid = NO_SID;
|
|
||||||
}
|
|
||||||
if (!GetTokenInformation (cygheap->user.token, TokenPrimaryGroup,
|
|
||||||
&tok_pgrpsid, sizeof tok_pgrpsid, &siz))
|
|
||||||
{
|
|
||||||
debug_printf ("GetTokenInformation(): %E");
|
|
||||||
tok_pgrpsid = NO_SID;
|
|
||||||
}
|
|
||||||
/* Check if the current user token was internally created. */
|
|
||||||
TOKEN_SOURCE ts;
|
|
||||||
if (!GetTokenInformation (cygheap->user.token, TokenSource,
|
|
||||||
&ts, sizeof ts, &siz))
|
|
||||||
debug_printf ("GetTokenInformation(): %E");
|
|
||||||
else if (!memcmp (ts.SourceName, "Cygwin.1", 8))
|
|
||||||
current_token_is_internal_token = TRUE;
|
|
||||||
if ((usersid && tok_usersid && usersid != tok_usersid) ||
|
|
||||||
/* Check for pgrp only if current token is an internal
|
|
||||||
token. Otherwise the external provided token is
|
|
||||||
very likely overwritten here. */
|
|
||||||
(current_token_is_internal_token &&
|
|
||||||
pgrpsid && tok_pgrpsid && pgrpsid != tok_pgrpsid))
|
|
||||||
{
|
{
|
||||||
/* If not, RevertToSelf and close old token. */
|
/* If not, RevertToSelf and close old token. */
|
||||||
debug_printf ("tsid != usersid");
|
debug_printf ("tsid != usersid");
|
||||||
|
@ -2113,9 +2090,28 @@ seteuid (__uid16_t uid)
|
||||||
&pgrpsid, sizeof pgrpsid))
|
&pgrpsid, sizeof pgrpsid))
|
||||||
debug_printf ("SetTokenInformation(user.token, "
|
debug_printf ("SetTokenInformation(user.token, "
|
||||||
"TokenPrimaryGroup): %E");
|
"TokenPrimaryGroup): %E");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
/* Set process def dacl to allow access to impersonated token */
|
||||||
|
char dacl_buf[MAX_DACL_LEN(5)];
|
||||||
|
origsid = cygheap->user.orig_sid ();
|
||||||
|
if (usersid && origsid &&
|
||||||
|
sec_acl((PACL) dacl_buf, FALSE, origsid, usersid))
|
||||||
|
{
|
||||||
|
HANDLE ptok = INVALID_HANDLE_VALUE;
|
||||||
|
TOKEN_DEFAULT_DACL tdacl;
|
||||||
|
tdacl.DefaultDacl = (PACL) dacl_buf;
|
||||||
|
if (!OpenProcessToken (GetCurrentProcess (), TOKEN_ADJUST_DEFAULT,
|
||||||
|
&ptok))
|
||||||
|
debug_printf ("OpenProcessToken(): %E");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!SetTokenInformation (ptok, TokenDefaultDacl,
|
||||||
|
&tdacl, sizeof dacl_buf))
|
||||||
|
debug_printf ("SetTokenInformation"
|
||||||
|
"(TokenDefaultDacl): %E");
|
||||||
|
}
|
||||||
|
if (ptok != INVALID_HANDLE_VALUE) CloseHandle (ptok);
|
||||||
|
}
|
||||||
/* Now try to impersonate. */
|
/* Now try to impersonate. */
|
||||||
if (!LookupAccountSid (NULL, usersid, username, &ulen,
|
if (!LookupAccountSid (NULL, usersid, username, &ulen,
|
||||||
domain, &dlen, &use))
|
domain, &dlen, &use))
|
||||||
|
@ -2180,7 +2176,6 @@ setegid (__gid16_t gid)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
myself->gid = gid;
|
myself->gid = gid;
|
||||||
#if 0 // Setting the primary group in token here isn't foolproof enough.
|
|
||||||
if (allow_ntsec)
|
if (allow_ntsec)
|
||||||
{
|
{
|
||||||
cygsid gsid;
|
cygsid gsid;
|
||||||
|
@ -2188,6 +2183,17 @@ setegid (__gid16_t gid)
|
||||||
|
|
||||||
if (gsid.getfromgr (gr))
|
if (gsid.getfromgr (gr))
|
||||||
{
|
{
|
||||||
|
/* Remove impersonation */
|
||||||
|
if (cygheap->user.token != INVALID_HANDLE_VALUE
|
||||||
|
&& cygheap->user.impersonated)
|
||||||
|
{
|
||||||
|
if (!SetTokenInformation (cygheap->user.token,
|
||||||
|
TokenPrimaryGroup,
|
||||||
|
&gsid, sizeof gsid))
|
||||||
|
debug_printf ("SetTokenInformation(primary, "
|
||||||
|
"TokenPrimaryGroup): %E");
|
||||||
|
RevertToSelf ();
|
||||||
|
}
|
||||||
if (!OpenProcessToken (GetCurrentProcess (),
|
if (!OpenProcessToken (GetCurrentProcess (),
|
||||||
TOKEN_ADJUST_DEFAULT,
|
TOKEN_ADJUST_DEFAULT,
|
||||||
&ptok))
|
&ptok))
|
||||||
|
@ -2196,13 +2202,15 @@ setegid (__gid16_t gid)
|
||||||
{
|
{
|
||||||
if (!SetTokenInformation (ptok, TokenPrimaryGroup,
|
if (!SetTokenInformation (ptok, TokenPrimaryGroup,
|
||||||
&gsid, sizeof gsid))
|
&gsid, sizeof gsid))
|
||||||
debug_printf ("SetTokenInformation(myself, "
|
debug_printf ("SetTokenInformation(process, "
|
||||||
"TokenPrimaryGroup): %E");
|
"TokenPrimaryGroup): %E");
|
||||||
CloseHandle (ptok);
|
CloseHandle (ptok);
|
||||||
}
|
}
|
||||||
|
if (cygheap->user.token != INVALID_HANDLE_VALUE
|
||||||
|
&& cygheap->user.impersonated)
|
||||||
|
ImpersonateLoggedOnUser (cygheap->user.token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
Loading…
Reference in New Issue