summaryrefslogtreecommitdiffstats
path: root/winsup/cygwin/uinfo.cc
diff options
context:
space:
mode:
Diffstat (limited to 'winsup/cygwin/uinfo.cc')
-rw-r--r--winsup/cygwin/uinfo.cc1194
1 files changed, 1113 insertions, 81 deletions
diff --git a/winsup/cygwin/uinfo.cc b/winsup/cygwin/uinfo.cc
index 4ca901f35..6ef2719e0 100644
--- a/winsup/cygwin/uinfo.cc
+++ b/winsup/cygwin/uinfo.cc
@@ -1,7 +1,7 @@
/* uinfo.cc: user info (uid, gid, etc...)
Copyright 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
- 2007, 2008, 2009, 2010, 2011, 2012 Red Hat, Inc.
+ 2007, 2008, 2009, 2010, 2011, 2012, 2014 Red Hat, Inc.
This file is part of Cygwin.
@@ -10,12 +10,14 @@ Cygwin license. Please consult the file "CYGWIN_LICENSE" for
details. */
#include "winsup.h"
-#include <unistd.h>
+#include <iptypes.h>
+#include <lm.h>
+#include <ntsecapi.h>
#include <wininet.h>
+#include <unistd.h>
#include <stdlib.h>
+#include <stdio.h>
#include <wchar.h>
-#include <lm.h>
-#include <iptypes.h>
#include <sys/cygwin.h>
#include "cygerrno.h"
#include "pinfo.h"
@@ -27,10 +29,12 @@ details. */
#include "registry.h"
#include "child_info.h"
#include "environ.h"
-#include "pwdgrp.h"
#include "tls_pbuf.h"
+#include "miscfuncs.h"
#include "ntdll.h"
+#include "ldap.h"
+
/* Initialize the part of cygheap_user that does not depend on files.
The information is used in shared.cc for the user shared.
Final initialization occurs in uinfo_init */
@@ -113,13 +117,13 @@ void
internal_getlogin (cygheap_user &user)
{
struct passwd *pw = NULL;
+ struct group *gr, *gr2;
cygpsid psid = user.sid ();
pw = internal_getpwsid (psid);
- if (!pw && !(pw = internal_getpwnam (user.name ()))
- && !(pw = internal_getpwuid (DEFAULT_UID)))
- debug_printf ("user not found in augmented /etc/passwd");
+ if (!pw && !(pw = internal_getpwnam (user.name ())))
+ debug_printf ("user not found in /etc/passwd");
else
{
cygsid gsid;
@@ -127,11 +131,17 @@ internal_getlogin (cygheap_user &user)
myself->uid = pw->pw_uid;
myself->gid = pw->pw_gid;
user.set_name (pw->pw_name);
- if (gsid.getfromgr (internal_getgrgid (pw->pw_gid)))
+ if (gsid.getfromgr (gr = internal_getgrgid (pw->pw_gid)))
{
+ /* We might have a group file with a group entry for the current
+ user's primary group, but the current user has no entry in passwd.
+ If so, pw_gid is taken from windows and might disagree with the
+ gr_gid from the group file. Overwrite it brutally. */
+ if ((gr2 = internal_getgrsid (gsid)) && gr2 != gr)
+ myself->gid = pw->pw_gid = gr2->gr_gid;
+ /* Set primary group to the group in /etc/passwd. */
if (gsid != user.groups.pgsid)
{
- /* Set primary group to the group in /etc/passwd. */
NTSTATUS status = NtSetInformationToken (hProcToken,
TokenPrimaryGroup,
&gsid, sizeof gsid);
@@ -415,7 +425,7 @@ cygheap_user::env_logsrv (const char *name, size_t namelen)
WCHAR wlogsrv[INTERNET_MAX_HOST_NAME_LENGTH + 3];
sys_mbstowcs (wdomain, MAX_DOMAIN_NAME_LEN + 1, mydomain);
cfree_and_set (plogsrv, almost_null);
- if (get_logon_server (wdomain, wlogsrv, false))
+ if (get_logon_server (wdomain, wlogsrv, DS_IS_FLAT_NAME))
sys_wcstombs_alloc (&plogsrv, HEAP_STR, wlogsrv);
return plogsrv;
}
@@ -525,104 +535,1126 @@ pwdgrp::add_line (char *eptr)
{
if (eptr)
{
- lptr = eptr;
- eptr = strchr (lptr, '\n');
- if (eptr)
- {
- if (eptr > lptr && eptr[-1] == '\r')
- eptr[-1] = '\0';
- else
- *eptr = '\0';
- eptr++;
- }
if (curr_lines >= max_lines)
{
max_lines += 10;
- *pwdgrp_buf = realloc (*pwdgrp_buf, max_lines * pwdgrp_buf_elem_size);
+ pwdgrp_buf = crealloc_abort (pwdgrp_buf,
+ max_lines * pwdgrp_buf_elem_size);
}
+ lptr = eptr;
if ((this->*parse) ())
curr_lines++;
}
return eptr;
}
+ugid_cache_t ugid_cache;
+
void
-pwdgrp::load (const wchar_t *rel_path)
+cygheap_pwdgrp::init ()
{
- static const char failed[] = "failed";
- static const char succeeded[] = "succeeded";
- const char *res = failed;
- HANDLE fh = NULL;
+ pwd_cache.file.init_pwd ();
+ pwd_cache.win.init_pwd ();
+ grp_cache.file.init_grp ();
+ grp_cache.win.init_grp ();
+ /* Default settings:
+
+ passwd: files db
+ group: files db
+ db_prefix: auto
+ db_cache: yes
+ db_separator: +
+ */
+ pwd_src = (NSS_FILES | NSS_DB);
+ grp_src = (NSS_FILES | NSS_DB);
+ prefix = NSS_AUTO;
+ separator[0] = L'+';
+ caching = true;
+}
- NTSTATUS status;
+/* The /etc/nssswitch.conf file is read exactly once by the root process of a
+ process tree. We can't afford methodical changes during the lifetime of a
+ process tree. */
+void
+cygheap_pwdgrp::nss_init_line (const char *line)
+{
+ const char *c = line + strspn (line, " \t");
+ switch (*c)
+ {
+ case 'p':
+ case 'g':
+ {
+ int *src = NULL;
+ if (!strncmp (c, "passwd:", 7))
+ {
+ src = &pwd_src;
+ c += 7;
+ }
+ else if (!strncmp (c, "group:", 6))
+ {
+ src = &grp_src;
+ c += 6;
+ }
+ if (src)
+ {
+ *src = 0;
+ while (*c)
+ {
+ c += strspn (c, " \t");
+ if (!*c || *c == '#')
+ break;
+ if (!strncmp (c, "files", 5) && strchr (" \t", c[5]))
+ {
+ *src |= NSS_FILES;
+ c += 5;
+ }
+ else if (!strncmp (c, "db", 2) && strchr (" \t", c[2]))
+ {
+ *src |= NSS_DB;
+ c += 2;
+ }
+ else
+ {
+ c += strcspn (c, " \t");
+ debug_printf ("Invalid nsswitch.conf content: %s", line);
+ }
+ }
+ if (*src == 0)
+ *src = (NSS_FILES | NSS_DB);
+ }
+ }
+ break;
+ case 'd':
+ if (strncmp (c, "db_", 3))
+ {
+ debug_printf ("Invalid nsswitch.conf content: %s", line);
+ break;
+ }
+ c += 3;
+ if (!strncmp (c, "prefix:", 7))
+ {
+ c += 7;
+ c += strspn (c, " \t");
+ if (!strncmp (c, "auto", 4) && strchr (" \t", c[4]))
+ prefix = NSS_AUTO;
+ else if (!strncmp (c, "primary", 7) && strchr (" \t", c[7]))
+ prefix = NSS_PRIMARY;
+ else if (!strncmp (c, "always", 6) && strchr (" \t", c[6]))
+ prefix = NSS_ALWAYS;
+ else
+ debug_printf ("Invalid nsswitch.conf content: %s", line);
+ }
+ else if (!strncmp (c, "separator:", 10))
+ {
+ c += 10;
+ c += strspn (c, " \t");
+ if ((unsigned char) *c <= 0x7f && strchr (" \t", c[1]))
+ separator[0] = (unsigned char) *c;
+ else
+ debug_printf ("Invalid nsswitch.conf content: %s", line);
+ }
+ else if (!strncmp (c, "cache:", 6))
+ {
+ c += 6;
+ c += strspn (c, " \t");
+ if (!strncmp (c, "yes", 3) && strchr (" \t", c[3]))
+ caching = true;
+ else if (!strncmp (c, "no", 2) && strchr (" \t", c[2]))
+ caching = false;
+ else
+ debug_printf ("Invalid nsswitch.conf content: %s", line);
+ }
+ break;
+ case '\0':
+ case '#':
+ break;
+ default:
+ debug_printf ("Invalid nsswitch.conf content: %s", line);
+ break;
+ }
+}
+
+void
+cygheap_pwdgrp::_nss_init ()
+{
+ UNICODE_STRING path;
OBJECT_ATTRIBUTES attr;
- IO_STATUS_BLOCK io;
- FILE_STANDARD_INFORMATION fsi;
+ NT_readline rl;
+ tmp_pathbuf tp;
+ char *buf = tp.c_get ();
+
+ PCWSTR rel_path = L"\\etc\\nsswitch.conf";
+ path.Buffer = (PWCHAR) alloca ((wcslen (cygheap->installation_root)
+ + wcslen (rel_path) + 1) * sizeof (WCHAR));
+ wcpcpy (wcpcpy (path.Buffer, cygheap->installation_root), rel_path);
+ RtlInitUnicodeString (&path, path.Buffer);
+ InitializeObjectAttributes (&attr, &path, OBJ_CASE_INSENSITIVE,
+ NULL, NULL);
+ if (rl.init (&attr, buf, NT_MAX_PATH))
+ while ((buf = rl.gets ()))
+ nss_init_line (buf);
+ nss_inited = true;
+}
- if (buf)
- free (buf);
- buf = NULL;
- curr_lines = 0;
+/* Override the ParentIndex value of the PDS_DOMAIN_TRUSTSW entry with the
+ PosixOffset. */
+#define PosixOffset ParentIndex
- if (!path &&
- !(path = (PWCHAR) malloc ((wcslen (cygheap->installation_root)
- + wcslen (rel_path) + 1) * sizeof (WCHAR))))
+bool
+cygheap_domain_info::init ()
+{
+ HANDLE lsa;
+ NTSTATUS status;
+ ULONG ret;
+ /* We *have* to copy the information. Apart from our wish to have the
+ stuff in the cygheap, even when not calling LsaFreeMemory on the result,
+ the data will be overwritten later. From what I gather, the information
+ is, in fact, stored on the stack. */
+ PPOLICY_DNS_DOMAIN_INFO pdom;
+ PPOLICY_ACCOUNT_DOMAIN_INFO adom;
+ PDS_DOMAIN_TRUSTSW td;
+ ULONG tdom_cnt;
+
+ if (adom_name)
+ return true;
+ lsa = lsa_open_policy (NULL, POLICY_VIEW_LOCAL_INFORMATION);
+ if (!lsa)
+ {
+ system_printf ("lsa_open_policy(NULL) failed");
+ return false;
+ }
+ /* Fetch primary domain information from local LSA. */
+ status = LsaQueryInformationPolicy (lsa, PolicyDnsDomainInformation,
+ (PVOID *) &pdom);
+ if (status != STATUS_SUCCESS)
{
- paranoid_printf ("malloc (%W) failed", rel_path);
- goto out;
+ system_printf ("LsaQueryInformationPolicy(Primary) %u", status);
+ return false;
}
- wcpcpy (wcpcpy (path, cygheap->installation_root), rel_path);
- RtlInitUnicodeString (&upath, path);
+ /* Copy primary domain info to cygheap. */
+ pdom_name = cwcsdup (pdom->Name.Buffer);
+ pdom_dns_name = pdom->DnsDomainName.Length
+ ? cwcsdup (pdom->DnsDomainName.Buffer) : NULL;
+ pdom_sid = pdom->Sid;
+ LsaFreeMemory (pdom);
+ /* Fetch account domain information from local LSA. */
+ status = LsaQueryInformationPolicy (lsa, PolicyAccountDomainInformation,
+ (PVOID *) &adom);
+ if (status != STATUS_SUCCESS)
+ {
+ system_printf ("LsaQueryInformationPolicy(Account) %u", status);
+ return false;
+ }
+ /* Copy account domain info to cygheap. */
+ adom_name = cwcsdup (adom->DomainName.Buffer);
+ adom_sid = adom->DomainSid;
+ LsaFreeMemory (adom);
+ lsa_close_policy (lsa);
+ if (cygheap->dom.member_machine ())
+ {
+ /* For a domain member machine fetch all trusted domain info. */
+ lowest_tdo_posix_offset = UNIX_POSIX_OFFSET - 1;
+ ret = DsEnumerateDomainTrustsW (NULL, DS_DOMAIN_DIRECT_INBOUND
+ | DS_DOMAIN_DIRECT_OUTBOUND
+ | DS_DOMAIN_IN_FOREST,
+ &td, &tdom_cnt);
+ if (ret != ERROR_SUCCESS)
+ {
+ SetLastError (ret);
+ debug_printf ("DsEnumerateDomainTrusts: %E");
+ return true;
+ }
+ if (tdom_cnt == 0)
+ {
+ return true;
+ }
+ /* Copy trusted domain info to cygheap, setting PosixOffset on the fly. */
+ tdom = (PDS_DOMAIN_TRUSTSW)
+ cmalloc_abort (HEAP_BUF, tdom_cnt * sizeof (DS_DOMAIN_TRUSTSW));
+ memcpy (tdom, td, tdom_cnt * sizeof (DS_DOMAIN_TRUSTSW));
+ for (ULONG idx = 0; idx < tdom_cnt; ++idx)
+ {
+ /* Copy... */
+ tdom[idx].NetbiosDomainName = cwcsdup (td[idx].NetbiosDomainName);
+ tdom[idx].DnsDomainName = cwcsdup (td[idx].DnsDomainName);
+ ULONG len = RtlLengthSid (td[idx].DomainSid);
+ tdom[idx].DomainSid = cmalloc_abort(HEAP_BUF, len);
+ RtlCopySid (len, tdom[idx].DomainSid, td[idx].DomainSid);
+ /* ...and set PosixOffset to 0. This */
+ tdom[idx].PosixOffset = 0;
+ }
+ NetApiBufferFree (td);
+ tdom_count = tdom_cnt;
+ }
+ /* If we have NFS installed, we make use of a name mapping server. This
+ can be either Active Directory to map uids/gids directly to Windows SIDs,
+ or an AD LDS or other RFC 2307 compatible identity store. The name of
+ the mapping domain can be fetched from the registry key created by the
+ NFS client installation and entered by the user via nfsadmin or the
+ "Services For NFS" MMC snap-in.
+
+ Reference:
+ http://blogs.technet.com/b/filecab/archive/2012/10/09/nfs-identity-mapping-in-windows-server-2012.aspx
+ Note that we neither support UNMP nor local passwd/group file mapping,
+ nor UUUA.
+
+ This function returns the mapping server from the aforementioned registry
+ key, or, if none is configured, NULL, which will be resolved to the
+ primary domain of the machine by the ldap_init function.
+
+ The latter is useful to get an RFC 2307 mapping for Samba UNIX accounts,
+ even if no NFS name mapping is configured on the machine. Fortunately,
+ the posixAccount and posixGroup schemas are already available in the
+ Active Directory default setup since Windows Server 2003 R2. */
+ reg_key reg (HKEY_LOCAL_MACHINE, KEY_READ | KEY_WOW64_64KEY,
+ L"SOFTWARE", L"Microsoft", L"ServicesForNFS", NULL);
+ if (!reg.error ())
+ {
+ DWORD rfc2307 = reg.get_dword (L"Rfc2307", 0);
+ if (rfc2307)
+ {
+ rfc2307_domain_buf = (PWCHAR) ccalloc_abort (HEAP_STR, 257,
+ sizeof (WCHAR));
+ reg.get_string (L"Rfc2307Domain", rfc2307_domain_buf, 257, L"");
+ if (!rfc2307_domain_buf[0])
+ {
+ cfree (rfc2307_domain_buf);
+ rfc2307_domain_buf = NULL;
+ }
+ }
+ }
+ return true;
+}
- InitializeObjectAttributes (&attr, &upath, OBJ_CASE_INSENSITIVE, NULL, NULL);
- etc_ix = etc::init (etc_ix, &attr);
+/* Per session, so it changes potentially when switching the user context. */
+static cygsid logon_sid ("");
- paranoid_printf ("%S", &upath);
+static void
+get_logon_sid ()
+{
+ if (PSID (logon_sid) == NO_SID)
+ {
+ NTSTATUS status;
+ ULONG size;
+ tmp_pathbuf tp;
+ PTOKEN_GROUPS groups = (PTOKEN_GROUPS) tp.c_get ();
- status = NtOpenFile (&fh, SYNCHRONIZE | FILE_READ_DATA, &attr, &io,
- FILE_SHARE_VALID_FLAGS,
- FILE_SYNCHRONOUS_IO_NONALERT
- | FILE_OPEN_FOR_BACKUP_INTENT);
- if (!NT_SUCCESS (status))
+ status = NtQueryInformationToken (hProcToken, TokenGroups, groups,
+ NT_MAX_PATH, &size);
+ if (!NT_SUCCESS (status))
+ debug_printf ("NtQueryInformationToken() %y", status);
+ else
+ {
+ for (DWORD pg = 0; pg < groups->GroupCount; ++pg)
+ if (groups->Groups[pg].Attributes & SE_GROUP_LOGON_ID)
+ {
+ logon_sid = groups->Groups[pg].Sid;
+ break;
+ }
+ }
+ }
+}
+
+void *
+pwdgrp::add_account_post_fetch (char *line)
+{
+ if (line)
+ {
+ void *ret;
+ pglock.init ("pglock")->acquire ();
+ add_line (line);
+ ret = ((char *) pwdgrp_buf) + (curr_lines - 1) * pwdgrp_buf_elem_size;
+ pglock.release ();
+ return ret;
+ }
+ return NULL;
+}
+
+void *
+pwdgrp::add_account_from_file (cygpsid &sid)
+{
+ if (!path.MaximumLength)
+ return NULL;
+ fetch_user_arg_t arg;
+ arg.type = SID_arg;
+ arg.sid = &sid;
+ char *line = fetch_account_from_file (arg);
+ return (struct passwd *) add_account_post_fetch (line);
+}
+
+void *
+pwdgrp::add_account_from_file (const char *name)
+{
+ if (!path.MaximumLength)
+ return NULL;
+ fetch_user_arg_t arg;
+ arg.type = NAME_arg;
+ arg.name = name;
+ char *line = fetch_account_from_file (arg);
+ return (struct passwd *) add_account_post_fetch (line);
+}
+
+void *
+pwdgrp::add_account_from_file (uint32_t id)
+{
+ if (!path.MaximumLength)
+ return NULL;
+ fetch_user_arg_t arg;
+ arg.type = ID_arg;
+ arg.id = id;
+ char *line = fetch_account_from_file (arg);
+ return (struct passwd *) add_account_post_fetch (line);
+}
+
+void *
+pwdgrp::add_account_from_windows (cygpsid &sid, bool group)
+{
+ fetch_user_arg_t arg;
+ arg.type = SID_arg;
+ arg.sid = &sid;
+ char *line = fetch_account_from_windows (arg, group);
+ if (!line)
+ return NULL;
+ if (cygheap->pg.nss_db_caching ())
+ return add_account_post_fetch (line);
+ return (prep_tls_pwbuf ())->add_account_post_fetch (line);
+}
+
+void *
+pwdgrp::add_account_from_windows (const char *name, bool group)
+{
+ fetch_user_arg_t arg;
+ arg.type = NAME_arg;
+ arg.name = name;
+ char *line = fetch_account_from_windows (arg, group);
+ if (!line)
+ return NULL;
+ if (cygheap->pg.nss_db_caching ())
+ return add_account_post_fetch (line);
+ return (prep_tls_pwbuf ())->add_account_post_fetch (line);
+}
+
+void *
+pwdgrp::add_account_from_windows (uint32_t id, bool group)
+{
+ fetch_user_arg_t arg;
+ arg.type = ID_arg;
+ arg.id = id;
+ char *line = fetch_account_from_windows (arg, group);
+ if (!line)
+ return NULL;
+ if (cygheap->pg.nss_db_caching ())
+ return add_account_post_fetch (line);
+ return (prep_tls_pwbuf ())->add_account_post_fetch (line);
+}
+
+/* Check if file exists and if it has been written to since last checked.
+ If file has been changed, invalidate the current cache.
+
+ If the file doesn't exist when this function is called the first time,
+ by the first Cygwin process in a process tree, the file will never be
+ visited again by any process in this process tree. This is important,
+ because we cannot allow a change of UID/GID values for the lifetime
+ of a process tree.
+
+ If the file gets deleted or unreadable, the file cache will stay in
+ place, but we won't try to read new accounts from the file.
+
+ The return code indicates to the calling function if the file exists. */
+bool
+pwdgrp::check_file (bool group)
+{
+ FILE_BASIC_INFORMATION fbi;
+ NTSTATUS status;
+
+ if (!path.Buffer)
{
- paranoid_printf ("NtOpenFile(%S) failed, status %y", &upath, status);
- goto out;
+ PCWSTR rel_path = group ? L"\\etc\\group" : L"\\etc\\passwd";
+ path.Buffer = (PWCHAR) cmalloc_abort (HEAP_BUF,
+ (wcslen (cygheap->installation_root)
+ + wcslen (rel_path) + 1)
+ * sizeof (WCHAR));
+ wcpcpy (wcpcpy (path.Buffer, cygheap->installation_root), rel_path);
+ RtlInitUnicodeString (&path, path.Buffer);
+ InitializeObjectAttributes (&attr, &path, OBJ_CASE_INSENSITIVE,
+ NULL, NULL);
}
- status = NtQueryInformationFile (fh, &io, &fsi, sizeof fsi,
- FileStandardInformation);
+ else if (path.MaximumLength == 0) /* Indicates that the file doesn't exist. */
+ return false;
+ status = NtQueryAttributesFile (&attr, &fbi);
if (!NT_SUCCESS (status))
{
- paranoid_printf ("NtQueryInformationFile(%S) failed, status %y",
- &upath, status);
- goto out;
+ if (last_modified.QuadPart)
+ last_modified.QuadPart = 0LL;
+ else
+ path.MaximumLength = 0;
+ return false;
}
- /* FIXME: Should we test for HighPart set? If so, the
- passwd or group file is way beyond what we can handle. */
- /* FIXME 2: It's still ugly that we keep the file in memory.
- Big organizations have naturally large passwd files. */
- buf = (char *) malloc (fsi.EndOfFile.LowPart + 1);
- if (!buf)
+ if (fbi.LastWriteTime.QuadPart > last_modified.QuadPart)
{
- paranoid_printf ("malloc (%u) failed", fsi.EndOfFile.LowPart);
- goto out;
+ last_modified.QuadPart = fbi.LastWriteTime.QuadPart;
+ if (curr_lines > 0)
+ {
+ pglock.init ("pglock")->acquire ();
+ int curr = curr_lines;
+ curr_lines = 0;
+ for (int i = 0; i < curr; ++i)
+ cfree (group ? this->group ()[i].g.gr_name
+ : this->passwd ()[i].p.pw_name);
+ pglock.release ();
+ }
}
- status = NtReadFile (fh, NULL, NULL, NULL, &io, buf, fsi.EndOfFile.LowPart,
- NULL, NULL);
- if (!NT_SUCCESS (status))
+ return true;
+}
+
+char *
+pwdgrp::fetch_account_from_line (fetch_user_arg_t &arg, const char *line)
+{
+ char *p, *e;
+
+ switch (arg.type)
+ {
+ case SID_arg:
+ /* Ignore fields, just scan for SID string. */
+ if (!(p = strstr (line, arg.name)) || p[arg.len] != ':')
+ return NULL;
+ break;
+ case NAME_arg:
+ /* First field is always name. */
+ if (!strncasematch (line, arg.name, arg.len) || line[arg.len] != ':')
+ return NULL;
+ break;
+ case ID_arg:
+ /* Skip to third field. */
+ if (!(p = strchr (line, ':')) || !(p = strchr (p + 1, ':')))
+ return NULL;
+ if (strtoul (p + 1, &e, 10) != arg.id || !e || *e != ':')
+ return NULL;
+ break;
+ }
+ return cstrdup (line);
+}
+
+char *
+pwdgrp::fetch_account_from_file (fetch_user_arg_t &arg)
+{
+ NT_readline rl;
+ tmp_pathbuf tp;
+ char *buf = tp.c_get ();
+ char *str = tp.c_get ();
+ char *ret = NULL;
+
+ /* Create search string. */
+ switch (arg.type)
+ {
+ case SID_arg:
+ /* Override SID with SID string. */
+ arg.sid->string (str);
+ arg.name = str;
+ /*FALLTHRU*/
+ case NAME_arg:
+ arg.len = strlen (arg.name);
+ break;
+ case ID_arg:
+ break;
+ }
+ if (rl.init (&attr, buf, NT_MAX_PATH))
+ while ((buf = rl.gets ()))
+ if ((ret = fetch_account_from_line (arg, buf)))
+ return ret;
+ return NULL;
+}
+
+char *
+pwdgrp::fetch_account_from_windows (fetch_user_arg_t &arg, bool group)
+{
+ /* Used in LookupAccount calls. */
+ WCHAR namebuf[UNLEN + 1], *name = namebuf;
+ WCHAR dom[DNLEN + 1] = L"";
+ cygsid csid;
+ DWORD nlen = UNLEN + 1;
+ DWORD dlen = DNLEN + 1;
+ DWORD slen = MAX_SID_LEN;
+ cygpsid sid = NO_SID;
+ SID_NAME_USE acc_type;
+ BOOL ret = false;
+ /* Cygwin user name style. */
+ enum {
+ name_only,
+ plus_prepended,
+ fully_qualified
+ } name_style = name_only;
+ /* Computed stuff. */
+ uid_t uid = ILLEGAL_UID;
+ gid_t gid = ILLEGAL_GID;
+ bool is_domain_account = true;
+ PCWSTR domain = NULL;
+ PWCHAR shell = NULL;
+ PWCHAR user = NULL;
+ PWCHAR home = NULL;
+ PWCHAR gecos = NULL;
+ PWCHAR p;
+ WCHAR sidstr[128];
+ /* Temporary stuff. */
+ ULONG posix_offset = 0;
+ cyg_ldap cldap;
+ bool ldap_open = false;
+
+ /* Initialize */
+ if (!cygheap->dom.init ())
+ return NULL;
+
+ switch (arg.type)
{
- paranoid_printf ("NtReadFile(%S) failed, status %y", &upath, status);
- free (buf);
- goto out;
+ case SID_arg:
+ sid = *arg.sid;
+ ret = LookupAccountSidW (NULL, sid, name, &nlen, dom, &dlen, &acc_type);
+ break;
+ case NAME_arg:
+ /* Skip leading domain separator. This denotes an alias or well-known
+ group, which will be found first by LookupAccountNameW anyway.
+ Otherwise, if the name has no leading domain name, it's either a
+ standalone machine, or the username must be from the primary domain.
+ In the latter case, prepend the primary domain name so as not to
+ collide with an account from the account domain with the same name. */
+ p = name;
+ if (*arg.name == cygheap->pg.nss_separator ()[0])
+ ++arg.name;
+ else if (!strchr (arg.name, cygheap->pg.nss_separator ()[0])
+ && cygheap->dom.member_machine ())
+ p = wcpcpy (wcpcpy (p, cygheap->dom.primary_flat_name ()),
+ cygheap->pg.nss_separator ());
+ /* Now fill up with name to search. */
+ sys_mbstowcs (p, UNLEN + 1, arg.name);
+ /* Replace domain separator char with backslash and make sure p is NULL
+ or points to the backslash, so... */
+ if ((p = wcschr (name, cygheap->pg.nss_separator ()[0])))
+ *p = L'\\';
+ sid = csid;
+ ret = LookupAccountNameW (NULL, name, sid, &slen, dom, &dlen, &acc_type);
+ if (!ret)
+ {
+ debug_printf ("LookupAccountNameW (%W), %E", name);
+ return NULL;
+ }
+ /* ... we can skip the backslash in the rest of this function. */
+ if (p)
+ name = p + 1;
+ break;
+ case ID_arg:
+ /* Construct SID from ID using the SFU rules, just like the code below
+ goes the opposite route. */
+#ifndef INTERIX_COMPATIBLE
+ /* Except for Builtin and Alias groups in the SECURITY_NT_AUTHORITY.
+ We create uid/gid values compatible with the old values generated
+ by mkpasswd/mkgroup. */
+ if (arg.id < 0x200)
+ __small_swprintf (sidstr, L"S-1-5-%u", arg.id & 0x1ff);
+ else if (arg.id <= 0x7ff)
+ __small_swprintf (sidstr, L"S-1-5-32-%u", arg.id & 0x7ff);
+ else
+#endif
+ if (arg.id == 0xffe)
+ {
+ /* OtherSession != Logon SID. */
+ get_logon_sid ();
+ /* LookupAccountSidW will fail. */
+ sid = csid = logon_sid;
+ sid_sub_auth_rid (sid) = 0;
+ break;
+ }
+ else if (arg.id == 0xfff)
+ {
+ /* CurrentSession == Logon SID. */
+ get_logon_sid ();
+ /* LookupAccountSidW will fail. */
+ sid = logon_sid;
+ break;
+ }
+ else if (arg.id < 0x10000)
+ {
+ /* Nothing. */
+ debug_printf ("Invalid POSIX id %u", arg.id);
+ return NULL;
+ }
+ else if (arg.id < 0x20000)
+ {
+ /* Well-Known Group */
+ arg.id -= 0x10000;
+ __small_swprintf (sidstr, L"S-1-%u-%u", arg.id >> 8, arg.id & 0xff);
+ }
+ else if (arg.id >= 0x30000 && arg.id < 0x40000)
+ {
+ /* Account domain user or group. */
+ PWCHAR s = cygheap->dom.account_sid ().pstring (sidstr);
+ __small_swprintf (s, L"-%u", arg.id & 0xffff);
+ }
+ else if (arg.id < 0x60000)
+ {
+ /* Builtin Alias */
+ __small_swprintf (sidstr, L"S-1-5-%u-%u",
+ arg.id >> 12, arg.id & 0xffff);
+ }
+ else if (arg.id < 0x70000)
+ {
+ /* Mandatory Label. */
+ __small_swprintf (sidstr, L"S-1-16-%u", arg.id & 0xffff);
+ }
+ else if (arg.id < 0x80000)
+ {
+ /* Identity assertion SIDs. */
+ __small_swprintf (sidstr, L"S-1-18-%u", arg.id & 0xffff);
+ }
+ else if (arg.id < 0x100000)
+ {
+ /* Nothing. */
+ debug_printf ("Invalid POSIX id %u", arg.id);
+ return NULL;
+ }
+ else if (arg.id == ILLEGAL_UID)
+ {
+ /* Just some fake. */
+ sid = csid = "S-1-99-0";
+ break;
+ }
+ else if (arg.id >= UNIX_POSIX_OFFSET)
+ {
+ /* UNIX (unknown NFS or Samba) user account. */
+ __small_swprintf (sidstr, L"S-1-22-%u-%u",
+ group ? 2 : 1, arg.id & UNIX_POSIX_MASK);
+ /* LookupAccountSidW will fail. */
+ sid = csid = sidstr;
+ break;
+ }
+ else
+ {
+ /* Some trusted domain? */
+ PDS_DOMAIN_TRUSTSW td = NULL;
+
+ for (ULONG idx = 0; (td = cygheap->dom.trusted_domain (idx)); ++idx)
+ {
+ /* If we don't have the PosixOffset of the domain, fetch it.
+ Skip primary domain. */
+ if (!td->PosixOffset && !(td->Flags & DS_DOMAIN_PRIMARY))
+ {
+ uint32_t id_val;
+
+ if (!ldap_open && !(ldap_open = cldap.open (NULL)))
+ id_val = cygheap->dom.lowest_tdo_posix_offset
+ - 0x01000000;
+ else
+ id_val =
+ cldap.fetch_posix_offset_for_domain (td->DnsDomainName);
+ if (id_val)
+ {
+ td->PosixOffset = id_val;
+ if (id_val < cygheap->dom.lowest_tdo_posix_offset)
+ cygheap->dom.lowest_tdo_posix_offset = id_val;
+ }
+ }
+ if (td->PosixOffset > posix_offset && td->PosixOffset <= arg.id)
+ posix_offset = td->PosixOffset;
+ }
+ if (posix_offset)
+ {
+ cygpsid tsid (td->DomainSid);
+ PWCHAR s = tsid.pstring (sidstr);
+ __small_swprintf (s, L"-%u", arg.id - posix_offset);
+ }
+ else
+ {
+ /* Primary domain */
+ PWCHAR s = cygheap->dom.primary_sid ().pstring (sidstr);
+ __small_swprintf (s, L"-%u", arg.id - 0x100000);
+ }
+ posix_offset = 0;
+ }
+ sid = csid = sidstr;
+ ret = LookupAccountSidW (NULL, sid, name, &nlen, dom, &dlen, &acc_type);
+ if (!ret)
+ {
+ debug_printf ("LookupAccountSidW (%W), %E", sidstr);
+ return NULL;
+ }
+ break;
}
- buf[fsi.EndOfFile.LowPart] = '\0';
- for (char *eptr = buf; (eptr = add_line (eptr)); )
- continue;
- debug_printf ("%W curr_lines %d", rel_path, curr_lines);
- res = succeeded;
-
-out:
- if (fh)
- NtClose (fh);
- debug_printf ("%W load %s", rel_path, res);
- initialized = true;
+ if (ret)
+ {
+ /* Builtin account? SYSTEM, for instance, is returned as SidTypeUser,
+ if a process is running as LocalSystem service. */
+ if (acc_type == SidTypeUser && sid_sub_auth_count (sid) <= 3)
+ acc_type = SidTypeWellKnownGroup;
+ /* Alias? There are two types, the builtin aliases like "Administrators"
+ and the local groups in SAM. Handle local groups as groups. */
+ else if (acc_type == SidTypeAlias
+ && sid_sub_auth (sid, 0) == SECURITY_NT_NON_UNIQUE)
+ acc_type = SidTypeGroup;
+
+ switch (acc_type)
+ {
+ case SidTypeUser:
+ case SidTypeGroup:
+ /* Account domain account? */
+ if (!wcscmp (dom, cygheap->dom.account_flat_name ()))
+ {
+ posix_offset = 0x30000;
+ if (cygheap->dom.member_machine ()
+ || !cygheap->pg.nss_prefix_auto ())
+ name_style = fully_qualified;
+ domain = cygheap->dom.account_flat_name ();
+ is_domain_account = false;
+ }
+ /* Domain member machine? */
+ else if (cygheap->dom.member_machine ())
+ {
+ /* Primary domain account? */
+ if (!wcscmp (dom, cygheap->dom.primary_flat_name ()))
+ {
+ posix_offset = 0x100000;
+ /* In theory domain should have been set to
+ cygheap->dom.primary_dns_name (), but it turns out
+ that not setting the domain here has advantages.
+ We open the ldap connection to NULL (== some domain
+ control of our primary domain) anyway. So the domain
+ is only used
+ later on. So, don't set domain here to non-NULL, unless
+ you're sure you have also changed subsequent assumptions
+ that domain is NULL if it's a primary domain account. */
+ domain = NULL;
+ if (!cygheap->pg.nss_prefix_auto ())
+ name_style = fully_qualified;
+ }
+ else
+ {
+ /* No, fetch POSIX offset. */
+ PDS_DOMAIN_TRUSTSW td = NULL;
+
+ name_style = fully_qualified;
+ for (ULONG idx = 0;
+ (td = cygheap->dom.trusted_domain (idx));
+ ++idx)
+ {
+ if (wcscmp (dom, td->NetbiosDomainName))
+ continue;
+ domain = td->DnsDomainName;
+ posix_offset = td->PosixOffset;
+ /* If we don't have the PosixOffset of the domain,
+ fetch it. */
+ if (!posix_offset)
+ {
+ uint32_t id_val;
+
+ if (!ldap_open && !(ldap_open = cldap.open (NULL)))
+ {
+ /* We're probably running under a local account,
+ so we're not allowed to fetch any information
+ from AD beyond the most obvious. Never mind,
+ just fake a reasonable posix offset. */
+ id_val = cygheap->dom.lowest_tdo_posix_offset
+ - 0x01000000;
+ }
+ else
+ id_val =
+ cldap.fetch_posix_offset_for_domain (domain);
+ if (id_val)
+ {
+ td->PosixOffset = posix_offset = id_val;
+ if (id_val < cygheap->dom.lowest_tdo_posix_offset)
+ cygheap->dom.lowest_tdo_posix_offset = id_val;
+ }
+ }
+ break;
+ }
+
+ if (!domain)
+ {
+ debug_printf ("Unknown domain %W", dom);
+ return NULL;
+ }
+ }
+ }
+ /* If the domain returned by LookupAccountSid is not our machine
+ name, and if our machine is no domain member, we lose. We have
+ nobody to ask for the POSIX offset. */
+ else
+ {
+ debug_printf ("Unknown domain %W", dom);
+ return NULL;
+ }
+ /* Generate values. */
+ uid = posix_offset + sid_sub_auth_rid (sid);
+ gid = posix_offset + DOMAIN_GROUP_RID_USERS; /* Default. */
+
+ if (is_domain_account)
+ {
+ /* Use LDAP to fetch domain account infos. */
+ if (!ldap_open && !cldap.open (NULL))
+ break;
+ if (cldap.fetch_ad_account (sid, group))
+ {
+ PWCHAR val;
+ uint32_t id_val;
+
+ if (!group)
+ {
+ if ((id_val = cldap.get_primary_gid ()) != ILLEGAL_GID)
+ gid = posix_offset + id_val;
+ if ((val = cldap.get_user_name ())
+ && wcscmp (name, val))
+ user = wcscpy ((PWCHAR) alloca ((wcslen (val) + 1)
+ * sizeof (WCHAR)), val);
+ if ((val = cldap.get_gecos ()))
+ gecos = wcscpy ((PWCHAR) alloca ((wcslen (val) + 1)
+ * sizeof (WCHAR)), val);
+ if ((val = cldap.get_home ()))
+ home = wcscpy ((PWCHAR) alloca ((wcslen (val) + 1)
+ * sizeof (WCHAR)), val);
+ if ((val = cldap.get_shell ()))
+ shell = wcscpy ((PWCHAR) alloca ((wcslen (val) + 1)
+ * sizeof (WCHAR)), val);
+ /* Check and, if necessary, add unix<->windows
+ id mapping on the fly. */
+ id_val = cldap.get_unix_uid ();
+ if (id_val != ILLEGAL_UID
+ && ugid_cache.get_uid (id_val) == ILLEGAL_UID)
+ ugid_cache.add_uid (id_val, uid);
+ }
+ else
+ {
+ if ((val = cldap.get_group_name ())
+ && wcscmp (name, val))
+ user = wcscpy ((PWCHAR) alloca ((wcslen (val) + 1)
+ * sizeof (WCHAR)), val);
+ id_val = cldap.get_unix_gid ();
+ if (id_val != ILLEGAL_GID
+ && ugid_cache.get_gid (id_val) == ILLEGAL_GID)
+ ugid_cache.add_gid (id_val, uid);
+ }
+ }
+ }
+ /* Otherwise check account domain (local SAM).*/
+ else if (acc_type == SidTypeUser)
+ {
+ NET_API_STATUS nas;
+ PUSER_INFO_4 ui;
+
+ nas = NetUserGetInfo (domain, name, 4, (PBYTE *) &ui);
+ if (nas != NERR_Success)
+ debug_printf ("NetUserGetInfo(%W,%W) %u", domain, name, nas);
+ else
+ {
+ struct {
+ PCWSTR str;
+ size_t len;
+ PWCHAR *tgt;
+ } search[] = {
+ { L"name=\"", 6, &user },
+ { L"home=\"", 6, &home },
+ { L"shell=\"", 7, &shell }
+ };
+ PWCHAR s, e;
+
+ /* Fetch primary group. */
+ gid = posix_offset + ui->usri4_primary_group_id;
+ /* Local SAM accounts have only a handful attributes
+ available to home users. Therefore, fetch different
+ Cygwin user name, Cygwin home dir, and Cygwin login
+ shell from the "Description" field in XML short
+ style. */
+ if ((s = wcsstr (ui->usri4_comment, L"<cygwin "))
+ && (e = wcsstr (s + 8, L"/>")))
+ {
+ s += 8;
+ *e = L'\0';
+ while (*s)
+ {
+ while (*s == L' ')
+ ++s;
+ for (size_t i = 0;
+ i < sizeof search / sizeof search[0];
+ ++i)
+ if (!wcsncmp (s, search[i].str, search[i].len))
+ {
+ s += search[i].len;
+ if ((e = wcschr (s, L'"'))
+ && (i > 0 || wcsncmp (name, s, e - s)))
+ {
+ *search[i].tgt =
+ (PWCHAR) alloca ((e - s + 1)
+ * sizeof (WCHAR));
+ *wcpncpy (*search[i].tgt, s, e - s) = L'\0';
+ s = e + 1;
+ }
+ else
+ {
+ *s = L'\0';
+ break;
+ }
+ }
+ }
+ }
+ NetApiBufferFree (ui);
+ }
+ }
+ else /* SidTypeGroup */
+ {
+ NET_API_STATUS nas;
+ PGROUP_INFO_3 gi;
+
+ nas = NetGroupGetInfo (domain, name, 3, (PBYTE *) &gi);
+ if (nas != NERR_Success)
+ debug_printf ("NetGroupGetInfo(%W,%W) %u", domain, name, nas);
+ else
+ {
+ PWCHAR s, e;
+
+ /* Fetch different Cygwin group name from description. */
+ if ((s = wcsstr (gi->grpi3_comment, L"<cygwin "))
+ && (e = wcsstr (s + 8, L"/>")))
+ {
+ s += 8;
+ *e = L'\0';
+ while (*s)
+ {
+ while (*s == L' ')
+ ++s;
+ if (!wcsncmp (s, L"name=\"", 6))
+ {
+ s += 6;
+ if ((e = wcschr (s, L'"')))
+ {
+ *wcpncpy (name = namebuf, s, e - s) = L'\0';
+ s = e + 1;
+ }
+ else
+ break;
+ }
+ }
+ }
+ NetApiBufferFree (gi);
+ }
+ }
+ break;
+ case SidTypeAlias:
+ case SidTypeWellKnownGroup:
+ name_style = (cygheap->pg.nss_prefix_always ()) ? fully_qualified
+ : plus_prepended;
+#ifdef INTERIX_COMPATIBLE
+ if (sid_id_auth (sid) == 5 /* SECURITY_NT_AUTHORITY */
+ && sid_sub_auth_count (sid) > 1)
+ {
+ uid = 0x1000 * sid_sub_auth (sid, 0)
+ + (sid_sub_auth_rid (sid) & 0xffff);
+ if (sid_sub_auth (sid, 0) > SECURITY_BUILTIN_DOMAIN_RID)
+ name_style = fully_qualified;
+ }
+ else
+ uid = 0x10000 + 0x100 * sid_id_auth (sid)
+ + (sid_sub_auth_rid (sid) & 0xff);
+#else
+ if (sid_id_auth (sid) != 5 /* SECURITY_NT_AUTHORITY */)
+ uid = 0x10000 + 0x100 * sid_id_auth (sid)
+ + (sid_sub_auth_rid (sid) & 0xff);
+ else if (sid_sub_auth (sid, 0) < SECURITY_PACKAGE_BASE_RID)
+ uid = sid_sub_auth_rid (sid) & 0x7ff;
+ else
+ {
+ uid = 0x1000 * sid_sub_auth (sid, 0)
+ + (sid_sub_auth_rid (sid) & 0xffff);
+ //name_style = fully_qualified;
+ }
+#endif
+ /* Special case for "Everyone". We don't want to return Everyone
+ as user or group. Ever. */
+ if (uid == 0x10100) /* Computed from S-1-1-0. */
+ return NULL;
+ break;
+ case SidTypeLabel:
+ uid = 0x60000 + sid_sub_auth_rid (sid);
+ name_style = (cygheap->pg.nss_prefix_always ()) ? fully_qualified
+ : plus_prepended;
+ break;
+ default:
+ return NULL;
+ }
+ }
+ else if (sid_id_auth (sid) == 5 /* SECURITY_NT_AUTHORITY */
+ && sid_sub_auth (sid, 0) == SECURITY_LOGON_IDS_RID)
+ {
+ /* Logon ID. Mine or other? */
+ get_logon_sid ();
+ if (PSID (logon_sid) == NO_SID)
+ return NULL;
+ if (RtlEqualSid (sid, logon_sid))
+ {
+ uid = 0xfff;
+ wcpcpy (name = namebuf, L"CurrentSession");
+ }
+ else
+ {
+ uid = 0xffe;
+ wcpcpy (name = namebuf, L"OtherSession");
+ }
+ }
+ else if (sid_id_auth (sid) == 18)
+ {
+ /* Authentication assertion SIDs.
+
+ Available when using a 2012R2 DC, but not supported by
+ LookupAccountXXX on pre Windows 8/2012 machines */
+ uid = 0x11200 + sid_sub_auth_rid (sid);
+ wcpcpy (name = namebuf, sid_sub_auth_rid (sid) == 1
+ ? (PWCHAR) L"Authentication authority asserted identity"
+ : (PWCHAR) L"Service asserted identity");
+ name_style = plus_prepended;
+ }
+ else if (sid_id_auth (sid) == 22)
+ {
+ /* Samba UNIX Users/Groups
+
+ This *might* colide with a posix_offset of some trusted domain.
+ It's just very unlikely. */
+ uid = MAP_UNIX_TO_CYGWIN_ID (sid_sub_auth_rid (sid));
+ /* Unfortunately we have no access to the file server from here,
+ so we can't generate correct user names. */
+ p = wcpcpy (dom, L"UNIX_");
+ wcpcpy (p, sid_sub_auth (sid, 0) == 1 ? L"User" : L"Group");
+ __small_swprintf (name = namebuf, L"%d", uid & UNIX_POSIX_MASK);
+ name_style = fully_qualified;
+ }
+ else
+ {
+ wcpcpy (dom, L"Unknown");
+ wcpcpy (name = namebuf, group ? L"Group" : L"User");
+ name_style = fully_qualified;
+ }
+
+ tmp_pathbuf tp;
+ PWCHAR linebuf = tp.w_get ();
+ char *line = NULL;
+
+ WCHAR posix_name[UNLEN + 1 + DNLEN + 1];
+ p = posix_name;
+ if (gid == ILLEGAL_GID)
+ gid = uid;
+ if (name_style >= fully_qualified)
+ p = wcpcpy (p, user ? group ? L"Posix_Group" : L"Posix_User" : dom);
+ if (name_style >= plus_prepended)
+ p = wcpcpy (p, cygheap->pg.nss_separator ());
+ wcpcpy (p, user ?: name);
+
+ if (group)
+ __small_swprintf (linebuf, L"%W:%W:%u:",
+ posix_name, sid.string (sidstr), uid);
+ else
+ __small_swprintf (linebuf, L"%W:*:%u:%u:%W%WU-%W\\%W,%W:%W%W:%W",
+ posix_name, uid, gid,
+ gecos ?: L"", gecos ? L"," : L"",
+ dom, name,
+ sid.string (sidstr),
+ home ? L"" : L"/home/", home ?: user ?: name,
+ shell ?: L"/bin/sh");
+ sys_wcstombs_alloc (&line, HEAP_BUF, linebuf);
+ debug_printf ("line: <%s>", line);
+ return line;
}