util.odin 10 KB

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