mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-01 20:28:15 +00:00
Add support to core:windows to add/delete users.
main :: proc() {
using fmt;
using windows;
username := "testuser";
password := "testpass";
ok := add_user("", username, password);
fmt.printf("add_user: %v\n", ok);
pi := windows.PROCESS_INFORMATION{};
ok2, path := windows.add_user_profile(username);
fmt.printf("add_user_profile: %v, %v\n", ok2, path);
ok3 := windows.delete_user_profile(username);
fmt.printf("delete_user_profile: %v\n", ok3);
ok4 := windows.delete_user("", username);
fmt.printf("delete_user: %v\n", ok4);
// Has optional bool to not wait on the process before returning.
b := run_as_user(username, password, "C:\\Repro\\repro.exe", "Hellope!", &pi);
fmt.printf("run_as_user: %v %v\n", b, pi);
}
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
package sys_windows
|
||||
|
||||
import "core:strings"
|
||||
import "core:runtime"
|
||||
import "core:sys/win32"
|
||||
|
||||
LOWORD :: #force_inline proc "contextless" (x: DWORD) -> WORD {
|
||||
return WORD(x & 0xffff);
|
||||
}
|
||||
@@ -81,3 +85,375 @@ utf16_to_utf8 :: proc(s: []u16, allocator := context.temp_allocator) -> string {
|
||||
return wstring_to_utf8(raw_data(s), len(s), allocator);
|
||||
}
|
||||
|
||||
// AdvAPI32, NetAPI32 and UserENV helpers.
|
||||
|
||||
allowed_username :: proc(username: string) -> bool {
|
||||
/*
|
||||
User account names are limited to 20 characters and group names are limited to 256 characters.
|
||||
In addition, account names cannot be terminated by a period and they cannot include commas or any of the following printable characters:
|
||||
", /, , [, ], :, |, <, >, +, =, ;, ?, *. Names also cannot include characters in the range 1-31, which are nonprintable.
|
||||
*/
|
||||
|
||||
_DISALLOWED :: "\"/ []:|<>+=;?*,";
|
||||
|
||||
if len(username) > LM20_UNLEN || len(username) == 0 {
|
||||
return false;
|
||||
}
|
||||
if username[len(username)-1] == '.' {
|
||||
return false;
|
||||
}
|
||||
|
||||
for r in username {
|
||||
if r > 0 && r < 32 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if strings.contains_any(username, _DISALLOWED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns .Success on success.
|
||||
_add_user :: proc(servername: string, username: string, password: string) -> (ok: NET_API_STATUS) {
|
||||
|
||||
servername_w: wstring;
|
||||
username_w: []u16;
|
||||
password_w: []u16;
|
||||
|
||||
if len(servername) == 0 {
|
||||
// Create account on this computer
|
||||
servername_w = nil;
|
||||
} else {
|
||||
server := utf8_to_utf16(servername, context.temp_allocator);
|
||||
servername_w = &server[0];
|
||||
}
|
||||
|
||||
if len(username) == 0 || len(username) > LM20_UNLEN {
|
||||
return .BadUsername;
|
||||
}
|
||||
if !allowed_username(username) {
|
||||
return .BadUsername;
|
||||
}
|
||||
if len(password) == 0 || len(password) > LM20_PWLEN {
|
||||
return .BadPassword;
|
||||
}
|
||||
|
||||
username_w = utf8_to_utf16(username, context.temp_allocator);
|
||||
password_w = utf8_to_utf16(password, context.temp_allocator);
|
||||
|
||||
|
||||
level := DWORD(1);
|
||||
parm_err: DWORD;
|
||||
|
||||
user_info := USER_INFO_1{
|
||||
name = &username_w[0],
|
||||
password = &password_w[0], // Max password length is defined in LM20_PWLEN.
|
||||
password_age = 0, // Ignored
|
||||
priv = .User,
|
||||
home_dir = nil, // We'll set it later
|
||||
comment = nil,
|
||||
flags = {.Script, .Normal_Account},
|
||||
script_path = nil,
|
||||
};
|
||||
|
||||
ok = NetUserAdd(
|
||||
servername_w,
|
||||
level,
|
||||
&user_info,
|
||||
&parm_err,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
get_computer_name_and_account_sid :: proc(username: string) -> (computer_name: string, sid := SID{}, ok: bool) {
|
||||
|
||||
username_w := utf8_to_utf16(username, context.temp_allocator);
|
||||
cbsid: DWORD;
|
||||
computer_name_size: DWORD;
|
||||
pe_use := SID_TYPE.User;
|
||||
|
||||
res := LookupAccountNameW(
|
||||
nil, // Look on this computer first
|
||||
&username_w[0],
|
||||
&sid,
|
||||
&cbsid,
|
||||
nil,
|
||||
&computer_name_size,
|
||||
&pe_use,
|
||||
);
|
||||
if computer_name_size == 0 {
|
||||
// User didn't exist, or we'd have a size here.
|
||||
return "", {}, false;
|
||||
}
|
||||
|
||||
cname_w := make([]u16, min(computer_name_size, 1), context.temp_allocator);
|
||||
|
||||
res = LookupAccountNameW(
|
||||
nil,
|
||||
&username_w[0],
|
||||
&sid,
|
||||
&cbsid,
|
||||
&cname_w[0],
|
||||
&computer_name_size,
|
||||
&pe_use,
|
||||
);
|
||||
|
||||
if !res {
|
||||
return "", {}, false;
|
||||
}
|
||||
computer_name = utf16_to_utf8(cname_w, context.temp_allocator);
|
||||
|
||||
ok = true;
|
||||
return;
|
||||
}
|
||||
|
||||
get_sid :: proc(username: string, sid: ^SID) -> (ok: bool) {
|
||||
|
||||
username_w := utf8_to_utf16(username, context.temp_allocator);
|
||||
cbsid: DWORD;
|
||||
computer_name_size: DWORD;
|
||||
pe_use := SID_TYPE.User;
|
||||
|
||||
res := LookupAccountNameW(
|
||||
nil, // Look on this computer first
|
||||
&username_w[0],
|
||||
sid,
|
||||
&cbsid,
|
||||
nil,
|
||||
&computer_name_size,
|
||||
&pe_use,
|
||||
);
|
||||
if computer_name_size == 0 {
|
||||
// User didn't exist, or we'd have a size here.
|
||||
return false;
|
||||
}
|
||||
|
||||
cname_w := make([]u16, min(computer_name_size, 1), context.temp_allocator);
|
||||
|
||||
res = LookupAccountNameW(
|
||||
nil,
|
||||
&username_w[0],
|
||||
sid,
|
||||
&cbsid,
|
||||
&cname_w[0],
|
||||
&computer_name_size,
|
||||
&pe_use,
|
||||
);
|
||||
|
||||
if !res {
|
||||
return false;
|
||||
}
|
||||
ok = true;
|
||||
return;
|
||||
}
|
||||
|
||||
add_user_to_group :: proc(sid: ^SID, group: string) -> (ok: NET_API_STATUS) {
|
||||
group_member := LOCALGROUP_MEMBERS_INFO_0{
|
||||
sid = sid,
|
||||
};
|
||||
group_name := utf8_to_utf16(group, context.temp_allocator);
|
||||
ok = NetLocalGroupAddMembers(
|
||||
nil,
|
||||
&group_name[0],
|
||||
0,
|
||||
&group_member,
|
||||
1,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
add_del_from_group :: proc(sid: ^SID, group: string) -> (ok: NET_API_STATUS) {
|
||||
group_member := LOCALGROUP_MEMBERS_INFO_0{
|
||||
sid = sid,
|
||||
};
|
||||
group_name := utf8_to_utf16(group, context.temp_allocator);
|
||||
ok = NetLocalGroupDelMembers(
|
||||
nil,
|
||||
&group_name[0],
|
||||
0,
|
||||
&group_member,
|
||||
1,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
add_user_profile :: proc(username: string) -> (ok: bool, profile_path: string) {
|
||||
username_w := utf8_to_utf16(username, context.temp_allocator);
|
||||
|
||||
sid := SID{};
|
||||
ok = get_sid(username, &sid);
|
||||
if ok == false {
|
||||
return false, "";
|
||||
}
|
||||
|
||||
sb: wstring;
|
||||
res := ConvertSidToStringSidW(&sid, &sb);
|
||||
if res == false {
|
||||
return false, "";
|
||||
}
|
||||
defer win32.local_free(sb);
|
||||
|
||||
pszProfilePath := make([]u16, 257, context.temp_allocator);
|
||||
cchProfilePath: DWORD;
|
||||
res2 := CreateProfile(
|
||||
sb,
|
||||
&username_w[0],
|
||||
&pszProfilePath[0],
|
||||
257,
|
||||
);
|
||||
if res2 != 0 {
|
||||
return false, "";
|
||||
}
|
||||
profile_path = wstring_to_utf8(&pszProfilePath[0], 257);
|
||||
|
||||
return true, profile_path;
|
||||
}
|
||||
|
||||
|
||||
delete_user_profile :: proc(username: string) -> (ok: bool) {
|
||||
sid := SID{};
|
||||
ok = get_sid(username, &sid);
|
||||
if ok == false {
|
||||
return false;
|
||||
}
|
||||
|
||||
sb: wstring;
|
||||
res := ConvertSidToStringSidW(&sid, &sb);
|
||||
if res == false {
|
||||
return false;
|
||||
}
|
||||
defer win32.local_free(sb);
|
||||
|
||||
res2 := DeleteProfileW(
|
||||
sb,
|
||||
nil,
|
||||
nil,
|
||||
);
|
||||
return bool(res2);
|
||||
}
|
||||
|
||||
add_user :: proc(servername: string, username: string, password: string) -> (ok: bool) {
|
||||
/*
|
||||
Convenience function that creates a new user, adds it to the group Users and creates a profile directory for it.
|
||||
Requires elevated privileges (run as administrator).
|
||||
|
||||
TODO: Add a bool that governs whether to delete the user if adding to group and/or creating profile fail?
|
||||
TODO: SecureZeroMemory the password after use.
|
||||
*/
|
||||
|
||||
res := _add_user(servername, username, password);
|
||||
if res != .Success {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Grab the SID to add the user to the Users group.
|
||||
sid: SID;
|
||||
ok2 := get_sid(username, &sid);
|
||||
if ok2 == false {
|
||||
return false;
|
||||
}
|
||||
|
||||
ok3 := add_user_to_group(&sid, "Users");
|
||||
if ok3 != .Success {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
delete_user :: proc(servername: string, username: string) -> (ok: bool) {
|
||||
/*
|
||||
Convenience function that deletes a user.
|
||||
Requires elevated privileges (run as administrator).
|
||||
|
||||
TODO: Add a bool that governs whether to delete the profile from this wrapper?
|
||||
*/
|
||||
|
||||
servername_w: wstring;
|
||||
if len(servername) == 0 {
|
||||
// Delete account on this computer
|
||||
servername_w = nil;
|
||||
} else {
|
||||
server := utf8_to_utf16(servername, context.temp_allocator);
|
||||
servername_w = &server[0];
|
||||
}
|
||||
username_w := utf8_to_utf16(username);
|
||||
|
||||
res := NetUserDel(
|
||||
servername_w,
|
||||
&username_w[0],
|
||||
);
|
||||
if res != .Success {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
run_as_user :: proc(username, password, application, commandline: string, pi: ^PROCESS_INFORMATION, wait := true) -> (ok: bool) {
|
||||
/*
|
||||
Needs to be run as an account which has the "Replace a process level token" privilege.
|
||||
This can be added to an account from: Control Panel -> Administrative Tools -> Local Security Policy.
|
||||
The path to this policy is as follows: Local Policies -> User Rights Assignment -> Replace a process level token.
|
||||
A reboot may be required for this change to take effect and impersonating a user to work.
|
||||
|
||||
TODO: SecureZeroMemory the password after use.
|
||||
|
||||
*/
|
||||
|
||||
username_w := utf8_to_utf16(username);
|
||||
domain_w := utf8_to_utf16(".");
|
||||
password_w := utf8_to_utf16(password);
|
||||
app_w := utf8_to_utf16(application);
|
||||
|
||||
commandline_w: []u16 = {0};
|
||||
if len(commandline) > 0 {
|
||||
commandline_w = utf8_to_utf16(commandline);
|
||||
}
|
||||
|
||||
user_token: HANDLE;
|
||||
|
||||
ok = bool(LogonUserW(
|
||||
lpszUsername = &username_w[0],
|
||||
lpszDomain = &domain_w[0],
|
||||
lpszPassword = &password_w[0],
|
||||
dwLogonType = .NEW_CREDENTIALS,
|
||||
dwLogonProvider = .WINNT50,
|
||||
phToken = &user_token,
|
||||
));
|
||||
|
||||
if !ok {
|
||||
return false;
|
||||
// err := GetLastError();
|
||||
// fmt.printf("GetLastError: %v\n", err);
|
||||
}
|
||||
si := STARTUPINFO{};
|
||||
si.cb = size_of(STARTUPINFO);
|
||||
pi := pi;
|
||||
|
||||
ok = bool(CreateProcessAsUserW(
|
||||
user_token,
|
||||
&app_w[0],
|
||||
&commandline_w[0],
|
||||
nil, // lpProcessAttributes,
|
||||
nil, // lpThreadAttributes,
|
||||
false, // bInheritHandles,
|
||||
0, // creation flags
|
||||
nil, // environment,
|
||||
nil, // current directory: inherit from parent if nil
|
||||
&si,
|
||||
pi,
|
||||
));
|
||||
if ok {
|
||||
if wait {
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user