util.odin 13 KB

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