diff options
Diffstat (limited to 'winsup/cygwin/uinfo.cc')
-rw-r--r-- | winsup/cygwin/uinfo.cc | 1194 |
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; } |