Source file
src/os/user/lookup_windows.go
1
2
3
4
5 package user
6
7 import (
8 "errors"
9 "fmt"
10 "internal/syscall/windows"
11 "internal/syscall/windows/registry"
12 "runtime"
13 "syscall"
14 "unsafe"
15 )
16
17 func isDomainJoined() (bool, error) {
18 var domain *uint16
19 var status uint32
20 err := syscall.NetGetJoinInformation(nil, &domain, &status)
21 if err != nil {
22 return false, err
23 }
24 syscall.NetApiBufferFree((*byte)(unsafe.Pointer(domain)))
25 return status == syscall.NetSetupDomainName, nil
26 }
27
28 func lookupFullNameDomain(domainAndUser string) (string, error) {
29 return syscall.TranslateAccountName(domainAndUser,
30 syscall.NameSamCompatible, syscall.NameDisplay, 50)
31 }
32
33 func lookupFullNameServer(servername, username string) (string, error) {
34 s, e := syscall.UTF16PtrFromString(servername)
35 if e != nil {
36 return "", e
37 }
38 u, e := syscall.UTF16PtrFromString(username)
39 if e != nil {
40 return "", e
41 }
42 var p *byte
43 e = syscall.NetUserGetInfo(s, u, 10, &p)
44 if e != nil {
45 return "", e
46 }
47 defer syscall.NetApiBufferFree(p)
48 i := (*syscall.UserInfo10)(unsafe.Pointer(p))
49 return windows.UTF16PtrToString(i.FullName), nil
50 }
51
52 func lookupFullName(domain, username, domainAndUser string) (string, error) {
53 joined, err := isDomainJoined()
54 if err == nil && joined {
55 name, err := lookupFullNameDomain(domainAndUser)
56 if err == nil {
57 return name, nil
58 }
59 }
60 name, err := lookupFullNameServer(domain, username)
61 if err == nil {
62 return name, nil
63 }
64
65
66
67 return username, nil
68 }
69
70
71
72 func getProfilesDirectory() (string, error) {
73 n := uint32(100)
74 for {
75 b := make([]uint16, n)
76 e := windows.GetProfilesDirectory(&b[0], &n)
77 if e == nil {
78 return syscall.UTF16ToString(b), nil
79 }
80 if e != syscall.ERROR_INSUFFICIENT_BUFFER {
81 return "", e
82 }
83 if n <= uint32(len(b)) {
84 return "", e
85 }
86 }
87 }
88
89 func isServiceAccount(sid *syscall.SID) bool {
90 if !windows.IsValidSid(sid) {
91
92
93 return false
94 }
95
96
97
98
99
100
101 if windows.GetSidSubAuthorityCount(sid) != windows.SID_REVISION ||
102 windows.GetSidIdentifierAuthority(sid) != windows.SECURITY_NT_AUTHORITY {
103 return false
104 }
105 switch windows.GetSidSubAuthority(sid, 0) {
106 case windows.SECURITY_LOCAL_SYSTEM_RID,
107 windows.SECURITY_LOCAL_SERVICE_RID,
108 windows.SECURITY_NETWORK_SERVICE_RID:
109 return true
110 }
111 return false
112 }
113
114 func isValidUserAccountType(sid *syscall.SID, sidType uint32) bool {
115 switch sidType {
116 case syscall.SidTypeUser:
117 return true
118 case syscall.SidTypeWellKnownGroup:
119 return isServiceAccount(sid)
120 }
121 return false
122 }
123
124 func isValidGroupAccountType(sidType uint32) bool {
125 switch sidType {
126 case syscall.SidTypeGroup:
127 return true
128 case syscall.SidTypeWellKnownGroup:
129
130
131
132
133 return true
134 case syscall.SidTypeAlias:
135
136
137
138
139
140
141 return true
142 }
143 return false
144 }
145
146
147 func lookupUsernameAndDomain(usid *syscall.SID) (username, domain string, sidType uint32, e error) {
148 username, domain, sidType, e = usid.LookupAccount("")
149 if e != nil {
150 return "", "", 0, e
151 }
152 if !isValidUserAccountType(usid, sidType) {
153 return "", "", 0, fmt.Errorf("user: should be user account type, not %d", sidType)
154 }
155 return username, domain, sidType, nil
156 }
157
158
159 func findHomeDirInRegistry(uid string) (dir string, e error) {
160 k, e := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\`+uid, registry.QUERY_VALUE)
161 if e != nil {
162 return "", e
163 }
164 defer k.Close()
165 dir, _, e = k.GetStringValue("ProfileImagePath")
166 if e != nil {
167 return "", e
168 }
169 return dir, nil
170 }
171
172
173 func lookupGroupName(groupname string) (string, error) {
174 sid, _, t, e := syscall.LookupSID("", groupname)
175 if e != nil {
176 if errors.Is(e, windows.ERROR_NONE_MAPPED) {
177 return "", fmt.Errorf("%w: %w", UnknownGroupError(groupname), e)
178 }
179 return "", e
180 }
181 if !isValidGroupAccountType(t) {
182 return "", fmt.Errorf("lookupGroupName: should be group account type, not %d", t)
183 }
184 return sid.String()
185 }
186
187
188
189 func listGroupsForUsernameAndDomain(username, domain string) ([]string, error) {
190
191 var query string
192 joined, err := isDomainJoined()
193 if err == nil && joined && len(domain) != 0 {
194 query = domain + `\` + username
195 } else {
196 query = username
197 }
198 q, err := syscall.UTF16PtrFromString(query)
199 if err != nil {
200 return nil, err
201 }
202 var p0 *byte
203 var entriesRead, totalEntries uint32
204
205
206
207
208 err = windows.NetUserGetLocalGroups(nil, q, 0, windows.LG_INCLUDE_INDIRECT, &p0, windows.MAX_PREFERRED_LENGTH, &entriesRead, &totalEntries)
209 if err != nil {
210 return nil, err
211 }
212 defer syscall.NetApiBufferFree(p0)
213 if entriesRead == 0 {
214 return nil, nil
215 }
216 entries := (*[1024]windows.LocalGroupUserInfo0)(unsafe.Pointer(p0))[:entriesRead:entriesRead]
217 var sids []string
218 for _, entry := range entries {
219 if entry.Name == nil {
220 continue
221 }
222 sid, err := lookupGroupName(windows.UTF16PtrToString(entry.Name))
223 if err != nil {
224 return nil, err
225 }
226 sids = append(sids, sid)
227 }
228 return sids, nil
229 }
230
231 func newUser(uid, gid, dir, username, domain string) (*User, error) {
232 domainAndUser := domain + `\` + username
233 name, e := lookupFullName(domain, username, domainAndUser)
234 if e != nil {
235 return nil, e
236 }
237 u := &User{
238 Uid: uid,
239 Gid: gid,
240 Username: domainAndUser,
241 Name: name,
242 HomeDir: dir,
243 }
244 return u, nil
245 }
246
247 var (
248
249
250 userBuffer = 0
251 groupBuffer = 0
252 )
253
254 func current() (*User, error) {
255
256
257
258 var usr *User
259 err := runAsProcessOwner(func() error {
260 t, e := syscall.OpenCurrentProcessToken()
261 if e != nil {
262 return e
263 }
264 defer t.Close()
265 u, e := t.GetTokenUser()
266 if e != nil {
267 return e
268 }
269 pg, e := t.GetTokenPrimaryGroup()
270 if e != nil {
271 return e
272 }
273 uid, e := u.User.Sid.String()
274 if e != nil {
275 return e
276 }
277 gid, e := pg.PrimaryGroup.String()
278 if e != nil {
279 return e
280 }
281 dir, e := t.GetUserProfileDirectory()
282 if e != nil {
283 return e
284 }
285 username, e := windows.GetUserName(syscall.NameSamCompatible)
286 if e != nil {
287 return e
288 }
289 displayName, e := windows.GetUserName(syscall.NameDisplay)
290 if e != nil {
291
292
293 displayName = username
294 }
295 usr = &User{
296 Uid: uid,
297 Gid: gid,
298 Username: username,
299 Name: displayName,
300 HomeDir: dir,
301 }
302 return nil
303 })
304 return usr, err
305 }
306
307
308
309
310 func runAsProcessOwner(f func() error) error {
311 var impersonationRollbackErr error
312 runtime.LockOSThread()
313 defer func() {
314
315
316
317 if impersonationRollbackErr != nil {
318 println("os/user: failed to revert to previous token:", impersonationRollbackErr.Error())
319 runtime.Goexit()
320 } else {
321 runtime.UnlockOSThread()
322 }
323 }()
324 prevToken, isProcessToken, err := getCurrentToken()
325 if err != nil {
326 return fmt.Errorf("os/user: failed to get current token: %w", err)
327 }
328 defer prevToken.Close()
329 if !isProcessToken {
330 if err = windows.RevertToSelf(); err != nil {
331 return fmt.Errorf("os/user: failed to revert to self: %w", err)
332 }
333 defer func() {
334 impersonationRollbackErr = windows.ImpersonateLoggedOnUser(prevToken)
335 }()
336 }
337 return f()
338 }
339
340
341
342 func getCurrentToken() (t syscall.Token, isProcessToken bool, err error) {
343 thread, _ := windows.GetCurrentThread()
344
345 err = windows.OpenThreadToken(thread, syscall.TOKEN_QUERY|syscall.TOKEN_DUPLICATE|syscall.TOKEN_IMPERSONATE, true, &t)
346 if errors.Is(err, windows.ERROR_NO_TOKEN) {
347
348 isProcessToken = true
349 t, err = syscall.OpenCurrentProcessToken()
350 }
351 return t, isProcessToken, err
352 }
353
354
355
356
357 func lookupUserPrimaryGroup(username, domain string) (string, error) {
358
359 sid, _, t, e := syscall.LookupSID("", domain)
360 if e != nil {
361 return "", e
362 }
363 if t != syscall.SidTypeDomain {
364 return "", fmt.Errorf("lookupUserPrimaryGroup: should be domain account type, not %d", t)
365 }
366 domainRID, e := sid.String()
367 if e != nil {
368 return "", e
369 }
370
371
372
373
374
375
376
377
378
379
380
381
382
383 joined, err := isDomainJoined()
384 if err == nil && joined {
385 return domainRID + "-513", nil
386 }
387
388
389
390
391
392
393 u, e := syscall.UTF16PtrFromString(username)
394 if e != nil {
395 return "", e
396 }
397 d, e := syscall.UTF16PtrFromString(domain)
398 if e != nil {
399 return "", e
400 }
401 var p *byte
402 e = syscall.NetUserGetInfo(d, u, 4, &p)
403 if e != nil {
404 return "", e
405 }
406 defer syscall.NetApiBufferFree(p)
407 i := (*windows.UserInfo4)(unsafe.Pointer(p))
408 return fmt.Sprintf("%s-%d", domainRID, i.PrimaryGroupID), nil
409 }
410
411 func newUserFromSid(usid *syscall.SID) (*User, error) {
412 username, domain, sidType, e := lookupUsernameAndDomain(usid)
413 if e != nil {
414 return nil, e
415 }
416 uid, e := usid.String()
417 if e != nil {
418 return nil, e
419 }
420 var gid string
421 if sidType == syscall.SidTypeWellKnownGroup {
422
423
424
425
426 gid = uid
427 } else {
428 gid, e = lookupUserPrimaryGroup(username, domain)
429 if e != nil {
430 return nil, e
431 }
432 }
433
434
435
436
437
438
439
440
441
442 dir, e := findHomeDirInRegistry(uid)
443 if e != nil {
444
445
446
447
448 dir, e = getProfilesDirectory()
449 if e != nil {
450 return nil, e
451 }
452 dir += `\` + username
453 }
454 return newUser(uid, gid, dir, username, domain)
455 }
456
457 func lookupUser(username string) (*User, error) {
458 sid, _, t, e := syscall.LookupSID("", username)
459 if e != nil {
460 if errors.Is(e, windows.ERROR_NONE_MAPPED) {
461 return nil, fmt.Errorf("%w: %w", UnknownUserError(username), e)
462 }
463 return nil, e
464 }
465 if !isValidUserAccountType(sid, t) {
466 return nil, fmt.Errorf("user: should be user account type, not %d", t)
467 }
468 return newUserFromSid(sid)
469 }
470
471 func lookupUserId(uid string) (*User, error) {
472 sid, e := syscall.StringToSid(uid)
473 if e != nil {
474 return nil, e
475 }
476 return newUserFromSid(sid)
477 }
478
479 func lookupGroup(groupname string) (*Group, error) {
480 sid, err := lookupGroupName(groupname)
481 if err != nil {
482 return nil, err
483 }
484 return &Group{Name: groupname, Gid: sid}, nil
485 }
486
487 func lookupGroupId(gid string) (*Group, error) {
488 sid, err := syscall.StringToSid(gid)
489 if err != nil {
490 return nil, err
491 }
492 groupname, _, t, err := sid.LookupAccount("")
493 if err != nil {
494 return nil, err
495 }
496 if !isValidGroupAccountType(t) {
497 return nil, fmt.Errorf("lookupGroupId: should be group account type, not %d", t)
498 }
499 return &Group{Name: groupname, Gid: gid}, nil
500 }
501
502 func listGroups(user *User) ([]string, error) {
503 var sids []string
504 if u, err := Current(); err == nil && u.Uid == user.Uid {
505
506
507 err := runAsProcessOwner(func() error {
508 t, err := syscall.OpenCurrentProcessToken()
509 if err != nil {
510 return err
511 }
512 defer t.Close()
513 groups, err := windows.GetTokenGroups(t)
514 if err != nil {
515 return err
516 }
517 for _, g := range groups.AllGroups() {
518 sid, err := g.Sid.String()
519 if err != nil {
520 return err
521 }
522 sids = append(sids, sid)
523 }
524 return nil
525 })
526 if err != nil {
527 return nil, err
528 }
529 } else {
530 sid, err := syscall.StringToSid(user.Uid)
531 if err != nil {
532 return nil, err
533 }
534 username, domain, _, err := lookupUsernameAndDomain(sid)
535 if err != nil {
536 return nil, err
537 }
538 sids, err = listGroupsForUsernameAndDomain(username, domain)
539 if err != nil {
540 return nil, err
541 }
542 }
543
544
545 for _, sid := range sids {
546 if sid == user.Gid {
547 return sids, nil
548 }
549 }
550 return append(sids, user.Gid), nil
551 }
552
View as plain text