util.odin 10 KB

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