util.odin 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. // +build windows
  2. package sys_windows
  3. import "core:strings"
  4. import "core:sys/win32"
  5. LOWORD :: #force_inline proc "contextless" (x: DWORD) -> WORD {
  6. return WORD(x & 0xffff)
  7. }
  8. HIWORD :: #force_inline proc "contextless" (x: DWORD) -> WORD {
  9. return WORD(x >> 16)
  10. }
  11. utf8_to_utf16 :: proc(s: string, allocator := context.temp_allocator) -> []u16 {
  12. if len(s) < 1 {
  13. return nil
  14. }
  15. b := transmute([]byte)s
  16. cstr := raw_data(b)
  17. n := MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, cstr, i32(len(s)), nil, 0)
  18. if n == 0 {
  19. return nil
  20. }
  21. text := make([]u16, n+1, allocator)
  22. n1 := MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, cstr, i32(len(s)), raw_data(text), n)
  23. if n1 == 0 {
  24. delete(text, allocator)
  25. return nil
  26. }
  27. text[n] = 0
  28. for n >= 1 && text[n-1] == 0 {
  29. n -= 1
  30. }
  31. return text[:n]
  32. }
  33. utf8_to_wstring :: proc(s: string, allocator := context.temp_allocator) -> wstring {
  34. if res := utf8_to_utf16(s, allocator); res != nil {
  35. return &res[0]
  36. }
  37. return nil
  38. }
  39. wstring_to_utf8 :: proc(s: wstring, N: int, allocator := context.temp_allocator) -> string {
  40. if N <= 0 {
  41. return ""
  42. }
  43. n := WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, s, i32(N), nil, 0, nil, nil)
  44. if n == 0 {
  45. return ""
  46. }
  47. // If N == -1 the call to WideCharToMultiByte assume the wide string is null terminated
  48. // and will scan it to find the first null terminated character. The resulting string will
  49. // also null terminated.
  50. // If N != -1 it assumes the wide string is not null terminated and the resulting string
  51. // will not be null terminated, we therefore have to force it to be null terminated manually.
  52. text := make([]byte, n+1 if N != -1 else n, allocator)
  53. n1 := WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, s, i32(N), raw_data(text), n, nil, nil)
  54. if n1 == 0 {
  55. delete(text, allocator)
  56. return ""
  57. }
  58. for i in 0..<n {
  59. if text[i] == 0 {
  60. n = i
  61. break
  62. }
  63. }
  64. return string(text[:n])
  65. }
  66. utf16_to_utf8 :: proc(s: []u16, allocator := context.temp_allocator) -> string {
  67. if len(s) == 0 {
  68. return ""
  69. }
  70. return wstring_to_utf8(raw_data(s), len(s), allocator)
  71. }
  72. // AdvAPI32, NetAPI32 and UserENV helpers.
  73. allowed_username :: proc(username: string) -> bool {
  74. /*
  75. User account names are limited to 20 characters and group names are limited to 256 characters.
  76. In addition, account names cannot be terminated by a period and they cannot include commas or any of the following printable characters:
  77. ", /, , [, ], :, |, <, >, +, =, ;, ?, *. Names also cannot include characters in the range 1-31, which are nonprintable.
  78. */
  79. _DISALLOWED :: "\"/ []:|<>+=;?*,"
  80. if len(username) > LM20_UNLEN || len(username) == 0 {
  81. return false
  82. }
  83. if username[len(username)-1] == '.' {
  84. return false
  85. }
  86. for r in username {
  87. if r > 0 && r < 32 {
  88. return false
  89. }
  90. }
  91. if strings.contains_any(username, _DISALLOWED) {
  92. return false
  93. }
  94. return true
  95. }
  96. // Returns .Success on success.
  97. _add_user :: proc(servername: string, username: string, password: string) -> (ok: NET_API_STATUS) {
  98. servername_w: wstring
  99. username_w: []u16
  100. password_w: []u16
  101. if len(servername) == 0 {
  102. // Create account on this computer
  103. servername_w = nil
  104. } else {
  105. server := utf8_to_utf16(servername, context.temp_allocator)
  106. servername_w = &server[0]
  107. }
  108. if len(username) == 0 || len(username) > LM20_UNLEN {
  109. return .BadUsername
  110. }
  111. if !allowed_username(username) {
  112. return .BadUsername
  113. }
  114. if len(password) == 0 || len(password) > LM20_PWLEN {
  115. return .BadPassword
  116. }
  117. username_w = utf8_to_utf16(username, context.temp_allocator)
  118. password_w = utf8_to_utf16(password, context.temp_allocator)
  119. level := DWORD(1)
  120. parm_err: DWORD
  121. user_info := USER_INFO_1{
  122. name = &username_w[0],
  123. password = &password_w[0], // Max password length is defined in LM20_PWLEN.
  124. password_age = 0, // Ignored
  125. priv = .User,
  126. home_dir = nil, // We'll set it later
  127. comment = nil,
  128. flags = {.Script, .Normal_Account},
  129. script_path = nil,
  130. }
  131. ok = NetUserAdd(
  132. servername_w,
  133. level,
  134. &user_info,
  135. &parm_err,
  136. )
  137. return
  138. }
  139. get_computer_name_and_account_sid :: proc(username: string) -> (computer_name: string, sid := SID{}, ok: bool) {
  140. username_w := utf8_to_utf16(username, context.temp_allocator)
  141. cbsid: DWORD
  142. computer_name_size: DWORD
  143. pe_use := SID_TYPE.User
  144. res := LookupAccountNameW(
  145. nil, // Look on this computer first
  146. &username_w[0],
  147. &sid,
  148. &cbsid,
  149. nil,
  150. &computer_name_size,
  151. &pe_use,
  152. )
  153. if computer_name_size == 0 {
  154. // User didn't exist, or we'd have a size here.
  155. return "", {}, false
  156. }
  157. cname_w := make([]u16, min(computer_name_size, 1), context.temp_allocator)
  158. res = LookupAccountNameW(
  159. nil,
  160. &username_w[0],
  161. &sid,
  162. &cbsid,
  163. &cname_w[0],
  164. &computer_name_size,
  165. &pe_use,
  166. )
  167. if !res {
  168. return "", {}, false
  169. }
  170. computer_name = utf16_to_utf8(cname_w, context.temp_allocator)
  171. ok = true
  172. return
  173. }
  174. get_sid :: proc(username: string, sid: ^SID) -> (ok: bool) {
  175. username_w := utf8_to_utf16(username, context.temp_allocator)
  176. cbsid: DWORD
  177. computer_name_size: DWORD
  178. pe_use := SID_TYPE.User
  179. res := LookupAccountNameW(
  180. nil, // Look on this computer first
  181. &username_w[0],
  182. sid,
  183. &cbsid,
  184. nil,
  185. &computer_name_size,
  186. &pe_use,
  187. )
  188. if computer_name_size == 0 {
  189. // User didn't exist, or we'd have a size here.
  190. return false
  191. }
  192. cname_w := make([]u16, min(computer_name_size, 1), context.temp_allocator)
  193. res = LookupAccountNameW(
  194. nil,
  195. &username_w[0],
  196. sid,
  197. &cbsid,
  198. &cname_w[0],
  199. &computer_name_size,
  200. &pe_use,
  201. )
  202. if !res {
  203. return false
  204. }
  205. ok = true
  206. return
  207. }
  208. add_user_to_group :: proc(sid: ^SID, group: string) -> (ok: NET_API_STATUS) {
  209. group_member := LOCALGROUP_MEMBERS_INFO_0{
  210. sid = sid,
  211. }
  212. group_name := utf8_to_utf16(group, context.temp_allocator)
  213. ok = NetLocalGroupAddMembers(
  214. nil,
  215. &group_name[0],
  216. 0,
  217. &group_member,
  218. 1,
  219. )
  220. return
  221. }
  222. add_del_from_group :: proc(sid: ^SID, group: string) -> (ok: NET_API_STATUS) {
  223. group_member := LOCALGROUP_MEMBERS_INFO_0{
  224. sid = sid,
  225. }
  226. group_name := utf8_to_utf16(group, context.temp_allocator)
  227. ok = NetLocalGroupDelMembers(
  228. nil,
  229. &group_name[0],
  230. 0,
  231. &group_member,
  232. 1,
  233. )
  234. return
  235. }
  236. add_user_profile :: proc(username: string) -> (ok: bool, profile_path: string) {
  237. username_w := utf8_to_utf16(username, context.temp_allocator)
  238. sid := SID{}
  239. ok = get_sid(username, &sid)
  240. if ok == false {
  241. return false, ""
  242. }
  243. sb: wstring
  244. res := ConvertSidToStringSidW(&sid, &sb)
  245. if res == false {
  246. return false, ""
  247. }
  248. defer win32.local_free(sb)
  249. pszProfilePath := make([]u16, 257, context.temp_allocator)
  250. res2 := CreateProfile(
  251. sb,
  252. &username_w[0],
  253. &pszProfilePath[0],
  254. 257,
  255. )
  256. if res2 != 0 {
  257. return false, ""
  258. }
  259. profile_path = wstring_to_utf8(&pszProfilePath[0], 257)
  260. return true, profile_path
  261. }
  262. delete_user_profile :: proc(username: string) -> (ok: bool) {
  263. sid := SID{}
  264. ok = get_sid(username, &sid)
  265. if ok == false {
  266. return false
  267. }
  268. sb: wstring
  269. res := ConvertSidToStringSidW(&sid, &sb)
  270. if res == false {
  271. return false
  272. }
  273. defer win32.local_free(sb)
  274. res2 := DeleteProfileW(
  275. sb,
  276. nil,
  277. nil,
  278. )
  279. return bool(res2)
  280. }
  281. add_user :: proc(servername: string, username: string, password: string) -> (ok: bool) {
  282. /*
  283. Convenience function that creates a new user, adds it to the group Users and creates a profile directory for it.
  284. Requires elevated privileges (run as administrator).
  285. TODO: Add a bool that governs whether to delete the user if adding to group and/or creating profile fail?
  286. TODO: SecureZeroMemory the password after use.
  287. */
  288. res := _add_user(servername, username, password)
  289. if res != .Success {
  290. return false
  291. }
  292. // Grab the SID to add the user to the Users group.
  293. sid: SID
  294. ok2 := get_sid(username, &sid)
  295. if ok2 == false {
  296. return false
  297. }
  298. ok3 := add_user_to_group(&sid, "Users")
  299. if ok3 != .Success {
  300. return false
  301. }
  302. return true
  303. }
  304. delete_user :: proc(servername: string, username: string) -> (ok: bool) {
  305. /*
  306. Convenience function that deletes a user.
  307. Requires elevated privileges (run as administrator).
  308. TODO: Add a bool that governs whether to delete the profile from this wrapper?
  309. */
  310. servername_w: wstring
  311. if len(servername) == 0 {
  312. // Delete account on this computer
  313. servername_w = nil
  314. } else {
  315. server := utf8_to_utf16(servername, context.temp_allocator)
  316. servername_w = &server[0]
  317. }
  318. username_w := utf8_to_utf16(username)
  319. res := NetUserDel(
  320. servername_w,
  321. &username_w[0],
  322. )
  323. if res != .Success {
  324. return false
  325. }
  326. return true
  327. }
  328. run_as_user :: proc(username, password, application, commandline: string, pi: ^PROCESS_INFORMATION, wait := true) -> (ok: bool) {
  329. /*
  330. Needs to be run as an account which has the "Replace a process level token" privilege.
  331. This can be added to an account from: Control Panel -> Administrative Tools -> Local Security Policy.
  332. The path to this policy is as follows: Local Policies -> User Rights Assignment -> Replace a process level token.
  333. A reboot may be required for this change to take effect and impersonating a user to work.
  334. TODO: SecureZeroMemory the password after use.
  335. */
  336. username_w := utf8_to_utf16(username)
  337. domain_w := utf8_to_utf16(".")
  338. password_w := utf8_to_utf16(password)
  339. app_w := utf8_to_utf16(application)
  340. commandline_w: []u16 = {0}
  341. if len(commandline) > 0 {
  342. commandline_w = utf8_to_utf16(commandline)
  343. }
  344. user_token: HANDLE
  345. ok = bool(LogonUserW(
  346. lpszUsername = &username_w[0],
  347. lpszDomain = &domain_w[0],
  348. lpszPassword = &password_w[0],
  349. dwLogonType = .NEW_CREDENTIALS,
  350. dwLogonProvider = .WINNT50,
  351. phToken = &user_token,
  352. ))
  353. if !ok {
  354. return false
  355. // err := GetLastError();
  356. // fmt.printf("GetLastError: %v\n", err);
  357. }
  358. si := STARTUPINFO{}
  359. si.cb = size_of(STARTUPINFO)
  360. pi := pi
  361. ok = bool(CreateProcessAsUserW(
  362. user_token,
  363. &app_w[0],
  364. &commandline_w[0],
  365. nil, // lpProcessAttributes,
  366. nil, // lpThreadAttributes,
  367. false, // bInheritHandles,
  368. 0, // creation flags
  369. nil, // environment,
  370. nil, // current directory: inherit from parent if nil
  371. &si,
  372. pi,
  373. ))
  374. if ok {
  375. if wait {
  376. WaitForSingleObject(pi.hProcess, INFINITE)
  377. CloseHandle(pi.hProcess)
  378. CloseHandle(pi.hThread)
  379. }
  380. return true
  381. } else {
  382. return false
  383. }
  384. }