mirror of
https://github.com/odin-lang/Odin.git
synced 2026-01-02 19:22:33 +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:
@@ -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 ---;
|
||||
}
|
||||
37
core/sys/windows/netapi32.odin
Normal file
37
core/sys/windows/netapi32.odin
Normal 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 ---;
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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 ---
|
||||
}
|
||||
|
||||
@@ -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