lookup_windows.go 12 KB

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