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