...

Source file src/os/user/lookup_windows.go

     1	// Copyright 2012 The Go Authors. All rights reserved.
     2	// Use of this source code is governed by a BSD-style
     3	// license that can be found in the LICENSE file.
     4	
     5	package user
     6	
     7	import (
     8		"fmt"
     9		"internal/syscall/windows"
    10		"internal/syscall/windows/registry"
    11		"syscall"
    12		"unsafe"
    13	)
    14	
    15	func isDomainJoined() (bool, error) {
    16		var domain *uint16
    17		var status uint32
    18		err := syscall.NetGetJoinInformation(nil, &domain, &status)
    19		if err != nil {
    20			return false, err
    21		}
    22		syscall.NetApiBufferFree((*byte)(unsafe.Pointer(domain)))
    23		return status == syscall.NetSetupDomainName, nil
    24	}
    25	
    26	func lookupFullNameDomain(domainAndUser string) (string, error) {
    27		return syscall.TranslateAccountName(domainAndUser,
    28			syscall.NameSamCompatible, syscall.NameDisplay, 50)
    29	}
    30	
    31	func lookupFullNameServer(servername, username string) (string, error) {
    32		s, e := syscall.UTF16PtrFromString(servername)
    33		if e != nil {
    34			return "", e
    35		}
    36		u, e := syscall.UTF16PtrFromString(username)
    37		if e != nil {
    38			return "", e
    39		}
    40		var p *byte
    41		e = syscall.NetUserGetInfo(s, u, 10, &p)
    42		if e != nil {
    43			return "", e
    44		}
    45		defer syscall.NetApiBufferFree(p)
    46		i := (*syscall.UserInfo10)(unsafe.Pointer(p))
    47		if i.FullName == nil {
    48			return "", nil
    49		}
    50		name := syscall.UTF16ToString((*[1024]uint16)(unsafe.Pointer(i.FullName))[:])
    51		return name, nil
    52	}
    53	
    54	func lookupFullName(domain, username, domainAndUser string) (string, error) {
    55		joined, err := isDomainJoined()
    56		if err == nil && joined {
    57			name, err := lookupFullNameDomain(domainAndUser)
    58			if err == nil {
    59				return name, nil
    60			}
    61		}
    62		name, err := lookupFullNameServer(domain, username)
    63		if err == nil {
    64			return name, nil
    65		}
    66		// domain worked neither as a domain nor as a server
    67		// could be domain server unavailable
    68		// pretend username is fullname
    69		return username, nil
    70	}
    71	
    72	// getProfilesDirectory retrieves the path to the root directory
    73	// where user profiles are stored.
    74	func getProfilesDirectory() (string, error) {
    75		n := uint32(100)
    76		for {
    77			b := make([]uint16, n)
    78			e := windows.GetProfilesDirectory(&b[0], &n)
    79			if e == nil {
    80				return syscall.UTF16ToString(b), nil
    81			}
    82			if e != syscall.ERROR_INSUFFICIENT_BUFFER {
    83				return "", e
    84			}
    85			if n <= uint32(len(b)) {
    86				return "", e
    87			}
    88		}
    89	}
    90	
    91	// lookupUsernameAndDomain obtains the username and domain for usid.
    92	func lookupUsernameAndDomain(usid *syscall.SID) (username, domain string, e error) {
    93		username, domain, t, e := usid.LookupAccount("")
    94		if e != nil {
    95			return "", "", e
    96		}
    97		if t != syscall.SidTypeUser {
    98			return "", "", fmt.Errorf("user: should be user account type, not %d", t)
    99		}
   100		return username, domain, nil
   101	}
   102	
   103	// findHomeDirInRegistry finds the user home path based on the uid.
   104	func findHomeDirInRegistry(uid string) (dir string, e error) {
   105		k, e := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\`+uid, registry.QUERY_VALUE)
   106		if e != nil {
   107			return "", e
   108		}
   109		defer k.Close()
   110		dir, _, e = k.GetStringValue("ProfileImagePath")
   111		if e != nil {
   112			return "", e
   113		}
   114		return dir, nil
   115	}
   116	
   117	// lookupGroupName accepts the name of a group and retrieves the group SID.
   118	func lookupGroupName(groupname string) (string, error) {
   119		sid, _, t, e := syscall.LookupSID("", groupname)
   120		if e != nil {
   121			return "", e
   122		}
   123		// https://msdn.microsoft.com/en-us/library/cc245478.aspx#gt_0387e636-5654-4910-9519-1f8326cf5ec0
   124		// SidTypeAlias should also be treated as a group type next to SidTypeGroup
   125		// and SidTypeWellKnownGroup:
   126		// "alias object -> resource group: A group object..."
   127		//
   128		// Tests show that "Administrators" can be considered of type SidTypeAlias.
   129		if t != syscall.SidTypeGroup && t != syscall.SidTypeWellKnownGroup && t != syscall.SidTypeAlias {
   130			return "", fmt.Errorf("lookupGroupName: should be group account type, not %d", t)
   131		}
   132		return sid.String()
   133	}
   134	
   135	// listGroupsForUsernameAndDomain accepts username and domain and retrieves
   136	// a SID list of the local groups where this user is a member.
   137	func listGroupsForUsernameAndDomain(username, domain string) ([]string, error) {
   138		// Check if both the domain name and user should be used.
   139		var query string
   140		joined, err := isDomainJoined()
   141		if err == nil && joined && len(domain) != 0 {
   142			query = domain + `\` + username
   143		} else {
   144			query = username
   145		}
   146		q, err := syscall.UTF16PtrFromString(query)
   147		if err != nil {
   148			return nil, err
   149		}
   150		var p0 *byte
   151		var entriesRead, totalEntries uint32
   152		// https://msdn.microsoft.com/en-us/library/windows/desktop/aa370655(v=vs.85).aspx
   153		// NetUserGetLocalGroups() would return a list of LocalGroupUserInfo0
   154		// elements which hold the names of local groups where the user participates.
   155		// The list does not follow any sorting order.
   156		//
   157		// If no groups can be found for this user, NetUserGetLocalGroups() should
   158		// always return the SID of a single group called "None", which
   159		// also happens to be the primary group for the local user.
   160		err = windows.NetUserGetLocalGroups(nil, q, 0, windows.LG_INCLUDE_INDIRECT, &p0, windows.MAX_PREFERRED_LENGTH, &entriesRead, &totalEntries)
   161		if err != nil {
   162			return nil, err
   163		}
   164		defer syscall.NetApiBufferFree(p0)
   165		if entriesRead == 0 {
   166			return nil, fmt.Errorf("listGroupsForUsernameAndDomain: NetUserGetLocalGroups() returned an empty list for domain: %s, username: %s", domain, username)
   167		}
   168		entries := (*[1024]windows.LocalGroupUserInfo0)(unsafe.Pointer(p0))[:entriesRead]
   169		var sids []string
   170		for _, entry := range entries {
   171			if entry.Name == nil {
   172				continue
   173			}
   174			name := syscall.UTF16ToString((*[1024]uint16)(unsafe.Pointer(entry.Name))[:])
   175			sid, err := lookupGroupName(name)
   176			if err != nil {
   177				return nil, err
   178			}
   179			sids = append(sids, sid)
   180		}
   181		return sids, nil
   182	}
   183	
   184	func newUser(uid, gid, dir, username, domain string) (*User, error) {
   185		domainAndUser := domain + `\` + username
   186		name, e := lookupFullName(domain, username, domainAndUser)
   187		if e != nil {
   188			return nil, e
   189		}
   190		u := &User{
   191			Uid:      uid,
   192			Gid:      gid,
   193			Username: domainAndUser,
   194			Name:     name,
   195			HomeDir:  dir,
   196		}
   197		return u, nil
   198	}
   199	
   200	func current() (*User, error) {
   201		t, e := syscall.OpenCurrentProcessToken()
   202		if e != nil {
   203			return nil, e
   204		}
   205		defer t.Close()
   206		u, e := t.GetTokenUser()
   207		if e != nil {
   208			return nil, e
   209		}
   210		pg, e := t.GetTokenPrimaryGroup()
   211		if e != nil {
   212			return nil, e
   213		}
   214		uid, e := u.User.Sid.String()
   215		if e != nil {
   216			return nil, e
   217		}
   218		gid, e := pg.PrimaryGroup.String()
   219		if e != nil {
   220			return nil, e
   221		}
   222		dir, e := t.GetUserProfileDirectory()
   223		if e != nil {
   224			return nil, e
   225		}
   226		username, domain, e := lookupUsernameAndDomain(u.User.Sid)
   227		if e != nil {
   228			return nil, e
   229		}
   230		return newUser(uid, gid, dir, username, domain)
   231	}
   232	
   233	// lookupUserPrimaryGroup obtains the primary group SID for a user using this method:
   234	// https://support.microsoft.com/en-us/help/297951/how-to-use-the-primarygroupid-attribute-to-find-the-primary-group-for
   235	// The method follows this formula: domainRID + "-" + primaryGroupRID
   236	func lookupUserPrimaryGroup(username, domain string) (string, error) {
   237		// get the domain RID
   238		sid, _, t, e := syscall.LookupSID("", domain)
   239		if e != nil {
   240			return "", e
   241		}
   242		if t != syscall.SidTypeDomain {
   243			return "", fmt.Errorf("lookupUserPrimaryGroup: should be domain account type, not %d", t)
   244		}
   245		domainRID, e := sid.String()
   246		if e != nil {
   247			return "", e
   248		}
   249		// If the user has joined a domain use the RID of the default primary group
   250		// called "Domain Users":
   251		// https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems
   252		// SID: S-1-5-21domain-513
   253		//
   254		// The correct way to obtain the primary group of a domain user is
   255		// probing the user primaryGroupID attribute in the server Active Directory:
   256		// https://msdn.microsoft.com/en-us/library/ms679375(v=vs.85).aspx
   257		//
   258		// Note that the primary group of domain users should not be modified
   259		// on Windows for performance reasons, even if it's possible to do that.
   260		// The .NET Developer's Guide to Directory Services Programming - Page 409
   261		// https://books.google.bg/books?id=kGApqjobEfsC&lpg=PA410&ots=p7oo-eOQL7&dq=primary%20group%20RID&hl=bg&pg=PA409#v=onepage&q&f=false
   262		joined, err := isDomainJoined()
   263		if err == nil && joined {
   264			return domainRID + "-513", nil
   265		}
   266		// For non-domain users call NetUserGetInfo() with level 4, which
   267		// in this case would not have any network overhead.
   268		// The primary group should not change from RID 513 here either
   269		// but the group will be called "None" instead:
   270		// https://www.adampalmer.me/iodigitalsec/2013/08/10/windows-null-session-enumeration/
   271		// "Group 'None' (RID: 513)"
   272		u, e := syscall.UTF16PtrFromString(username)
   273		if e != nil {
   274			return "", e
   275		}
   276		d, e := syscall.UTF16PtrFromString(domain)
   277		if e != nil {
   278			return "", e
   279		}
   280		var p *byte
   281		e = syscall.NetUserGetInfo(d, u, 4, &p)
   282		if e != nil {
   283			return "", e
   284		}
   285		defer syscall.NetApiBufferFree(p)
   286		i := (*windows.UserInfo4)(unsafe.Pointer(p))
   287		return fmt.Sprintf("%s-%d", domainRID, i.PrimaryGroupID), nil
   288	}
   289	
   290	func newUserFromSid(usid *syscall.SID) (*User, error) {
   291		username, domain, e := lookupUsernameAndDomain(usid)
   292		if e != nil {
   293			return nil, e
   294		}
   295		gid, e := lookupUserPrimaryGroup(username, domain)
   296		if e != nil {
   297			return nil, e
   298		}
   299		uid, e := usid.String()
   300		if e != nil {
   301			return nil, e
   302		}
   303		// If this user has logged in at least once their home path should be stored
   304		// in the registry under the specified SID. References:
   305		// https://social.technet.microsoft.com/wiki/contents/articles/13895.how-to-remove-a-corrupted-user-profile-from-the-registry.aspx
   306		// https://support.asperasoft.com/hc/en-us/articles/216127438-How-to-delete-Windows-user-profiles
   307		//
   308		// The registry is the most reliable way to find the home path as the user
   309		// might have decided to move it outside of the default location,
   310		// (e.g. C:\users). Reference:
   311		// https://answers.microsoft.com/en-us/windows/forum/windows_7-security/how-do-i-set-a-home-directory-outside-cusers-for-a/aed68262-1bf4-4a4d-93dc-7495193a440f
   312		dir, e := findHomeDirInRegistry(uid)
   313		if e != nil {
   314			// If the home path does not exist in the registry, the user might
   315			// have not logged in yet; fall back to using getProfilesDirectory().
   316			// Find the username based on a SID and append that to the result of
   317			// getProfilesDirectory(). The domain is not relevant here.
   318			dir, e = getProfilesDirectory()
   319			if e != nil {
   320				return nil, e
   321			}
   322			dir += `\` + username
   323		}
   324		return newUser(uid, gid, dir, username, domain)
   325	}
   326	
   327	func lookupUser(username string) (*User, error) {
   328		sid, _, t, e := syscall.LookupSID("", username)
   329		if e != nil {
   330			return nil, e
   331		}
   332		if t != syscall.SidTypeUser {
   333			return nil, fmt.Errorf("user: should be user account type, not %d", t)
   334		}
   335		return newUserFromSid(sid)
   336	}
   337	
   338	func lookupUserId(uid string) (*User, error) {
   339		sid, e := syscall.StringToSid(uid)
   340		if e != nil {
   341			return nil, e
   342		}
   343		return newUserFromSid(sid)
   344	}
   345	
   346	func lookupGroup(groupname string) (*Group, error) {
   347		sid, err := lookupGroupName(groupname)
   348		if err != nil {
   349			return nil, err
   350		}
   351		return &Group{Name: groupname, Gid: sid}, nil
   352	}
   353	
   354	func lookupGroupId(gid string) (*Group, error) {
   355		sid, err := syscall.StringToSid(gid)
   356		if err != nil {
   357			return nil, err
   358		}
   359		groupname, _, t, err := sid.LookupAccount("")
   360		if err != nil {
   361			return nil, err
   362		}
   363		if t != syscall.SidTypeGroup && t != syscall.SidTypeWellKnownGroup && t != syscall.SidTypeAlias {
   364			return nil, fmt.Errorf("lookupGroupId: should be group account type, not %d", t)
   365		}
   366		return &Group{Name: groupname, Gid: gid}, nil
   367	}
   368	
   369	func listGroups(user *User) ([]string, error) {
   370		sid, err := syscall.StringToSid(user.Uid)
   371		if err != nil {
   372			return nil, err
   373		}
   374		username, domain, err := lookupUsernameAndDomain(sid)
   375		if err != nil {
   376			return nil, err
   377		}
   378		sids, err := listGroupsForUsernameAndDomain(username, domain)
   379		if err != nil {
   380			return nil, err
   381		}
   382		// Add the primary group of the user to the list if it is not already there.
   383		// This is done only to comply with the POSIX concept of a primary group.
   384		for _, sid := range sids {
   385			if sid == user.Gid {
   386				return sids, nil
   387			}
   388		}
   389		return append(sids, user.Gid), nil
   390	}
   391	

View as plain text