aboutsummaryrefslogtreecommitdiff
path: root/core/sys
diff options
context:
space:
mode:
authorJeroen van Rijn <Kelimion@users.noreply.github.com>2021-04-13 02:09:44 +0200
committerJeroen van Rijn <Kelimion@users.noreply.github.com>2021-04-13 02:09:44 +0200
commita1d871360cdfd0e2683bf17df5eaca094254282e (patch)
treecb401c055d04ab4d0408ce72182fb5867f8bbb1d /core/sys
parent2b36069924c9fa21111fe8a8b5471e25847c52cf (diff)
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); }
Diffstat (limited to 'core/sys')
-rw-r--r--core/sys/windows/advapi32.odin54
-rw-r--r--core/sys/windows/netapi32.odin37
-rw-r--r--core/sys/windows/types.odin450
-rw-r--r--core/sys/windows/userenv.odin28
-rw-r--r--core/sys/windows/util.odin376
5 files changed, 943 insertions, 2 deletions
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