util.odin 10 KB

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