1630 lines
44 KiB
C++
1630 lines
44 KiB
C++
/* security.cc: NT security functions
|
|
|
|
Copyright 1997, 1998, 1999, 2000, 2001 Red Hat, Inc.
|
|
|
|
Originaly written by Gunther Ebert, gunther.ebert@ixos-leipzig.de
|
|
Completely rewritten by Corinna Vinschen <corinna@vinschen.de>
|
|
|
|
This file is part of Cygwin.
|
|
|
|
This software is a copyrighted work licensed under the terms of the
|
|
Cygwin license. Please consult the file "CYGWIN_LICENSE" for
|
|
details. */
|
|
|
|
#include "winsup.h"
|
|
#include <grp.h>
|
|
#include <pwd.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/acl.h>
|
|
#include <ctype.h>
|
|
#include <winnls.h>
|
|
#include <wingdi.h>
|
|
#include <winuser.h>
|
|
#include <wininet.h>
|
|
#include <ntsecapi.h>
|
|
#include <subauth.h>
|
|
#include "cygerrno.h"
|
|
#include "perprocess.h"
|
|
#include "security.h"
|
|
#include "fhandler.h"
|
|
#include "path.h"
|
|
#include "dtable.h"
|
|
#include "sync.h"
|
|
#include "sigproc.h"
|
|
#include "pinfo.h"
|
|
#include "cygheap.h"
|
|
#include <ntdef.h>
|
|
#include "ntdll.h"
|
|
#include "lm.h"
|
|
|
|
|
|
extern BOOL allow_ntea;
|
|
BOOL allow_ntsec;
|
|
/* allow_smbntsec is handled exclusively in path.cc (path_conv::check).
|
|
It's defined here because of it's strong relationship to allow_ntsec.
|
|
The default is TRUE to reflect the old behaviour. */
|
|
BOOL allow_smbntsec = TRUE;
|
|
|
|
extern "C"
|
|
void
|
|
cygwin_set_impersonation_token (const HANDLE hToken)
|
|
{
|
|
debug_printf ("set_impersonation_token (%d)", hToken);
|
|
if (cygheap->user.token != hToken)
|
|
{
|
|
cygheap->user.token = hToken;
|
|
cygheap->user.impersonated = FALSE;
|
|
}
|
|
}
|
|
|
|
void
|
|
extract_nt_dom_user (const struct passwd *pw, char *domain, char *user)
|
|
{
|
|
cygsid psid;
|
|
DWORD ulen = UNLEN + 1;
|
|
DWORD dlen = INTERNET_MAX_HOST_NAME_LENGTH + 1;
|
|
SID_NAME_USE use;
|
|
char buf[INTERNET_MAX_HOST_NAME_LENGTH + UNLEN + 2];
|
|
char *c;
|
|
|
|
strcpy (domain, "");
|
|
strcpy (buf, pw->pw_name);
|
|
debug_printf ("pw_gecos = %x (%s)", pw->pw_gecos, pw->pw_gecos);
|
|
|
|
if (psid.getfrompw (pw) &&
|
|
LookupAccountSid (NULL, psid, user, &ulen, domain, &dlen, &use))
|
|
return;
|
|
|
|
if (pw->pw_gecos)
|
|
{
|
|
if ((c = strstr (pw->pw_gecos, "U-")) != NULL &&
|
|
(c == pw->pw_gecos || c[-1] == ','))
|
|
{
|
|
buf[0] = '\0';
|
|
strncat (buf, c + 2, INTERNET_MAX_HOST_NAME_LENGTH + UNLEN + 1);
|
|
if ((c = strchr (buf, ',')) != NULL)
|
|
*c = '\0';
|
|
}
|
|
}
|
|
if ((c = strchr (buf, '\\')) != NULL)
|
|
{
|
|
*c++ = '\0';
|
|
strcpy (domain, buf);
|
|
strcpy (user, c);
|
|
}
|
|
else
|
|
{
|
|
strcpy (domain, "");
|
|
strcpy (user, buf);
|
|
}
|
|
}
|
|
|
|
extern "C"
|
|
HANDLE
|
|
cygwin_logon_user (const struct passwd *pw, const char *password)
|
|
{
|
|
if (!wincap.has_security ())
|
|
{
|
|
set_errno (ENOSYS);
|
|
return INVALID_HANDLE_VALUE;
|
|
}
|
|
if (!pw)
|
|
{
|
|
set_errno (EINVAL);
|
|
return INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
char nt_domain[INTERNET_MAX_HOST_NAME_LENGTH + 1];
|
|
char nt_user[UNLEN + 1];
|
|
HANDLE hToken;
|
|
|
|
extract_nt_dom_user (pw, nt_domain, nt_user);
|
|
debug_printf ("LogonUserA (%s, %s, %s, ...)", nt_user, nt_domain, password);
|
|
if (!LogonUserA (nt_user, *nt_domain ? nt_domain : NULL, (char *) password,
|
|
LOGON32_LOGON_INTERACTIVE,
|
|
LOGON32_PROVIDER_DEFAULT,
|
|
&hToken)
|
|
|| !SetHandleInformation (hToken,
|
|
HANDLE_FLAG_INHERIT,
|
|
HANDLE_FLAG_INHERIT))
|
|
{
|
|
__seterrno ();
|
|
return INVALID_HANDLE_VALUE;
|
|
}
|
|
debug_printf ("%d = logon_user(%s,...)", hToken, pw->pw_name);
|
|
return hToken;
|
|
}
|
|
|
|
static void
|
|
str2lsa (LSA_STRING &tgt, const char *srcstr)
|
|
{
|
|
tgt.Length = strlen (srcstr);
|
|
tgt.MaximumLength = tgt.Length + 1;
|
|
tgt.Buffer = (PCHAR) srcstr;
|
|
}
|
|
|
|
static void
|
|
str2buf2lsa (LSA_STRING &tgt, char *buf, const char *srcstr)
|
|
{
|
|
tgt.Length = strlen (srcstr);
|
|
tgt.MaximumLength = tgt.Length + 1;
|
|
tgt.Buffer = (PCHAR) buf;
|
|
memcpy(buf, srcstr, tgt.MaximumLength);
|
|
}
|
|
|
|
void
|
|
str2buf2uni (UNICODE_STRING &tgt, WCHAR *buf, const char *srcstr)
|
|
{
|
|
tgt.Length = strlen (srcstr) * sizeof (WCHAR);
|
|
tgt.MaximumLength = tgt.Length + sizeof(WCHAR);
|
|
tgt.Buffer = (PWCHAR) buf;
|
|
sys_mbstowcs (buf, srcstr, tgt.MaximumLength);
|
|
}
|
|
|
|
static void
|
|
lsa2wchar (WCHAR *tgt, LSA_UNICODE_STRING &src, int size)
|
|
{
|
|
size = (size - 1) * sizeof (WCHAR);
|
|
if (src.Length < size)
|
|
size = src.Length;
|
|
memcpy (tgt, src.Buffer, size);
|
|
size >>= 1;
|
|
tgt[size] = 0;
|
|
}
|
|
|
|
static LSA_HANDLE
|
|
open_local_policy ()
|
|
{
|
|
LSA_OBJECT_ATTRIBUTES oa = { 0, 0, 0, 0, 0, 0 };
|
|
LSA_HANDLE lsa = NULL;
|
|
|
|
NTSTATUS ret = LsaOpenPolicy(NULL, &oa, POLICY_EXECUTE, &lsa);
|
|
if (ret != STATUS_SUCCESS)
|
|
set_errno (LsaNtStatusToWinError (ret));
|
|
return lsa;
|
|
}
|
|
|
|
static void
|
|
close_local_policy (LSA_HANDLE &lsa)
|
|
{
|
|
if (lsa != INVALID_HANDLE_VALUE)
|
|
LsaClose (lsa);
|
|
lsa = INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
static BOOL
|
|
get_lsa_srv_inf (LSA_HANDLE lsa, char *logonserver, char *domain)
|
|
{
|
|
NET_API_STATUS ret;
|
|
LPSERVER_INFO_101 buf;
|
|
DWORD cnt, tot;
|
|
char name[INTERNET_MAX_HOST_NAME_LENGTH + 1];
|
|
WCHAR account[INTERNET_MAX_HOST_NAME_LENGTH + 1];
|
|
WCHAR primary[INTERNET_MAX_HOST_NAME_LENGTH + 1];
|
|
PPOLICY_ACCOUNT_DOMAIN_INFO adi;
|
|
PPOLICY_PRIMARY_DOMAIN_INFO pdi;
|
|
|
|
if ((ret = LsaQueryInformationPolicy (lsa, PolicyAccountDomainInformation,
|
|
(PVOID *) &adi)) != STATUS_SUCCESS)
|
|
{
|
|
set_errno (LsaNtStatusToWinError(ret));
|
|
return FALSE;
|
|
}
|
|
lsa2wchar (account, adi->DomainName, INTERNET_MAX_HOST_NAME_LENGTH + 1);
|
|
LsaFreeMemory (adi);
|
|
if ((ret = LsaQueryInformationPolicy (lsa, PolicyPrimaryDomainInformation,
|
|
(PVOID *) &pdi)) != STATUS_SUCCESS)
|
|
{
|
|
set_errno (LsaNtStatusToWinError(ret));
|
|
return FALSE;
|
|
}
|
|
lsa2wchar (primary, pdi->Name, INTERNET_MAX_HOST_NAME_LENGTH + 1);
|
|
LsaFreeMemory (pdi);
|
|
if ((ret = NetServerEnum (NULL, 101, (LPBYTE *) &buf, MAX_PREFERRED_LENGTH,
|
|
&cnt, &tot, SV_TYPE_DOMAIN_CTRL, primary, NULL))
|
|
== STATUS_SUCCESS && cnt > 0)
|
|
{
|
|
sys_wcstombs (name, buf[0].sv101_name, INTERNET_MAX_HOST_NAME_LENGTH + 1);
|
|
if (domain)
|
|
sys_wcstombs (domain, primary, INTERNET_MAX_HOST_NAME_LENGTH + 1);
|
|
}
|
|
else
|
|
{
|
|
sys_wcstombs (name, account, INTERNET_MAX_HOST_NAME_LENGTH + 1);
|
|
if (domain)
|
|
sys_wcstombs (domain, account, INTERNET_MAX_HOST_NAME_LENGTH + 1);
|
|
}
|
|
if (ret == STATUS_SUCCESS)
|
|
NetApiBufferFree (buf);
|
|
strcpy (logonserver, "\\\\");
|
|
strcat (logonserver, name);
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL
|
|
get_logon_server (LSA_HANDLE lsa, char *logonserver)
|
|
{
|
|
return get_lsa_srv_inf (lsa, logonserver, NULL);
|
|
}
|
|
|
|
BOOL
|
|
get_logon_server_and_user_domain (char *logonserver, char *userdomain)
|
|
{
|
|
BOOL ret = FALSE;
|
|
LSA_HANDLE lsa = open_local_policy ();
|
|
if (lsa)
|
|
{
|
|
ret = get_lsa_srv_inf (lsa, logonserver, userdomain);
|
|
close_local_policy (lsa);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static BOOL
|
|
get_user_groups (WCHAR *wlogonserver, cygsidlist &grp_list, char *user)
|
|
{
|
|
WCHAR wuser[UNLEN + 1];
|
|
sys_mbstowcs (wuser, user, UNLEN + 1);
|
|
LPGROUP_USERS_INFO_0 buf;
|
|
DWORD cnt, tot;
|
|
NET_API_STATUS ret;
|
|
|
|
ret = NetUserGetGroups (wlogonserver, wuser, 0, (LPBYTE *) &buf,
|
|
MAX_PREFERRED_LENGTH, &cnt, &tot);
|
|
if (ret == ERROR_BAD_NETPATH || ret == RPC_S_SERVER_UNAVAILABLE)
|
|
ret = NetUserGetGroups (NULL, wuser, 0, (LPBYTE *) &buf,
|
|
MAX_PREFERRED_LENGTH, &cnt, &tot);
|
|
if (ret)
|
|
{
|
|
debug_printf ("%d = NetUserGetGroups ()", ret);
|
|
set_errno (ret);
|
|
/* It's no error when the user name can't be found. */
|
|
return ret == NERR_UserNotFound;
|
|
}
|
|
|
|
for (DWORD i = 0; i < cnt; ++i)
|
|
{
|
|
cygsid gsid;
|
|
char group[UNLEN + 1];
|
|
char domain[INTERNET_MAX_HOST_NAME_LENGTH + 1];
|
|
DWORD glen = UNLEN + 1;
|
|
DWORD dlen = INTERNET_MAX_HOST_NAME_LENGTH + 1;
|
|
SID_NAME_USE use = SidTypeInvalid;
|
|
|
|
sys_wcstombs (group, buf[i].grui0_name, UNLEN + 1);
|
|
if (!LookupAccountName (NULL, group, gsid, &glen, domain, &dlen, &use))
|
|
debug_printf ("LookupAccountName(%s): %lu\n", group, GetLastError ());
|
|
if (!legal_sid_type (use))
|
|
{
|
|
strcat (strcpy (group, domain), "\\");
|
|
sys_wcstombs (group + strlen (group), buf[i].grui0_name,
|
|
UNLEN + 1 - strlen (group));
|
|
glen = UNLEN + 1;
|
|
dlen = INTERNET_MAX_HOST_NAME_LENGTH + 1;
|
|
if (!LookupAccountName(NULL, group, gsid, &glen, domain, &dlen, &use))
|
|
debug_printf ("LookupAccountName(%s): %lu\n", group,GetLastError());
|
|
}
|
|
if (legal_sid_type (use))
|
|
grp_list += gsid;
|
|
}
|
|
|
|
NetApiBufferFree (buf);
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL
|
|
is_group_member (WCHAR *wlogonserver, WCHAR *wgroup,
|
|
cygsid &usersid, cygsidlist &grp_list)
|
|
{
|
|
LPLOCALGROUP_MEMBERS_INFO_0 buf;
|
|
DWORD cnt, tot;
|
|
NET_API_STATUS ret;
|
|
BOOL retval = FALSE;
|
|
|
|
ret = NetLocalGroupGetMembers (NULL, wgroup, 0, (LPBYTE *) &buf,
|
|
MAX_PREFERRED_LENGTH, &cnt, &tot, NULL);
|
|
if (ret)
|
|
return FALSE;
|
|
|
|
for (DWORD bidx = 0; !retval && bidx < cnt; ++bidx)
|
|
if (EqualSid (usersid, buf[bidx].lgrmi0_sid))
|
|
retval = TRUE;
|
|
else
|
|
for (int glidx = 0; !retval && glidx < grp_list.count; ++glidx)
|
|
if (EqualSid (grp_list.sids[glidx], buf[bidx].lgrmi0_sid))
|
|
retval = TRUE;
|
|
|
|
NetApiBufferFree (buf);
|
|
return retval;
|
|
}
|
|
|
|
static BOOL
|
|
get_user_local_groups (WCHAR *wlogonserver, const char *logonserver,
|
|
cygsidlist &grp_list, cygsid &usersid)
|
|
{
|
|
LPLOCALGROUP_INFO_0 buf;
|
|
DWORD cnt, tot;
|
|
NET_API_STATUS ret;
|
|
|
|
ret = NetLocalGroupEnum (NULL, 0, (LPBYTE *) &buf,
|
|
MAX_PREFERRED_LENGTH, &cnt, &tot, NULL);
|
|
if (ret)
|
|
{
|
|
debug_printf ("%d = NetLocalGroupEnum ()", ret);
|
|
set_errno (ret);
|
|
return FALSE;
|
|
}
|
|
|
|
for (DWORD i = 0; i < cnt; ++i)
|
|
if (is_group_member (wlogonserver, buf[i].lgrpi0_name, usersid, grp_list))
|
|
{
|
|
cygsid gsid;
|
|
char group[UNLEN + 1];
|
|
char domain[INTERNET_MAX_HOST_NAME_LENGTH + 1];
|
|
DWORD glen = UNLEN + 1;
|
|
DWORD dlen = INTERNET_MAX_HOST_NAME_LENGTH + 1;
|
|
SID_NAME_USE use = SidTypeInvalid;
|
|
|
|
sys_wcstombs (group, buf[i].lgrpi0_name, UNLEN + 1);
|
|
if (!LookupAccountName (NULL, group, gsid, &glen, domain, &dlen, &use))
|
|
{
|
|
glen = UNLEN + 1;
|
|
dlen = INTERNET_MAX_HOST_NAME_LENGTH + 1;
|
|
if (!LookupAccountName (logonserver + 2, group,
|
|
gsid, &glen, domain, &dlen, &use))
|
|
debug_printf ("LookupAccountName(%s): %lu\n", group,
|
|
GetLastError ());
|
|
}
|
|
else if (!legal_sid_type (use))
|
|
{
|
|
strcat (strcpy (group, domain), "\\");
|
|
sys_wcstombs (group + strlen (group), buf[i].lgrpi0_name,
|
|
UNLEN + 1 - strlen (group));
|
|
glen = UNLEN + 1;
|
|
dlen = INTERNET_MAX_HOST_NAME_LENGTH + 1;
|
|
if (!LookupAccountName (NULL, group, gsid, &glen,
|
|
domain, &dlen, &use))
|
|
debug_printf ("LookupAccountName(%s): %lu\n", group,
|
|
GetLastError ());
|
|
}
|
|
if (legal_sid_type (use))
|
|
grp_list += gsid;
|
|
}
|
|
|
|
NetApiBufferFree (buf);
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL
|
|
sid_in_token_groups (PTOKEN_GROUPS grps, cygsid &sid)
|
|
{
|
|
if (!grps)
|
|
return FALSE;
|
|
for (DWORD i = 0; i < grps->GroupCount; ++i)
|
|
if (sid == grps->Groups[i].Sid)
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
static BOOL
|
|
get_user_primary_group (WCHAR *wlogonserver, const char *user,
|
|
cygsid &usersid, cygsid &pgrpsid)
|
|
{
|
|
LPUSER_INFO_3 buf;
|
|
WCHAR wuser[UNLEN + 1];
|
|
NET_API_STATUS ret;
|
|
BOOL retval = FALSE;
|
|
UCHAR count = 0;
|
|
|
|
if (usersid == well_known_system_sid)
|
|
{
|
|
pgrpsid = well_known_system_sid;
|
|
return TRUE;
|
|
}
|
|
|
|
sys_mbstowcs (wuser, user, UNLEN + 1);
|
|
ret = NetUserGetInfo (wlogonserver, wuser, 3, (LPBYTE *) &buf);
|
|
if (ret == ERROR_BAD_NETPATH || ret == RPC_S_SERVER_UNAVAILABLE)
|
|
ret = NetUserGetInfo (NULL, wuser, 3, (LPBYTE *) &buf);
|
|
if (ret)
|
|
{
|
|
debug_printf ("%d = NetUserGetInfo ()", ret);
|
|
set_errno (ret);
|
|
return FALSE;
|
|
}
|
|
|
|
pgrpsid = usersid;
|
|
if (IsValidSid (pgrpsid) && (count = *GetSidSubAuthorityCount (pgrpsid)) > 1)
|
|
{
|
|
*GetSidSubAuthority (pgrpsid, count - 1) = buf->usri3_primary_group_id;
|
|
retval = TRUE;
|
|
}
|
|
NetApiBufferFree (buf);
|
|
return retval;
|
|
}
|
|
|
|
static int
|
|
get_supplementary_group_sidlist (const char *username, cygsidlist &grp_list)
|
|
{
|
|
struct group *gr;
|
|
int cnt = 0;
|
|
|
|
for (int gidx = 0; (gr = internal_getgrent (gidx)); ++gidx)
|
|
{
|
|
if (gr->gr_mem)
|
|
for (int gi = 0; gr->gr_mem[gi]; ++gi)
|
|
if (strcasematch (username, gr->gr_mem[gi]))
|
|
{
|
|
if (gr->gr_passwd && *gr->gr_passwd)
|
|
{
|
|
cygsid sid (gr->gr_passwd);
|
|
if ((PSID)sid && grp_list.add (sid))
|
|
++cnt;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return cnt;
|
|
}
|
|
|
|
static BOOL
|
|
get_group_sidlist (const char *logonserver, cygsidlist &grp_list,
|
|
cygsid &usersid, cygsid &pgrpsid,
|
|
PTOKEN_GROUPS my_grps, LUID auth_luid, int &auth_pos)
|
|
{
|
|
WCHAR wserver[INTERNET_MAX_HOST_NAME_LENGTH + 1];
|
|
char user[INTERNET_MAX_HOST_NAME_LENGTH + 1];
|
|
char domain[INTERNET_MAX_HOST_NAME_LENGTH + 1];
|
|
DWORD ulen = INTERNET_MAX_HOST_NAME_LENGTH + 1;
|
|
DWORD dlen = INTERNET_MAX_HOST_NAME_LENGTH + 1;
|
|
SID_NAME_USE use;
|
|
cygsidlist sup_list;
|
|
|
|
auth_pos = -1;
|
|
sys_mbstowcs (wserver, logonserver, INTERNET_MAX_HOST_NAME_LENGTH + 1);
|
|
if (!LookupAccountSid (NULL, usersid, user, &ulen, domain, &dlen, &use))
|
|
{
|
|
debug_printf ("LookupAccountSid () %E");
|
|
__seterrno ();
|
|
return FALSE;
|
|
}
|
|
grp_list += well_known_world_sid;
|
|
if (usersid == well_known_system_sid)
|
|
{
|
|
grp_list += well_known_system_sid;
|
|
grp_list += well_known_admins_sid;
|
|
}
|
|
else
|
|
{
|
|
if (my_grps)
|
|
{
|
|
if (sid_in_token_groups (my_grps, well_known_local_sid))
|
|
grp_list += well_known_local_sid;
|
|
if (sid_in_token_groups (my_grps, well_known_dialup_sid))
|
|
grp_list += well_known_dialup_sid;
|
|
if (sid_in_token_groups (my_grps, well_known_network_sid))
|
|
grp_list += well_known_network_sid;
|
|
if (sid_in_token_groups (my_grps, well_known_batch_sid))
|
|
grp_list += well_known_batch_sid;
|
|
if (sid_in_token_groups (my_grps, well_known_interactive_sid))
|
|
grp_list += well_known_interactive_sid;
|
|
if (sid_in_token_groups (my_grps, well_known_service_sid))
|
|
grp_list += well_known_service_sid;
|
|
grp_list += well_known_authenticated_users_sid;
|
|
}
|
|
else
|
|
{
|
|
grp_list += well_known_local_sid;
|
|
grp_list += well_known_interactive_sid;
|
|
grp_list += well_known_authenticated_users_sid;
|
|
}
|
|
if (auth_luid.QuadPart != 999) /* != SYSTEM_LUID */
|
|
{
|
|
char buf[64];
|
|
__small_sprintf (buf, "S-1-5-5-%u-%u", auth_luid.HighPart,
|
|
auth_luid.LowPart);
|
|
grp_list += buf;
|
|
auth_pos = grp_list.count - 1;
|
|
}
|
|
}
|
|
if (!pgrpsid)
|
|
get_user_primary_group (wserver, user, usersid, pgrpsid);
|
|
if (!get_user_groups (wserver, grp_list, user) ||
|
|
!get_user_local_groups (wserver, logonserver, grp_list, usersid))
|
|
return FALSE;
|
|
if (!grp_list.contains (pgrpsid))
|
|
grp_list += pgrpsid;
|
|
if (get_supplementary_group_sidlist (user, sup_list))
|
|
{
|
|
for (int i = 0; i < sup_list.count; ++i)
|
|
if (!grp_list.contains (sup_list.sids[i]))
|
|
grp_list += sup_list.sids[i];
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static const char *sys_privs[] = {
|
|
SE_TCB_NAME,
|
|
SE_ASSIGNPRIMARYTOKEN_NAME,
|
|
SE_CREATE_TOKEN_NAME,
|
|
SE_CHANGE_NOTIFY_NAME,
|
|
SE_SECURITY_NAME,
|
|
SE_BACKUP_NAME,
|
|
SE_RESTORE_NAME,
|
|
SE_SYSTEMTIME_NAME,
|
|
SE_SHUTDOWN_NAME,
|
|
SE_REMOTE_SHUTDOWN_NAME,
|
|
SE_TAKE_OWNERSHIP_NAME,
|
|
SE_DEBUG_NAME,
|
|
SE_SYSTEM_ENVIRONMENT_NAME,
|
|
SE_SYSTEM_PROFILE_NAME,
|
|
SE_PROF_SINGLE_PROCESS_NAME,
|
|
SE_INC_BASE_PRIORITY_NAME,
|
|
SE_LOAD_DRIVER_NAME,
|
|
SE_CREATE_PAGEFILE_NAME,
|
|
SE_INCREASE_QUOTA_NAME
|
|
};
|
|
|
|
#define SYSTEM_PERMISSION_COUNT (sizeof sys_privs / sizeof (const char *))
|
|
|
|
PTOKEN_PRIVILEGES
|
|
get_system_priv_list (cygsidlist &grp_list)
|
|
{
|
|
LUID priv;
|
|
PTOKEN_PRIVILEGES privs = (PTOKEN_PRIVILEGES) malloc (sizeof (ULONG) +
|
|
20 * sizeof (LUID_AND_ATTRIBUTES));
|
|
if (!privs)
|
|
{
|
|
debug_printf ("malloc (system_privs) failed.");
|
|
return NULL;
|
|
}
|
|
privs->PrivilegeCount = 0;
|
|
|
|
for (DWORD i = 0; i < SYSTEM_PERMISSION_COUNT; ++i)
|
|
if (LookupPrivilegeValue (NULL, sys_privs[i], &priv))
|
|
{
|
|
privs->Privileges[privs->PrivilegeCount].Luid = priv;
|
|
privs->Privileges[privs->PrivilegeCount].Attributes =
|
|
SE_PRIVILEGE_ENABLED | SE_PRIVILEGE_ENABLED_BY_DEFAULT;
|
|
++privs->PrivilegeCount;
|
|
}
|
|
return privs;
|
|
}
|
|
|
|
PTOKEN_PRIVILEGES
|
|
get_priv_list (LSA_HANDLE lsa, cygsid &usersid, cygsidlist &grp_list)
|
|
{
|
|
PLSA_UNICODE_STRING privstrs;
|
|
ULONG cnt;
|
|
PTOKEN_PRIVILEGES privs = NULL;
|
|
NTSTATUS ret;
|
|
char buf[INTERNET_MAX_HOST_NAME_LENGTH + 1];
|
|
|
|
if (usersid == well_known_system_sid)
|
|
return get_system_priv_list (grp_list);
|
|
|
|
for (int grp = -1; grp < grp_list.count; ++grp)
|
|
{
|
|
if (grp == -1)
|
|
{
|
|
if ((ret = LsaEnumerateAccountRights (lsa, usersid, &privstrs, &cnt))
|
|
!= STATUS_SUCCESS)
|
|
continue;
|
|
}
|
|
else if ((ret = LsaEnumerateAccountRights (lsa, grp_list.sids[grp],
|
|
&privstrs, &cnt))
|
|
!= STATUS_SUCCESS)
|
|
continue;
|
|
for (ULONG i = 0; i < cnt; ++i)
|
|
{
|
|
LUID priv;
|
|
PTOKEN_PRIVILEGES tmp;
|
|
DWORD tmp_count;
|
|
|
|
sys_wcstombs (buf, privstrs[i].Buffer,
|
|
INTERNET_MAX_HOST_NAME_LENGTH + 1);
|
|
if (!LookupPrivilegeValue (NULL, buf, &priv))
|
|
continue;
|
|
|
|
for (DWORD p = 0; privs && p < privs->PrivilegeCount; ++p)
|
|
if (!memcmp (&priv, &privs->Privileges[p].Luid, sizeof (LUID)))
|
|
goto next_account_right;
|
|
|
|
tmp_count = privs ? privs->PrivilegeCount : 0;
|
|
tmp = (PTOKEN_PRIVILEGES)
|
|
realloc (privs, sizeof (ULONG) +
|
|
(tmp_count + 1) * sizeof (LUID_AND_ATTRIBUTES));
|
|
if (!tmp)
|
|
{
|
|
if (privs)
|
|
free (privs);
|
|
LsaFreeMemory (privstrs);
|
|
debug_printf ("realloc (privs) failed.");
|
|
return NULL;
|
|
}
|
|
tmp->PrivilegeCount = tmp_count;
|
|
privs = tmp;
|
|
privs->Privileges[privs->PrivilegeCount].Luid = priv;
|
|
privs->Privileges[privs->PrivilegeCount].Attributes =
|
|
SE_PRIVILEGE_ENABLED | SE_PRIVILEGE_ENABLED_BY_DEFAULT;
|
|
++privs->PrivilegeCount;
|
|
|
|
next_account_right:
|
|
;
|
|
}
|
|
LsaFreeMemory (privstrs);
|
|
}
|
|
return privs;
|
|
}
|
|
|
|
#define token_acl_size (sizeof (ACL) + \
|
|
2 * (sizeof (ACCESS_ALLOWED_ACE) + MAX_SID_LEN))
|
|
|
|
static BOOL
|
|
get_dacl (PACL acl, cygsid usersid, cygsidlist &grp_list)
|
|
{
|
|
if (!InitializeAcl(acl, token_acl_size, ACL_REVISION))
|
|
{
|
|
__seterrno ();
|
|
return FALSE;
|
|
}
|
|
if (grp_list.contains (well_known_admins_sid))
|
|
{
|
|
if (!AddAccessAllowedAce(acl, ACL_REVISION, GENERIC_ALL,
|
|
well_known_admins_sid))
|
|
{
|
|
__seterrno ();
|
|
return FALSE;
|
|
}
|
|
}
|
|
else if (!AddAccessAllowedAce(acl, ACL_REVISION, GENERIC_ALL, usersid))
|
|
{
|
|
__seterrno ();
|
|
return FALSE;
|
|
}
|
|
if (!AddAccessAllowedAce(acl, ACL_REVISION, GENERIC_ALL,
|
|
well_known_system_sid))
|
|
{
|
|
__seterrno ();
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
HANDLE
|
|
create_token (cygsid &usersid, cygsid &pgrpsid)
|
|
{
|
|
NTSTATUS ret;
|
|
LSA_HANDLE lsa = NULL;
|
|
char logonserver[INTERNET_MAX_HOST_NAME_LENGTH + 1];
|
|
int old_priv_state;
|
|
|
|
cygsidlist grpsids;
|
|
|
|
SECURITY_QUALITY_OF_SERVICE sqos =
|
|
{ sizeof sqos, SecurityImpersonation, SECURITY_STATIC_TRACKING, FALSE };
|
|
OBJECT_ATTRIBUTES oa =
|
|
{ sizeof oa, 0, 0, 0, 0, &sqos };
|
|
SECURITY_ATTRIBUTES sa = { sizeof sa, NULL, TRUE };
|
|
LUID auth_luid = SYSTEM_LUID;
|
|
LARGE_INTEGER exp = { QuadPart:0x7fffffffffffffffLL };
|
|
|
|
TOKEN_USER user;
|
|
PTOKEN_GROUPS grps = NULL;
|
|
PTOKEN_PRIVILEGES privs = NULL;
|
|
TOKEN_OWNER owner;
|
|
TOKEN_PRIMARY_GROUP pgrp;
|
|
char acl_buf[token_acl_size];
|
|
TOKEN_DEFAULT_DACL dacl;
|
|
TOKEN_SOURCE source;
|
|
TOKEN_STATISTICS stats;
|
|
memcpy(source.SourceName, "Cygwin.1", 8);
|
|
source.SourceIdentifier.HighPart = 0;
|
|
source.SourceIdentifier.LowPart = 0x0101;
|
|
|
|
HANDLE token;
|
|
HANDLE primary_token = INVALID_HANDLE_VALUE;
|
|
|
|
HANDLE my_token = INVALID_HANDLE_VALUE;
|
|
PTOKEN_GROUPS my_grps = NULL;
|
|
DWORD size;
|
|
|
|
/* SE_CREATE_TOKEN_NAME privilege needed to call NtCreateToken. */
|
|
if ((old_priv_state = set_process_privilege (SE_CREATE_TOKEN_NAME)) < 0)
|
|
goto out;
|
|
|
|
/* Open policy object. */
|
|
if (!(lsa = open_local_policy ()))
|
|
goto out;
|
|
|
|
/* Get logon server. */
|
|
if (!get_logon_server (lsa, logonserver))
|
|
goto out;
|
|
|
|
/* User, owner, primary group. */
|
|
user.User.Sid = usersid;
|
|
user.User.Attributes = 0;
|
|
owner.Owner = usersid;
|
|
|
|
/* Retrieve authentication id and group list from own process. */
|
|
if (!OpenProcessToken (GetCurrentProcess (), TOKEN_QUERY, &my_token))
|
|
debug_printf ("OpenProcessToken(my_token): %E\n");
|
|
else
|
|
{
|
|
/* Switching user context to SYSTEM doesn't inherit the authentication
|
|
id of the user account running current process. */
|
|
if (usersid != well_known_system_sid)
|
|
if (!GetTokenInformation (my_token, TokenStatistics,
|
|
&stats, sizeof stats, &size))
|
|
debug_printf ("GetTokenInformation(my_token, TokenStatistics): %E\n");
|
|
else
|
|
auth_luid = stats.AuthenticationId;
|
|
|
|
/* Retrieving current processes group list to be able to inherit
|
|
some important well known group sids. */
|
|
if (!GetTokenInformation (my_token, TokenGroups, NULL, 0, &size) &&
|
|
GetLastError () != ERROR_INSUFFICIENT_BUFFER)
|
|
debug_printf ("GetTokenInformation(my_token, TokenGroups): %E\n");
|
|
else if (!(my_grps = (PTOKEN_GROUPS) malloc (size)))
|
|
debug_printf ("malloc (my_grps) failed.");
|
|
else if (!GetTokenInformation (my_token, TokenGroups, my_grps,
|
|
size, &size))
|
|
{
|
|
debug_printf ("GetTokenInformation(my_token, TokenGroups): %E\n");
|
|
free (my_grps);
|
|
my_grps = NULL;
|
|
}
|
|
CloseHandle (my_token);
|
|
}
|
|
|
|
/* Create list of groups, the user is member in. */
|
|
int auth_pos;
|
|
if (!get_group_sidlist (logonserver, grpsids, usersid, pgrpsid,
|
|
my_grps, auth_luid, auth_pos))
|
|
goto out;
|
|
|
|
/* Primary group. */
|
|
pgrp.PrimaryGroup = pgrpsid;
|
|
|
|
/* Create a TOKEN_GROUPS list from the above retrieved list of sids. */
|
|
char grps_buf[sizeof (ULONG) + grpsids.count * sizeof (SID_AND_ATTRIBUTES)];
|
|
grps = (PTOKEN_GROUPS) grps_buf;
|
|
grps->GroupCount = grpsids.count;
|
|
for (DWORD i = 0; i < grps->GroupCount; ++i)
|
|
{
|
|
grps->Groups[i].Sid = grpsids.sids[i];
|
|
grps->Groups[i].Attributes = SE_GROUP_MANDATORY |
|
|
SE_GROUP_ENABLED_BY_DEFAULT |
|
|
SE_GROUP_ENABLED;
|
|
if (auth_pos >= 0 && i == (DWORD) auth_pos)
|
|
grps->Groups[i].Attributes |= SE_GROUP_LOGON_ID;
|
|
}
|
|
|
|
/* Retrieve list of privileges of that user. */
|
|
if (!(privs = get_priv_list (lsa, usersid, grpsids)))
|
|
goto out;
|
|
|
|
/* Create default dacl. */
|
|
if (!get_dacl ((PACL) acl_buf, usersid, grpsids))
|
|
goto out;
|
|
dacl.DefaultDacl = (PACL) acl_buf;
|
|
|
|
/* Let's be heroic... */
|
|
ret = NtCreateToken (&token, TOKEN_ALL_ACCESS, &oa, TokenImpersonation,
|
|
&auth_luid, &exp, &user, grps, privs, &owner, &pgrp,
|
|
&dacl, &source);
|
|
if (ret)
|
|
set_errno (RtlNtStatusToDosError (ret));
|
|
else if (GetLastError () == ERROR_PROC_NOT_FOUND)
|
|
{
|
|
__seterrno ();
|
|
debug_printf ("Loading NtCreateToken failed.");
|
|
}
|
|
|
|
/* Convert to primary token. */
|
|
if (!DuplicateTokenEx (token, TOKEN_ALL_ACCESS, &sa,
|
|
SecurityImpersonation, TokenPrimary,
|
|
&primary_token))
|
|
__seterrno ();
|
|
|
|
out:
|
|
if (old_priv_state >= 0)
|
|
set_process_privilege (SE_CREATE_TOKEN_NAME, old_priv_state);
|
|
if (token != INVALID_HANDLE_VALUE)
|
|
CloseHandle (token);
|
|
if (privs)
|
|
free (privs);
|
|
if (my_grps)
|
|
free (my_grps);
|
|
close_local_policy (lsa);
|
|
|
|
debug_printf ("%d = create_token ()", primary_token);
|
|
return primary_token;
|
|
}
|
|
|
|
int subauth_id = 255;
|
|
|
|
HANDLE
|
|
subauth (struct passwd *pw)
|
|
{
|
|
LSA_STRING name;
|
|
HANDLE lsa_hdl;
|
|
LSA_OPERATIONAL_MODE sec_mode;
|
|
NTSTATUS ret, ret2;
|
|
ULONG package_id, size;
|
|
struct {
|
|
LSA_STRING str;
|
|
CHAR buf[16];
|
|
} origin;
|
|
struct {
|
|
MSV1_0_LM20_LOGON auth;
|
|
WCHAR dombuf[INTERNET_MAX_HOST_NAME_LENGTH + 1];
|
|
WCHAR usrbuf[UNLEN + 1];
|
|
WCHAR wkstbuf[1];
|
|
CHAR authinf1[1];
|
|
CHAR authinf2[1];
|
|
} subbuf;
|
|
TOKEN_SOURCE ts;
|
|
PMSV1_0_LM20_LOGON_PROFILE profile;
|
|
LUID luid;
|
|
QUOTA_LIMITS quota;
|
|
char nt_domain[INTERNET_MAX_HOST_NAME_LENGTH + 1];
|
|
char nt_user[UNLEN + 1];
|
|
SECURITY_ATTRIBUTES sa = { sizeof sa, NULL, TRUE };
|
|
HANDLE user_token = INVALID_HANDLE_VALUE;
|
|
HANDLE primary_token = INVALID_HANDLE_VALUE;
|
|
int old_tcb_state;
|
|
|
|
if ((old_tcb_state = set_process_privilege(SE_TCB_NAME)) < 0)
|
|
return INVALID_HANDLE_VALUE;
|
|
|
|
/* Register as logon process. */
|
|
str2lsa (name, "Cygwin");
|
|
SetLastError (0);
|
|
ret = LsaRegisterLogonProcess(&name, &lsa_hdl, &sec_mode);
|
|
if (ret != STATUS_SUCCESS)
|
|
{
|
|
debug_printf ("LsaRegisterLogonProcess: %d", ret);
|
|
set_errno (LsaNtStatusToWinError(ret));
|
|
goto out;
|
|
}
|
|
else if (GetLastError () == ERROR_PROC_NOT_FOUND)
|
|
{
|
|
debug_printf ("Couldn't load Secur32.dll");
|
|
goto out;
|
|
}
|
|
/* Get handle to MSV1_0 package. */
|
|
str2lsa (name, MSV1_0_PACKAGE_NAME);
|
|
ret = LsaLookupAuthenticationPackage(lsa_hdl, &name, &package_id);
|
|
if (ret != STATUS_SUCCESS)
|
|
{
|
|
debug_printf ("LsaLookupAuthenticationPackage: %d", ret);
|
|
set_errno (LsaNtStatusToWinError(ret));
|
|
LsaDeregisterLogonProcess(lsa_hdl);
|
|
goto out;
|
|
}
|
|
/* Create origin. */
|
|
str2buf2lsa (origin.str, origin.buf, "Cygwin");
|
|
/* Create token source. */
|
|
memcpy(ts.SourceName, "Cygwin.1", 8);
|
|
ts.SourceIdentifier.HighPart = 0;
|
|
ts.SourceIdentifier.LowPart = 0x0100;
|
|
/* Get user information. */
|
|
extract_nt_dom_user (pw, nt_domain, nt_user);
|
|
/* Fill subauth with values. */
|
|
subbuf.auth.MessageType = MsV1_0NetworkLogon;
|
|
str2buf2uni(subbuf.auth.LogonDomainName, subbuf.dombuf, nt_domain);
|
|
str2buf2uni(subbuf.auth.UserName, subbuf.usrbuf, nt_user);
|
|
str2buf2uni(subbuf.auth.Workstation, subbuf.wkstbuf, "");
|
|
memcpy(subbuf.auth.ChallengeToClient, "12345678", MSV1_0_CHALLENGE_LENGTH);
|
|
str2buf2lsa(subbuf.auth.CaseSensitiveChallengeResponse, subbuf.authinf1, "");
|
|
str2buf2lsa(subbuf.auth.CaseInsensitiveChallengeResponse, subbuf.authinf2,"");
|
|
subbuf.auth.ParameterControl = 0 | (subauth_id << 24);
|
|
/* Try to logon... */
|
|
ret = LsaLogonUser(lsa_hdl, (PLSA_STRING) &origin, Network,
|
|
package_id, &subbuf, sizeof subbuf,
|
|
NULL, &ts, (PVOID *)&profile, &size,
|
|
&luid, &user_token, "a, &ret2);
|
|
if (ret != STATUS_SUCCESS)
|
|
{
|
|
debug_printf ("LsaLogonUser: %d", ret);
|
|
set_errno (LsaNtStatusToWinError(ret));
|
|
LsaDeregisterLogonProcess(lsa_hdl);
|
|
goto out;
|
|
}
|
|
LsaFreeReturnBuffer(profile);
|
|
/* Convert to primary token. */
|
|
if (!DuplicateTokenEx (user_token, TOKEN_ALL_ACCESS, &sa,
|
|
SecurityImpersonation, TokenPrimary,
|
|
&primary_token))
|
|
__seterrno ();
|
|
|
|
out:
|
|
set_process_privilege(SE_TCB_NAME, old_tcb_state);
|
|
if (user_token != INVALID_HANDLE_VALUE)
|
|
CloseHandle (user_token);
|
|
return primary_token;
|
|
}
|
|
|
|
/* read_sd reads a security descriptor from a file.
|
|
In case of error, -1 is returned and errno is set.
|
|
If sd_buf is too small, 0 is returned and sd_size
|
|
is set to the needed buffer size.
|
|
On success, 1 is returned.
|
|
|
|
GetFileSecurity() is used instead of BackupRead()
|
|
to avoid access denied errors if the caller has
|
|
not the permission to open that file for read.
|
|
|
|
Originally the function should return the size
|
|
of the SD on success. Unfortunately NT returns
|
|
0 in `len' on success, while W2K returns the
|
|
correct size!
|
|
*/
|
|
|
|
LONG
|
|
read_sd(const char *file, PSECURITY_DESCRIPTOR sd_buf, LPDWORD sd_size)
|
|
{
|
|
/* Check parameters */
|
|
if (!sd_size)
|
|
{
|
|
set_errno (EINVAL);
|
|
return -1;
|
|
}
|
|
|
|
debug_printf("file = %s", file);
|
|
|
|
DWORD len = 0;
|
|
const char *pfile = file;
|
|
char fbuf [PATH_MAX];
|
|
if (current_codepage == oem_cp)
|
|
{
|
|
DWORD fname_len = min (sizeof (fbuf) - 1, strlen (file));
|
|
bzero (fbuf, sizeof (fbuf));
|
|
OemToCharBuff(file, fbuf, fname_len);
|
|
pfile = fbuf;
|
|
}
|
|
|
|
if (!GetFileSecurity (pfile,
|
|
OWNER_SECURITY_INFORMATION
|
|
| GROUP_SECURITY_INFORMATION
|
|
| DACL_SECURITY_INFORMATION,
|
|
sd_buf, *sd_size, &len))
|
|
{
|
|
__seterrno ();
|
|
return -1;
|
|
}
|
|
debug_printf("file = %s: len=%d", file, len);
|
|
if (len > *sd_size)
|
|
{
|
|
*sd_size = len;
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
LONG
|
|
write_sd(const char *file, PSECURITY_DESCRIPTOR sd_buf, DWORD sd_size)
|
|
{
|
|
/* Check parameters */
|
|
if (!sd_buf || !sd_size)
|
|
{
|
|
set_errno (EINVAL);
|
|
return -1;
|
|
}
|
|
|
|
/* No need to be thread save. */
|
|
static BOOL first_time = TRUE;
|
|
if (first_time)
|
|
{
|
|
set_process_privilege (SE_RESTORE_NAME);
|
|
first_time = FALSE;
|
|
}
|
|
|
|
HANDLE fh;
|
|
fh = CreateFile (file,
|
|
WRITE_OWNER | WRITE_DAC,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
&sec_none_nih,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS,
|
|
NULL);
|
|
|
|
if (fh == INVALID_HANDLE_VALUE)
|
|
{
|
|
__seterrno ();
|
|
return -1;
|
|
}
|
|
|
|
LPVOID context = NULL;
|
|
DWORD bytes_written = 0;
|
|
WIN32_STREAM_ID header;
|
|
|
|
memset (&header, 0, sizeof (header));
|
|
/* write new security info header */
|
|
header.dwStreamId = BACKUP_SECURITY_DATA;
|
|
header.dwStreamAttributes = STREAM_CONTAINS_SECURITY;
|
|
header.Size.HighPart = 0;
|
|
header.Size.LowPart = sd_size;
|
|
header.dwStreamNameSize = 0;
|
|
if (!BackupWrite (fh, (LPBYTE) &header,
|
|
3 * sizeof (DWORD) + sizeof (LARGE_INTEGER),
|
|
&bytes_written, FALSE, TRUE, &context))
|
|
{
|
|
__seterrno ();
|
|
CloseHandle (fh);
|
|
return -1;
|
|
}
|
|
|
|
/* write new security descriptor */
|
|
if (!BackupWrite (fh, (LPBYTE) sd_buf,
|
|
header.Size.LowPart + header.dwStreamNameSize,
|
|
&bytes_written, FALSE, TRUE, &context))
|
|
{
|
|
/* Samba returns ERROR_NOT_SUPPORTED.
|
|
FAT returns ERROR_INVALID_SECURITY_DESCR.
|
|
This shouldn't return as error, but better be ignored. */
|
|
DWORD ret = GetLastError ();
|
|
if (ret != ERROR_NOT_SUPPORTED && ret != ERROR_INVALID_SECURITY_DESCR)
|
|
{
|
|
__seterrno ();
|
|
BackupWrite (fh, NULL, 0, &bytes_written, TRUE, TRUE, &context);
|
|
CloseHandle (fh);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* terminate the restore process */
|
|
BackupWrite (fh, NULL, 0, &bytes_written, TRUE, TRUE, &context);
|
|
CloseHandle (fh);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
get_nt_attribute (const char *file, int *attribute,
|
|
uid_t *uidret, gid_t *gidret)
|
|
{
|
|
if (!wincap.has_security ())
|
|
return 0;
|
|
|
|
syscall_printf ("file: %s", file);
|
|
|
|
/* Yeah, sounds too much, but I've seen SDs of 2100 bytes!*/
|
|
DWORD sd_size = 4096;
|
|
char sd_buf[4096];
|
|
PSECURITY_DESCRIPTOR psd = (PSECURITY_DESCRIPTOR) sd_buf;
|
|
|
|
int ret;
|
|
if ((ret = read_sd (file, psd, &sd_size)) <= 0)
|
|
{
|
|
debug_printf ("read_sd %E");
|
|
return ret;
|
|
}
|
|
|
|
PSID owner_sid;
|
|
PSID group_sid;
|
|
BOOL dummy;
|
|
|
|
if (!GetSecurityDescriptorOwner (psd, &owner_sid, &dummy))
|
|
debug_printf ("GetSecurityDescriptorOwner %E");
|
|
if (!GetSecurityDescriptorGroup (psd, &group_sid, &dummy))
|
|
debug_printf ("GetSecurityDescriptorGroup %E");
|
|
|
|
PACL acl;
|
|
BOOL acl_exists;
|
|
|
|
if (!GetSecurityDescriptorDacl (psd, &acl_exists, &acl, &dummy))
|
|
{
|
|
__seterrno ();
|
|
debug_printf ("GetSecurityDescriptorDacl %E");
|
|
return -1;
|
|
}
|
|
|
|
uid_t uid = cygsid(owner_sid).get_uid ();
|
|
gid_t gid = cygsid(group_sid).get_gid ();
|
|
if (uidret)
|
|
*uidret = uid;
|
|
if (gidret)
|
|
*gidret = gid;
|
|
|
|
if (!attribute)
|
|
{
|
|
syscall_printf ("file: %s uid %d, gid %d", file, uid, gid);
|
|
return 0;
|
|
}
|
|
|
|
BOOL grp_member = is_grp_member (uid, gid);
|
|
|
|
if (!acl_exists || !acl)
|
|
{
|
|
*attribute |= S_IRWXU | S_IRWXG | S_IRWXO;
|
|
syscall_printf ("file: %s No ACL = %x, uid %d, gid %d",
|
|
file, *attribute, uid, gid);
|
|
return 0;
|
|
}
|
|
|
|
ACCESS_ALLOWED_ACE *ace;
|
|
int allow = 0;
|
|
int deny = 0;
|
|
int *flags, *anti;
|
|
|
|
for (DWORD i = 0; i < acl->AceCount; ++i)
|
|
{
|
|
if (!GetAce (acl, i, (PVOID *) &ace))
|
|
continue;
|
|
if (ace->Header.AceFlags & INHERIT_ONLY_ACE)
|
|
continue;
|
|
switch (ace->Header.AceType)
|
|
{
|
|
case ACCESS_ALLOWED_ACE_TYPE:
|
|
flags = &allow;
|
|
anti = &deny;
|
|
break;
|
|
case ACCESS_DENIED_ACE_TYPE:
|
|
flags = &deny;
|
|
anti = &allow;
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
cygsid ace_sid ((PSID) &ace->SidStart);
|
|
if (owner_sid && ace_sid == owner_sid)
|
|
{
|
|
if (ace->Mask & FILE_READ_DATA)
|
|
*flags |= S_IRUSR;
|
|
if (ace->Mask & FILE_WRITE_DATA)
|
|
*flags |= S_IWUSR;
|
|
if (ace->Mask & FILE_EXECUTE)
|
|
*flags |= S_IXUSR;
|
|
}
|
|
else if (group_sid && ace_sid == group_sid)
|
|
{
|
|
if (ace->Mask & FILE_READ_DATA)
|
|
*flags |= S_IRGRP
|
|
| ((grp_member && !(*anti & S_IRUSR)) ? S_IRUSR : 0);
|
|
if (ace->Mask & FILE_WRITE_DATA)
|
|
*flags |= S_IWGRP
|
|
| ((grp_member && !(*anti & S_IWUSR)) ? S_IWUSR : 0);
|
|
if (ace->Mask & FILE_EXECUTE)
|
|
*flags |= S_IXGRP
|
|
| ((grp_member && !(*anti & S_IXUSR)) ? S_IXUSR : 0);
|
|
}
|
|
else if (ace_sid == well_known_world_sid)
|
|
{
|
|
if (ace->Mask & FILE_READ_DATA)
|
|
*flags |= S_IROTH
|
|
| ((!(*anti & S_IRGRP)) ? S_IRGRP : 0)
|
|
| ((!(*anti & S_IRUSR)) ? S_IRUSR : 0);
|
|
if (ace->Mask & FILE_WRITE_DATA)
|
|
*flags |= S_IWOTH
|
|
| ((!(*anti & S_IWGRP)) ? S_IWGRP : 0)
|
|
| ((!(*anti & S_IWUSR)) ? S_IWUSR : 0);
|
|
if (ace->Mask & FILE_EXECUTE)
|
|
{
|
|
*flags |= S_IXOTH
|
|
| ((!(*anti & S_IXGRP)) ? S_IXGRP : 0)
|
|
| ((!(*anti & S_IXUSR)) ? S_IXUSR : 0);
|
|
}
|
|
if ((*attribute & S_IFDIR) &&
|
|
(ace->Mask & (FILE_WRITE_DATA | FILE_EXECUTE | FILE_DELETE_CHILD))
|
|
== (FILE_WRITE_DATA | FILE_EXECUTE))
|
|
*flags |= S_ISVTX;
|
|
}
|
|
else if (ace_sid == well_known_null_sid)
|
|
{
|
|
/* Read SUID, SGID and VTX bits from NULL ACE. */
|
|
if (ace->Mask & FILE_READ_DATA)
|
|
*flags |= S_ISVTX;
|
|
if (ace->Mask & FILE_WRITE_DATA)
|
|
*flags |= S_ISGID;
|
|
if (ace->Mask & FILE_APPEND_DATA)
|
|
*flags |= S_ISUID;
|
|
}
|
|
}
|
|
*attribute &= ~(S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX | S_ISGID | S_ISUID);
|
|
*attribute |= allow;
|
|
*attribute &= ~deny;
|
|
syscall_printf ("file: %s %x, uid %d, gid %d", file, *attribute, uid, gid);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
get_file_attribute (int use_ntsec, const char *file,
|
|
int *attribute, uid_t *uidret, gid_t *gidret)
|
|
{
|
|
int res;
|
|
|
|
if (use_ntsec && allow_ntsec)
|
|
{
|
|
res = get_nt_attribute (file, attribute, uidret, gidret);
|
|
if (attribute && (*attribute & S_IFLNK) == S_IFLNK)
|
|
*attribute |= S_IRWXU | S_IRWXG | S_IRWXO;
|
|
return res;
|
|
}
|
|
|
|
if (uidret)
|
|
*uidret = getuid ();
|
|
if (gidret)
|
|
*gidret = getgid ();
|
|
|
|
if (!attribute)
|
|
return 0;
|
|
|
|
if (allow_ntea)
|
|
{
|
|
int oatt = *attribute;
|
|
res = NTReadEA (file, ".UNIXATTR", (char *) attribute, sizeof (*attribute));
|
|
*attribute |= oatt;
|
|
}
|
|
else
|
|
res = 0;
|
|
|
|
/* symlinks are everything for everyone!*/
|
|
if ((*attribute & S_IFLNK) == S_IFLNK)
|
|
*attribute |= S_IRWXU | S_IRWXG | S_IRWXO;
|
|
|
|
return res > 0 ? 0 : -1;
|
|
}
|
|
|
|
BOOL
|
|
add_access_allowed_ace (PACL acl, int offset, DWORD attributes,
|
|
PSID sid, size_t &len_add, DWORD inherit)
|
|
{
|
|
if (!AddAccessAllowedAce (acl, ACL_REVISION, attributes, sid))
|
|
{
|
|
__seterrno ();
|
|
return FALSE;
|
|
}
|
|
ACCESS_ALLOWED_ACE *ace;
|
|
if (GetAce(acl, offset, (PVOID *) &ace))
|
|
ace->Header.AceFlags |= inherit;
|
|
len_add += sizeof (ACCESS_DENIED_ACE) - sizeof (DWORD)
|
|
+ GetLengthSid (sid);
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL
|
|
add_access_denied_ace (PACL acl, int offset, DWORD attributes,
|
|
PSID sid, size_t &len_add, DWORD inherit)
|
|
{
|
|
if (!AddAccessDeniedAce (acl, ACL_REVISION, attributes, sid))
|
|
{
|
|
__seterrno ();
|
|
return FALSE;
|
|
}
|
|
ACCESS_DENIED_ACE *ace;
|
|
if (GetAce(acl, offset, (PVOID *) &ace))
|
|
ace->Header.AceFlags |= inherit;
|
|
len_add += sizeof (ACCESS_DENIED_ACE) - sizeof (DWORD)
|
|
+ GetLengthSid (sid);
|
|
return TRUE;
|
|
}
|
|
|
|
PSECURITY_DESCRIPTOR
|
|
alloc_sd (uid_t uid, gid_t gid, const char *logsrv, int attribute,
|
|
PSECURITY_DESCRIPTOR sd_ret, DWORD *sd_size_ret)
|
|
{
|
|
BOOL dummy;
|
|
|
|
if (!wincap.has_security ())
|
|
return NULL;
|
|
|
|
if (!sd_ret || !sd_size_ret)
|
|
{
|
|
set_errno (EINVAL);
|
|
return NULL;
|
|
}
|
|
|
|
/* Get SID and name of new owner. */
|
|
char owner[UNLEN + 1];
|
|
cygsid owner_sid;
|
|
struct passwd *pw = getpwuid (uid);
|
|
strcpy (owner, pw ? pw->pw_name : getlogin ());
|
|
if ((!pw || !owner_sid.getfrompw (pw))
|
|
&& !lookup_name (owner, logsrv, owner_sid))
|
|
return NULL;
|
|
debug_printf ("owner: %s [%d]", owner,
|
|
*GetSidSubAuthority(owner_sid,
|
|
*GetSidSubAuthorityCount(owner_sid) - 1));
|
|
|
|
/* Get SID and name of new group. */
|
|
cygsid group_sid (NO_SID);
|
|
struct group *grp = getgrgid (gid);
|
|
if (grp)
|
|
{
|
|
if ((!grp || !group_sid.getfromgr (grp))
|
|
&& !lookup_name (grp->gr_name, logsrv, group_sid))
|
|
return NULL;
|
|
}
|
|
else
|
|
debug_printf ("no group");
|
|
|
|
/* Initialize local security descriptor. */
|
|
SECURITY_DESCRIPTOR sd;
|
|
PSECURITY_DESCRIPTOR psd = NULL;
|
|
if (!InitializeSecurityDescriptor (&sd, SECURITY_DESCRIPTOR_REVISION))
|
|
{
|
|
__seterrno ();
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* We set the SE_DACL_PROTECTED flag here to prevent the DACL from being
|
|
* modified by inheritable ACEs.
|
|
* This flag as well as the SetSecurityDescriptorControl call are available
|
|
* only since Win2K.
|
|
*/
|
|
if (wincap.has_security_descriptor_control ())
|
|
SetSecurityDescriptorControl (&sd, SE_DACL_PROTECTED, SE_DACL_PROTECTED);
|
|
|
|
/* Create owner for local security descriptor. */
|
|
if (!SetSecurityDescriptorOwner(&sd, owner_sid, FALSE))
|
|
{
|
|
__seterrno ();
|
|
return NULL;
|
|
}
|
|
|
|
/* Create group for local security descriptor. */
|
|
if (group_sid && !SetSecurityDescriptorGroup(&sd, group_sid, FALSE))
|
|
{
|
|
__seterrno ();
|
|
return NULL;
|
|
}
|
|
|
|
/* Initialize local access control list. */
|
|
char acl_buf[3072];
|
|
PACL acl = (PACL) acl_buf;
|
|
if (!InitializeAcl (acl, 3072, ACL_REVISION))
|
|
{
|
|
__seterrno ();
|
|
return NULL;
|
|
}
|
|
|
|
/* From here fill ACL. */
|
|
size_t acl_len = sizeof (ACL);
|
|
int ace_off = 0;
|
|
|
|
/* Construct allow attribute for owner. */
|
|
DWORD owner_allow = (STANDARD_RIGHTS_ALL & ~DELETE)
|
|
| FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA;
|
|
if (attribute & S_IRUSR)
|
|
owner_allow |= FILE_GENERIC_READ;
|
|
if (attribute & S_IWUSR)
|
|
owner_allow |= FILE_GENERIC_WRITE | DELETE;
|
|
if (attribute & S_IXUSR)
|
|
owner_allow |= FILE_GENERIC_EXECUTE;
|
|
if ((attribute & (S_IFDIR | S_IWUSR | S_IXUSR))
|
|
== (S_IFDIR | S_IWUSR | S_IXUSR))
|
|
owner_allow |= FILE_DELETE_CHILD;
|
|
|
|
/* Construct allow attribute for group. */
|
|
DWORD group_allow = STANDARD_RIGHTS_READ
|
|
| FILE_READ_ATTRIBUTES | FILE_READ_EA;
|
|
if (attribute & S_IRGRP)
|
|
group_allow |= FILE_GENERIC_READ;
|
|
if (attribute & S_IWGRP)
|
|
group_allow |= STANDARD_RIGHTS_WRITE | FILE_GENERIC_WRITE;
|
|
if (attribute & S_IXGRP)
|
|
group_allow |= FILE_GENERIC_EXECUTE;
|
|
if ((attribute & (S_IFDIR | S_IWGRP | S_IXGRP))
|
|
== (S_IFDIR | S_IWGRP | S_IXGRP)
|
|
&& !(attribute & S_ISVTX))
|
|
group_allow |= FILE_DELETE_CHILD;
|
|
|
|
/* Construct allow attribute for everyone. */
|
|
DWORD other_allow = STANDARD_RIGHTS_READ
|
|
| FILE_READ_ATTRIBUTES | FILE_READ_EA;
|
|
if (attribute & S_IROTH)
|
|
other_allow |= FILE_GENERIC_READ;
|
|
if (attribute & S_IWOTH)
|
|
other_allow |= STANDARD_RIGHTS_WRITE | FILE_GENERIC_WRITE;
|
|
if (attribute & S_IXOTH)
|
|
other_allow |= FILE_GENERIC_EXECUTE;
|
|
if ((attribute & (S_IFDIR | S_IWOTH | S_IXOTH))
|
|
== (S_IFDIR | S_IWOTH | S_IXOTH)
|
|
&& !(attribute & S_ISVTX))
|
|
other_allow |= FILE_DELETE_CHILD;
|
|
|
|
/* Construct SUID, SGID and VTX bits in NULL ACE. */
|
|
DWORD null_allow = 0L;
|
|
if (attribute & (S_ISUID | S_ISGID | S_ISVTX))
|
|
{
|
|
if (attribute & S_ISUID)
|
|
null_allow |= FILE_APPEND_DATA;
|
|
if (attribute & S_ISGID)
|
|
null_allow |= FILE_WRITE_DATA;
|
|
if (attribute & S_ISVTX)
|
|
null_allow |= FILE_READ_DATA;
|
|
}
|
|
|
|
/* Construct deny attributes for owner and group. */
|
|
DWORD owner_deny = 0;
|
|
if (is_grp_member (uid, gid))
|
|
owner_deny = ~owner_allow & (group_allow | other_allow);
|
|
else
|
|
owner_deny = ~owner_allow & other_allow;
|
|
owner_deny &= ~(STANDARD_RIGHTS_READ
|
|
| FILE_READ_ATTRIBUTES | FILE_READ_EA
|
|
| FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA);
|
|
DWORD group_deny = ~group_allow & other_allow;
|
|
group_deny &= ~(STANDARD_RIGHTS_READ | FILE_READ_ATTRIBUTES | FILE_READ_EA);
|
|
|
|
/* Construct appropriate inherit attribute. */
|
|
DWORD inherit = (attribute & S_IFDIR) ? INHERIT_ALL : DONT_INHERIT;
|
|
|
|
/* Set deny ACE for owner. */
|
|
if (owner_deny
|
|
&& !add_access_denied_ace (acl, ace_off++, owner_deny,
|
|
owner_sid, acl_len, inherit))
|
|
return NULL;
|
|
/* Set allow ACE for owner. */
|
|
if (!add_access_allowed_ace (acl, ace_off++, owner_allow,
|
|
owner_sid, acl_len, inherit))
|
|
return NULL;
|
|
/* Set deny ACE for group. */
|
|
if (group_deny
|
|
&& !add_access_denied_ace (acl, ace_off++, group_deny,
|
|
group_sid, acl_len, inherit))
|
|
return NULL;
|
|
/* Set allow ACE for group. */
|
|
if (!add_access_allowed_ace (acl, ace_off++, group_allow,
|
|
group_sid, acl_len, inherit))
|
|
return NULL;
|
|
|
|
/* Set allow ACE for everyone. */
|
|
if (!add_access_allowed_ace (acl, ace_off++, other_allow,
|
|
well_known_world_sid, acl_len, inherit))
|
|
return NULL;
|
|
/* Set null ACE for special bits. */
|
|
if (null_allow
|
|
&& !add_access_allowed_ace (acl, ace_off++, null_allow,
|
|
well_known_null_sid, acl_len, DONT_INHERIT))
|
|
return NULL;
|
|
|
|
/* Get owner and group from current security descriptor. */
|
|
PSID cur_owner_sid = NULL;
|
|
PSID cur_group_sid = NULL;
|
|
if (!GetSecurityDescriptorOwner (sd_ret, &cur_owner_sid, &dummy))
|
|
debug_printf ("GetSecurityDescriptorOwner %E");
|
|
if (!GetSecurityDescriptorGroup (sd_ret, &cur_group_sid, &dummy))
|
|
debug_printf ("GetSecurityDescriptorGroup %E");
|
|
|
|
/* Fill ACL with unrelated ACEs from current security descriptor. */
|
|
PACL oacl;
|
|
BOOL acl_exists;
|
|
ACCESS_ALLOWED_ACE *ace;
|
|
if (GetSecurityDescriptorDacl (sd_ret, &acl_exists, &oacl, &dummy)
|
|
&& acl_exists && oacl)
|
|
for (DWORD i = 0; i < oacl->AceCount; ++i)
|
|
if (GetAce (oacl, i, (PVOID *) &ace))
|
|
{
|
|
cygsid ace_sid ((PSID) &ace->SidStart);
|
|
/* Check for related ACEs. */
|
|
if ((cur_owner_sid && ace_sid == cur_owner_sid)
|
|
|| (owner_sid && ace_sid == owner_sid)
|
|
|| (cur_group_sid && ace_sid == cur_group_sid)
|
|
|| (group_sid && ace_sid == group_sid)
|
|
|| (ace_sid == well_known_world_sid)
|
|
|| (ace_sid == well_known_null_sid))
|
|
continue;
|
|
/*
|
|
* Add unrelated ACCESS_DENIED_ACE to the beginning but
|
|
* behind the owner_deny, ACCESS_ALLOWED_ACE to the end.
|
|
*/
|
|
if (!AddAce(acl, ACL_REVISION,
|
|
ace->Header.AceType == ACCESS_DENIED_ACE_TYPE ?
|
|
(owner_deny ? 1 : 0) : MAXDWORD,
|
|
(LPVOID) ace, ace->Header.AceSize))
|
|
{
|
|
__seterrno ();
|
|
return NULL;
|
|
}
|
|
acl_len += ace->Header.AceSize;
|
|
}
|
|
|
|
/* Set AclSize to computed value. */
|
|
acl->AclSize = acl_len;
|
|
debug_printf ("ACL-Size: %d", acl_len);
|
|
|
|
/* Create DACL for local security descriptor. */
|
|
if (!SetSecurityDescriptorDacl (&sd, TRUE, acl, FALSE))
|
|
{
|
|
__seterrno ();
|
|
return NULL;
|
|
}
|
|
|
|
/* Make self relative security descriptor. */
|
|
*sd_size_ret = 0;
|
|
MakeSelfRelativeSD (&sd, sd_ret, sd_size_ret);
|
|
if (*sd_size_ret <= 0)
|
|
{
|
|
__seterrno ();
|
|
return NULL;
|
|
}
|
|
if (!MakeSelfRelativeSD (&sd, sd_ret, sd_size_ret))
|
|
{
|
|
__seterrno ();
|
|
return NULL;
|
|
}
|
|
psd = sd_ret;
|
|
debug_printf ("Created SD-Size: %d", *sd_size_ret);
|
|
|
|
return psd;
|
|
}
|
|
|
|
void
|
|
set_security_attribute (int attribute, PSECURITY_ATTRIBUTES psa,
|
|
void *sd_buf, DWORD sd_buf_size)
|
|
{
|
|
/* symlinks are anything for everyone!*/
|
|
if ((attribute & S_IFLNK) == S_IFLNK)
|
|
attribute |= S_IRWXU | S_IRWXG | S_IRWXO;
|
|
|
|
psa->lpSecurityDescriptor = sd_buf;
|
|
InitializeSecurityDescriptor ((PSECURITY_DESCRIPTOR)sd_buf,
|
|
SECURITY_DESCRIPTOR_REVISION);
|
|
psa->lpSecurityDescriptor = alloc_sd (geteuid (), getegid (),
|
|
cygheap->user.logsrv (),
|
|
attribute, (PSECURITY_DESCRIPTOR)sd_buf,
|
|
&sd_buf_size);
|
|
}
|
|
|
|
static int
|
|
set_nt_attribute (const char *file, uid_t uid, gid_t gid,
|
|
const char *logsrv, int attribute)
|
|
{
|
|
if (!wincap.has_security ())
|
|
return 0;
|
|
|
|
DWORD sd_size = 4096;
|
|
char sd_buf[4096];
|
|
PSECURITY_DESCRIPTOR psd = (PSECURITY_DESCRIPTOR) sd_buf;
|
|
|
|
int ret;
|
|
if ((ret = read_sd (file, psd, &sd_size)) <= 0)
|
|
{
|
|
debug_printf ("read_sd %E");
|
|
return -1;
|
|
}
|
|
|
|
sd_size = 4096;
|
|
if (!(psd = alloc_sd (uid, gid, logsrv, attribute, psd, &sd_size)))
|
|
return -1;
|
|
|
|
return write_sd (file, psd, sd_size);
|
|
}
|
|
|
|
int
|
|
set_file_attribute (int use_ntsec, const char *file,
|
|
uid_t uid, gid_t gid,
|
|
int attribute, const char *logsrv)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (use_ntsec && allow_ntsec)
|
|
ret = set_nt_attribute (file, uid, gid, logsrv, attribute);
|
|
else if (allow_ntea && !NTWriteEA (file, ".UNIXATTR", (char *) &attribute,
|
|
sizeof (attribute)))
|
|
{
|
|
__seterrno ();
|
|
ret = -1;
|
|
}
|
|
syscall_printf ("%d = set_file_attribute (%s, %d, %d, %p)",
|
|
ret, file, uid, gid, attribute);
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
set_file_attribute (int use_ntsec, const char *file, int attribute)
|
|
{
|
|
return set_file_attribute (use_ntsec, file,
|
|
myself->uid, myself->gid,
|
|
attribute, cygheap->user.logsrv ());
|
|
}
|