Skip to content

Commit

Permalink
stdlib/os: add isAdmin (#17012)
Browse files Browse the repository at this point in the history
* stdlib/os: add isAdmin

* uint8 -> cuchar, assert isAdmin on Azure

Co-authored-by: Timothee Cour <timothee.cour2@gmail.com>

* Update lib/pure/os.nim docs

Co-authored-by: Timothee Cour <timothee.cour2@gmail.com>

* Address comments on #17012

* Raise on errors in #17012

* Check the result of FreeSid in #17012

* Change case in #17012

* Fix memory leak in #17012

* Address comments in #17012

Co-authored-by: Timothee Cour <timothee.cour2@gmail.com>

Co-authored-by: Timothee Cour <timothee.cour2@gmail.com>
  • Loading branch information
rominf and timotheecour authored Mar 7, 2021
1 parent d1e0932 commit 31424b3
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 0 deletions.
3 changes: 3 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,9 @@ provided by the operating system.
(instead of skipping them sometimes as it was before).
- Added optional `followSymlinks` argument to `setFilePermissions`.

- Added `os.isAdmin` to tell whether the caller's process is a member of the
Administrators local group (on Windows) or a root (on POSIX).

- Added `random.initRand()` overload with no argument which uses the current time as a seed.

- Added experimental `linenoise.readLineStatus` to get line and status (e.g. ctrl-D or ctrl-C).
Expand Down
30 changes: 30 additions & 0 deletions lib/pure/os.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1688,6 +1688,36 @@ proc setFilePermissions*(filename: string, permissions: set[FilePermission],
var res2 = setFileAttributesA(filename, res)
if res2 == - 1'i32: raiseOSError(osLastError(), $(filename, permissions))

proc isAdmin*: bool {.noWeirdTarget.} =
## Returns whether the caller's process is a member of the Administrators local
## group (on Windows) or a root (on POSIX), via `geteuid() == 0`.
when defined(windows):
# Rewrite of the example from Microsoft Docs:
# https://docs.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-checktokenmembership#examples
# and corresponding PostgreSQL function:
# https://doxygen.postgresql.org/win32security_8c.html#ae6b61e106fa5d6c5d077a9d14ee80569
var ntAuthority = SID_IDENTIFIER_AUTHORITY(value: SECURITY_NT_AUTHORITY)
var administratorsGroup: PSID
if not isSuccess(allocateAndInitializeSid(addr ntAuthority,
BYTE(2),
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0,
addr administratorsGroup)):
raiseOSError(osLastError(), "could not get SID for Administrators group")

defer:
if freeSid(administratorsGroup) != nil:
raiseOSError(osLastError(), "failed to free SID for Administrators group")

var b: WINBOOL
if not isSuccess(checkTokenMembership(0, administratorsGroup, addr b)):
raiseOSError(osLastError(), "could not check access token membership")

return isSuccess(b)
else:
return geteuid() == 0

proc createSymlink*(src, dest: string) {.noWeirdTarget.} =
## Create a symbolic link at `dest` which points to the item specified
## by `src`. On most operating systems, will fail if a link already exists.
Expand Down
44 changes: 44 additions & 0 deletions lib/windows/winlean.nim
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ when useWinUnicode:
else:
type WinChar* = char

# See https://docs.microsoft.com/en-us/windows/win32/winprog/windows-data-types
type
Handle* = int
LONG* = int32
Expand All @@ -33,13 +34,15 @@ type
WINBOOL* = int32
## `WINBOOL` uses opposite convention as posix, !=0 meaning success.
# xxx this should be distinct int32, distinct would make code less error prone
PBOOL* = ptr WINBOOL
DWORD* = int32
PDWORD* = ptr DWORD
LPINT* = ptr int32
ULONG_PTR* = uint
PULONG_PTR* = ptr uint
HDC* = Handle
HGLRC* = Handle
BYTE* = cuchar

SECURITY_ATTRIBUTES* {.final, pure.} = object
nLength*: int32
Expand Down Expand Up @@ -136,6 +139,10 @@ const

HANDLE_FLAG_INHERIT* = 0x00000001'i32

proc isSuccess*(a: WINBOOL): bool {.inline.} =
## Returns true if `a != 0`. Windows uses a different convention than POSIX,
## where `a == 0` is commonly used on success.
a != 0
proc getVersionExW*(lpVersionInfo: ptr OSVERSIONINFO): WINBOOL {.
stdcall, dynlib: "kernel32", importc: "GetVersionExW", sideEffect.}
proc getVersionExA*(lpVersionInfo: ptr OSVERSIONINFO): WINBOOL {.
Expand Down Expand Up @@ -1129,5 +1136,42 @@ proc setFileTime*(hFile: Handle, lpCreationTime: LPFILETIME,
lpLastAccessTime: LPFILETIME, lpLastWriteTime: LPFILETIME): WINBOOL
{.stdcall, dynlib: "kernel32", importc: "SetFileTime".}

type
# https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-sid_identifier_authority
SID_IDENTIFIER_AUTHORITY* {.importc, header: "<windows.h>".} = object
value* {.importc: "Value"}: array[6, BYTE]
# https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-sid
SID* {.importc, header: "<windows.h>".} = object
Revision: BYTE
SubAuthorityCount: BYTE
IdentifierAuthority: SID_IDENTIFIER_AUTHORITY
SubAuthority: ptr ptr DWORD
PSID* = ptr SID

const
# https://docs.microsoft.com/en-us/windows/win32/secauthz/sid-components
# https://github.com/mirror/mingw-w64/blob/84c950bdab7c999ace49fe8383856be77f88c4a8/mingw-w64-headers/include/winnt.h#L2994
SECURITY_NT_AUTHORITY* = [BYTE(0), BYTE(0), BYTE(0), BYTE(0), BYTE(0), BYTE(5)]
SECURITY_BUILTIN_DOMAIN_RID* = 32
DOMAIN_ALIAS_RID_ADMINS* = 544

proc allocateAndInitializeSid*(pIdentifierAuthority: ptr SID_IDENTIFIER_AUTHORITY,
nSubAuthorityCount: BYTE,
nSubAuthority0: DWORD,
nSubAuthority1: DWORD,
nSubAuthority2: DWORD,
nSubAuthority3: DWORD,
nSubAuthority4: DWORD,
nSubAuthority5: DWORD,
nSubAuthority6: DWORD,
nSubAuthority7: DWORD,
pSid: ptr PSID): WINBOOL
{.stdcall, dynlib: "Advapi32", importc: "AllocateAndInitializeSid".}
proc checkTokenMembership*(tokenHandle: Handle, sidToCheck: PSID,
isMember: PBOOL): WINBOOL
{.stdcall, dynlib: "Advapi32", importc: "CheckTokenMembership".}
proc freeSid*(pSid: PSID): PSID
{.stdcall, dynlib: "Advapi32", importc: "FreeSid".}

when defined(nimHasStyleChecks):
{.pop.} # {.push styleChecks: off.}
7 changes: 7 additions & 0 deletions tests/stdlib/tos.nim
Original file line number Diff line number Diff line change
Expand Up @@ -655,3 +655,10 @@ block: # normalizeExe
doAssert "foo/../bar".dup(normalizeExe) == "foo/../bar"
when defined(windows):
doAssert "foo".dup(normalizeExe) == "foo"

block: # isAdmin
let isAzure = existsEnv("TF_BUILD") # xxx factor with testament.specs.isAzure
# In Azure on Windows tests run as an admin user
if isAzure and defined(windows): doAssert isAdmin()
# In Azure on POSIX tests run as a normal user
if isAzure and defined(posix): doAssert not isAdmin()

0 comments on commit 31424b3

Please sign in to comment.