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