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:
Jeroen van Rijn
2021-04-13 02:09:44 +02:00
parent 2b36069924
commit a1d871360c
5 changed files with 943 additions and 2 deletions

View File

@@ -10,3 +10,57 @@ foreign advapi32 {
DesiredAccess: DWORD,
TokenHandle: ^HANDLE) -> BOOL ---
}
// Necessary to create a token to impersonate a user with for CreateProcessAsUser
@(default_calling_convention="stdcall")
foreign advapi32 {
LogonUserW :: proc(
lpszUsername: LPCWSTR,
lpszDomain: LPCWSTR,
lpszPassword: LPCWSTR,
dwLogonType: Logon32_Type,
dwLogonProvider: Logon32_Provider,
phToken: ^HANDLE,
) -> BOOL ---
// https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-lookupaccountnamew
// To look up the SID to use with DeleteProfileW.
LookupAccountNameW :: proc(
lpSystemName: wstring,
lpAccountName: wstring,
Sid: ^SID,
cbSid: ^DWORD,
ReferencedDomainName: wstring,
cchReferencedDomainName: ^DWORD,
peUse: ^SID_TYPE,
) -> BOOL ---
CreateProcessWithLogonW :: proc(
lpUsername: wstring,
lpDomain: wstring,
lpPassword: wstring,
dwLogonFlags: DWORD,
lpApplicationName: wstring,
lpCommandLine: wstring,
dwCreationFlags: DWORD,
lpEnvironment: LPVOID,
lpCurrentDirectory: wstring,
lpStartupInfo: LPSTARTUPINFO,
lpProcessInformation: LPPROCESS_INFORMATION,
) -> BOOL ---
// https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasuserw
CreateProcessAsUserW :: proc(
hToken: HANDLE,
lpApplicationName: wstring,
lpCommandLine: wstring,
lpProcessAttributes: LPSECURITY_ATTRIBUTES,
lpThreadAttributes: LPSECURITY_ATTRIBUTES,
bInheritHandles: BOOL,
dwCreationFlags: DWORD,
lpEnvironment: LPVOID,
lpCurrentDirectory: wstring,
lpStartupInfo: LPSTARTUPINFO,
lpProcessInformation: LPPROCESS_INFORMATION,
) -> BOOL ---;
}

View File

@@ -0,0 +1,37 @@
package sys_windows
foreign import netapi32 "system:Netapi32.lib"
@(default_calling_convention="stdcall")
foreign netapi32 {
NetUserAdd :: proc(
servername: wstring,
level: DWORD,
user_info: ^USER_INFO_1, // Perhaps make this a #raw_union with USER_INFO1..4 when we need the other levels.
parm_err: ^DWORD,
) -> NET_API_STATUS ---;
NetUserDel :: proc(
servername: wstring,
username: wstring,
) -> NET_API_STATUS ---;
NetUserGetInfo :: proc(
servername: wstring,
username: wstring,
level: DWORD,
user_info: ^USER_INFO_1,
) -> NET_API_STATUS ---;
NetLocalGroupAddMembers :: proc(
servername: wstring,
groupname: wstring,
level: DWORD,
group_members_info: ^LOCALGROUP_MEMBERS_INFO_0, // Actually a variably sized array of these.
totalentries: DWORD,
) -> NET_API_STATUS ---;
NetLocalGroupDelMembers :: proc(
servername: wstring,
groupname: wstring,
level: DWORD,
group_members_info: ^LOCALGROUP_MEMBERS_INFO_0, // Actually a variably sized array of these.
totalentries: DWORD,
) -> NET_API_STATUS ---;
}

View File

@@ -570,7 +570,8 @@ PROCESS_INFORMATION :: struct {
dwThreadId: DWORD,
}
STARTUPINFO :: struct {
// FYI: This is STARTUPINFOW, not STARTUPINFOA
STARTUPINFO :: struct #packed {
cb: DWORD,
lpReserved: LPWSTR,
lpDesktop: LPWSTR,
@@ -580,7 +581,7 @@ STARTUPINFO :: struct {
dwXSize: DWORD,
dwYSize: DWORD,
dwXCountChars: DWORD,
dwYCountCharts: DWORD,
dwYCountChars: DWORD,
dwFillAttribute: DWORD,
dwFlags: DWORD,
wShowWindow: WORD,
@@ -788,3 +789,448 @@ OSVERSIONINFOEXW :: struct {
wProductType: UCHAR,
wReserved: UCHAR,
};
// https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-quota_limits
// Used in LogonUserExW
PQUOTA_LIMITS :: struct {
PagedPoolLimit: SIZE_T,
NonPagedPoolLimit: SIZE_T,
MinimumWorkingSetSize: SIZE_T,
MaximumWorkingSetSize: SIZE_T,
PagefileLimit: SIZE_T,
TimeLimit: LARGE_INTEGER,
};
Logon32_Type :: enum DWORD {
INTERACTIVE = 2,
NETWORK = 3,
BATCH = 4,
SERVICE = 5,
UNLOCK = 7,
NETWORK_CLEARTEXT = 8,
NEW_CREDENTIALS = 9,
}
Logon32_Provider :: enum DWORD {
DEFAULT = 0,
WINNT35 = 1,
WINNT40 = 2,
WINNT50 = 3,
VIRTUAL = 4,
}
// https://docs.microsoft.com/en-us/windows/win32/api/profinfo/ns-profinfo-profileinfow
// Used in LoadUserProfileW
PROFILEINFOW :: struct {
dwSize: DWORD,
dwFlags: DWORD,
lpUserName: LPWSTR,
lpProfilePath: LPWSTR,
lpDefaultPath: LPWSTR,
lpServerName: LPWSTR,
lpPolicyPath: LPWSTR,
hProfile: HANDLE,
};
// Used in LookupAccountNameW
SID_NAME_USE :: distinct DWORD;
SID_TYPE :: enum SID_NAME_USE {
User = 1,
Group,
Domain,
Alias,
WellKnownGroup,
DeletedAccount,
Invalid,
Unknown,
Computer,
Label,
LogonSession
}
SECURITY_MAX_SID_SIZE :: 68;
// https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-sid
SID :: struct #packed {
Revision: byte,
SubAuthorityCount: byte,
IdentifierAuthority: SID_IDENTIFIER_AUTHORITY,
SubAuthority: [15]DWORD, // Array of DWORDs
};
#assert(size_of(SID) == SECURITY_MAX_SID_SIZE);
SID_IDENTIFIER_AUTHORITY :: struct #packed {
Value: [6]u8,
};
// For NetAPI32
// https://github.com/tpn/winsdk-10/blob/master/Include/10.0.14393.0/shared/lmerr.h
// https://github.com/tpn/winsdk-10/blob/master/Include/10.0.14393.0/shared/LMaccess.h
UNLEN :: 256; // Maximum user name length
LM20_UNLEN :: 20; // LM 2.0 Maximum user name length
GNLEN :: UNLEN; // Group name
LM20_GNLEN :: LM20_UNLEN; // LM 2.0 Group name
PWLEN :: 256; // Maximum password length
LM20_PWLEN :: 14; // LM 2.0 Maximum password length
USER_PRIV :: enum DWORD {
Guest = 0,
User = 1,
Admin = 2,
Mask = 0x3,
}
USER_INFO_FLAG :: enum DWORD {
Script = 0, // 1 << 0: 0x0001,
AccountDisable = 1, // 1 << 1: 0x0002,
HomeDir_Required = 3, // 1 << 3: 0x0008,
Lockout = 4, // 1 << 4: 0x0010,
Passwd_NotReqd = 5, // 1 << 5: 0x0020,
Passwd_Cant_Change = 6, // 1 << 6: 0x0040,
Encrypted_Text_Password_Allowed = 7, // 1 << 7: 0x0080,
Temp_Duplicate_Account = 8, // 1 << 8: 0x0100,
Normal_Account = 9, // 1 << 9: 0x0200,
InterDomain_Trust_Account = 11, // 1 << 11: 0x0800,
Workstation_Trust_Account = 12, // 1 << 12: 0x1000,
Server_Trust_Account = 13, // 1 << 13: 0x2000,
}
USER_INFO_FLAGS :: distinct bit_set[USER_INFO_FLAG];
USER_INFO_1 :: struct #packed {
name: LPWSTR,
password: LPWSTR, // Max password length is defined in LM20_PWLEN.
password_age: DWORD,
priv: USER_PRIV,
home_dir: LPWSTR,
comment: LPWSTR,
flags: USER_INFO_FLAGS,
script_path: LPWSTR,
};
#assert(size_of(USER_INFO_1) == 50);
LOCALGROUP_MEMBERS_INFO_0 :: struct #packed {
sid: ^SID,
};
NET_API_STATUS :: enum DWORD {
Success = 0,
ERROR_ACCESS_DENIED = 5,
MemberInAlias = 1378,
NetNotStarted = 2102,
UnknownServer = 2103,
ShareMem = 2104,
NoNetworkResource = 2105,
RemoteOnly = 2106,
DevNotRedirected = 2107,
ServerNotStarted = 2114,
ItemNotFound = 2115,
UnknownDevDir = 2116,
RedirectedPath = 2117,
DuplicateShare = 2118,
NoRoom = 2119,
TooManyItems = 2121,
InvalidMaxUsers = 2122,
BufTooSmall = 2123,
RemoteErr = 2127,
LanmanIniError = 2131,
NetworkError = 2136,
WkstaInconsistentState = 2137,
WkstaNotStarted = 2138,
BrowserNotStarted = 2139,
InternalError = 2140,
BadTransactConfig = 2141,
InvalidAPI = 2142,
BadEventName = 2143,
DupNameReboot = 2144,
CfgCompNotFound = 2146,
CfgParamNotFound = 2147,
LineTooLong = 2149,
QNotFound = 2150,
JobNotFound = 2151,
DestNotFound = 2152,
DestExists = 2153,
QExists = 2154,
QNoRoom = 2155,
JobNoRoom = 2156,
DestNoRoom = 2157,
DestIdle = 2158,
DestInvalidOp = 2159,
ProcNoRespond = 2160,
SpoolerNotLoaded = 2161,
DestInvalidState = 2162,
QInvalidState = 2163,
JobInvalidState = 2164,
SpoolNoMemory = 2165,
DriverNotFound = 2166,
DataTypeInvalid = 2167,
ProcNotFound = 2168,
ServiceTableLocked = 2180,
ServiceTableFull = 2181,
ServiceInstalled = 2182,
ServiceEntryLocked = 2183,
ServiceNotInstalled = 2184,
BadServiceName = 2185,
ServiceCtlTimeout = 2186,
ServiceCtlBusy = 2187,
BadServiceProgName = 2188,
ServiceNotCtrl = 2189,
ServiceKillProc = 2190,
ServiceCtlNotValid = 2191,
NotInDispatchTbl = 2192,
BadControlRecv = 2193,
ServiceNotStarting = 2194,
AlreadyLoggedOn = 2200,
NotLoggedOn = 2201,
BadUsername = 2202,
BadPassword = 2203,
UnableToAddName_W = 2204,
UnableToAddName_F = 2205,
UnableToDelName_W = 2206,
UnableToDelName_F = 2207,
LogonsPaused = 2209,
LogonServerConflict = 2210,
LogonNoUserPath = 2211,
LogonScriptError = 2212,
StandaloneLogon = 2214,
LogonServerNotFound = 2215,
LogonDomainExists = 2216,
NonValidatedLogon = 2217,
ACFNotFound = 2219,
GroupNotFound = 2220,
UserNotFound = 2221,
ResourceNotFound = 2222,
GroupExists = 2223,
UserExists = 2224,
ResourceExists = 2225,
NotPrimary = 2226,
ACFNotLoaded = 2227,
ACFNoRoom = 2228,
ACFFileIOFail = 2229,
ACFTooManyLists = 2230,
UserLogon = 2231,
ACFNoParent = 2232,
CanNotGrowSegment = 2233,
SpeGroupOp = 2234,
NotInCache = 2235,
UserInGroup = 2236,
UserNotInGroup = 2237,
AccountUndefined = 2238,
AccountExpired = 2239,
InvalidWorkstation = 2240,
InvalidLogonHours = 2241,
PasswordExpired = 2242,
PasswordCantChange = 2243,
PasswordHistConflict = 2244,
PasswordTooShort = 2245,
PasswordTooRecent = 2246,
InvalidDatabase = 2247,
DatabaseUpToDate = 2248,
SyncRequired = 2249,
UseNotFound = 2250,
BadAsgType = 2251,
DeviceIsShared = 2252,
SameAsComputerName = 2253,
NoComputerName = 2270,
MsgAlreadyStarted = 2271,
MsgInitFailed = 2272,
NameNotFound = 2273,
AlreadyForwarded = 2274,
AddForwarded = 2275,
AlreadyExists = 2276,
TooManyNames = 2277,
DelComputerName = 2278,
LocalForward = 2279,
GrpMsgProcessor = 2280,
PausedRemote = 2281,
BadReceive = 2282,
NameInUse = 2283,
MsgNotStarted = 2284,
NotLocalName = 2285,
NoForwardName = 2286,
RemoteFull = 2287,
NameNotForwarded = 2288,
TruncatedBroadcast = 2289,
InvalidDevice = 2294,
WriteFault = 2295,
DuplicateName = 2297,
DeleteLater = 2298,
IncompleteDel = 2299,
MultipleNets = 2300,
NetNameNotFound = 2310,
DeviceNotShared = 2311,
ClientNameNotFound = 2312,
FileIdNotFound = 2314,
ExecFailure = 2315,
TmpFile = 2316,
TooMuchData = 2317,
DeviceShareConflict = 2318,
BrowserTableIncomplete = 2319,
NotLocalDomain = 2320,
IsDfsShare = 2321,
DevInvalidOpCode = 2331,
DevNotFound = 2332,
DevNotOpen = 2333,
BadQueueDevString = 2334,
BadQueuePriority = 2335,
NoCommDevs = 2337,
QueueNotFound = 2338,
BadDevString = 2340,
BadDev = 2341,
InUseBySpooler = 2342,
CommDevInUse = 2343,
InvalidComputer = 2351,
MaxLenExceeded = 2354,
BadComponent = 2356,
CantType = 2357,
TooManyEntries = 2362,
ProfileFileTooBig = 2370,
ProfileOffset = 2371,
ProfileCleanup = 2372,
ProfileUnknownCmd = 2373,
ProfileLoadErr = 2374,
ProfileSaveErr = 2375,
LogOverflow = 2377,
LogFileChanged = 2378,
LogFileCorrupt = 2379,
SourceIsDir = 2380,
BadSource = 2381,
BadDest = 2382,
DifferentServers = 2383,
RunSrvPaused = 2385,
ErrCommRunSrv = 2389,
ErrorExecingGhost = 2391,
ShareNotFound = 2392,
InvalidLana = 2400,
OpenFiles = 2401,
ActiveConns = 2402,
BadPasswordCore = 2403,
DevInUse = 2404,
LocalDrive = 2405,
AlertExists = 2430,
TooManyAlerts = 2431,
NoSuchAlert = 2432,
BadRecipient = 2433,
AcctLimitExceeded = 2434,
InvalidLogSeek = 2440,
BadUasConfig = 2450,
InvalidUASOp = 2451,
LastAdmin = 2452,
DCNotFound = 2453,
LogonTrackingError = 2454,
NetlogonNotStarted = 2455,
CanNotGrowUASFile = 2456,
TimeDiffAtDC = 2457,
PasswordMismatch = 2458,
NoSuchServer = 2460,
NoSuchSession = 2461,
NoSuchConnection = 2462,
TooManyServers = 2463,
TooManySessions = 2464,
TooManyConnections = 2465,
TooManyFiles = 2466,
NoAlternateServers = 2467,
TryDownLevel = 2470,
UPSDriverNotStarted = 2480,
UPSInvalidConfig = 2481,
UPSInvalidCommPort = 2482,
UPSSignalAsserted = 2483,
UPSShutdownFailed = 2484,
BadDosRetCode = 2500,
ProgNeedsExtraMem = 2501,
BadDosFunction = 2502,
RemoteBootFailed = 2503,
BadFileCheckSum = 2504,
NoRplBootSystem = 2505,
RplLoadrNetBiosErr = 2506,
RplLoadrDiskErr = 2507,
ImageParamErr = 2508,
TooManyImageParams = 2509,
NonDosFloppyUsed = 2510,
RplBootRestart = 2511,
RplSrvrCallFailed = 2512,
CantConnectRplSrvr = 2513,
CantOpenImageFile = 2514,
CallingRplSrvr = 2515,
StartingRplBoot = 2516,
RplBootServiceTerm = 2517,
RplBootStartFailed = 2518,
RPL_CONNECTED = 2519,
BrowserConfiguredToNotRun = 2550,
RplNoAdaptersStarted = 2610,
RplBadRegistry = 2611,
RplBadDatabase = 2612,
RplRplfilesShare = 2613,
RplNotRplServer = 2614,
RplCannotEnum = 2615,
RplWkstaInfoCorrupted = 2616,
RplWkstaNotFound = 2617,
RplWkstaNameUnavailable = 2618,
RplProfileInfoCorrupted = 2619,
RplProfileNotFound = 2620,
RplProfileNameUnavailable = 2621,
RplProfileNotEmpty = 2622,
RplConfigInfoCorrupted = 2623,
RplConfigNotFound = 2624,
RplAdapterInfoCorrupted = 2625,
RplInternal = 2626,
RplVendorInfoCorrupted = 2627,
RplBootInfoCorrupted = 2628,
RplWkstaNeedsUserAcct = 2629,
RplNeedsRPLUSERAcct = 2630,
RplBootNotFound = 2631,
RplIncompatibleProfile = 2632,
RplAdapterNameUnavailable = 2633,
RplConfigNotEmpty = 2634,
RplBootInUse = 2635,
RplBackupDatabase = 2636,
RplAdapterNotFound = 2637,
RplVendorNotFound = 2638,
RplVendorNameUnavailable = 2639,
RplBootNameUnavailable = 2640,
RplConfigNameUnavailable = 2641,
DfsInternalCorruption = 2660,
DfsVolumeDataCorrupt = 2661,
DfsNoSuchVolume = 2662,
DfsVolumeAlreadyExists = 2663,
DfsAlreadyShared = 2664,
DfsNoSuchShare = 2665,
DfsNotALeafVolume = 2666,
DfsLeafVolume = 2667,
DfsVolumeHasMultipleServers = 2668,
DfsCantCreateJunctionPoint = 2669,
DfsServerNotDfsAware = 2670,
DfsBadRenamePath = 2671,
DfsVolumeIsOffline = 2672,
DfsNoSuchServer = 2673,
DfsCyclicalName = 2674,
DfsNotSupportedInServerDfs = 2675,
DfsDuplicateService = 2676,
DfsCantRemoveLastServerShare = 2677,
DfsVolumeIsInterDfs = 2678,
DfsInconsistent = 2679,
DfsServerUpgraded = 2680,
DfsDataIsIdentical = 2681,
DfsCantRemoveDfsRoot = 2682,
DfsChildOrParentInDfs = 2683,
DfsInternalError = 2690,
SetupAlreadyJoined = 2691,
SetupNotJoined = 2692,
SetupDomainController = 2693,
DefaultJoinRequired = 2694,
InvalidWorkgroupName = 2695,
NameUsesIncompatibleCodePage = 2696,
ComputerAccountNotFound = 2697,
PersonalSku = 2698,
SetupCheckDNSConfig = 2699,
PasswordMustChange = 2701,
AccountLockedOut = 2702,
PasswordTooLong = 2703,
PasswordNotComplexEnough = 2704,
PasswordFilterError = 2705,
}

View File

@@ -7,4 +7,32 @@ foreign userenv {
GetUserProfileDirectoryW :: proc(hToken: HANDLE,
lpProfileDir: LPWSTR,
lpcchSize: ^DWORD) -> BOOL ---
LoadUserProfileW :: proc(
hToken: HANDLE,
lpProfileInfo: ^PROFILEINFOW,
) -> BOOL ---
// https://docs.microsoft.com/en-us/windows/win32/api/userenv/nf-userenv-createprofile
// The caller must have administrator privileges to call this function.
CreateProfile :: proc(
pszUserSid: LPCWSTR,
pszUserName: LPCWSTR,
pszProfilePath: wstring,
cchProfilePath: DWORD,
) -> u32 ---
// https://docs.microsoft.com/en-us/windows/win32/api/userenv/nf-userenv-deleteprofilew
// The caller must have administrative privileges to delete a user's profile.
DeleteProfileW :: proc(
lpSidString: LPCWSTR,
lpProfilePath: LPCWSTR,
lpComputerName: LPCWSTR,
) -> BOOL ---
// https://docs.microsoft.com/en-us/windows/win32/api/sddl/nf-sddl-convertsidtostringsida
// To turn a SID into a string SID to use with CreateProfile & DeleteProfileW.
ConvertSidToStringSidW :: proc(
Sid: ^SID,
StringSid: ^LPCWSTR,
) -> BOOL ---
}

View File

@@ -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;
}
}