util.odin 11 KB

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