util.odin 11 KB

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