diff --git a/core/sys/windows/advapi32.odin b/core/sys/windows/advapi32.odin index b25ed6ef4..31e542e37 100644 --- a/core/sys/windows/advapi32.odin +++ b/core/sys/windows/advapi32.odin @@ -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 ---; +} \ No newline at end of file diff --git a/core/sys/windows/netapi32.odin b/core/sys/windows/netapi32.odin new file mode 100644 index 000000000..d9b41657e --- /dev/null +++ b/core/sys/windows/netapi32.odin @@ -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 ---; +} \ No newline at end of file diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index f5e404863..65aa3a113 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -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, +} \ No newline at end of file diff --git a/core/sys/windows/userenv.odin b/core/sys/windows/userenv.odin index a701b6ca6..b57ef5f2d 100644 --- a/core/sys/windows/userenv.odin +++ b/core/sys/windows/userenv.odin @@ -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 --- } diff --git a/core/sys/windows/util.odin b/core/sys/windows/util.odin index 843bd8bcb..c53657e63 100644 --- a/core/sys/windows/util.odin +++ b/core/sys/windows/util.odin @@ -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; + } +} \ No newline at end of file