The root user now owns the whole MSYS2 installation and can run pacman. The login path the journey depends on - the sshd, the login shell zsh and cat - is kept as a protected copy in C:\msys2\opt\kik that only SYSTEM and the administrators may write, and the sshd runs as SYSTEM from there (the new KiK sshd task; the msys2-sshd service is disabled). Replacing the user's own /usr/bin/sshd therefore cannot touch the running server. opt/kik is two levels below C:\msys2, so its runtime resolves the same installation root and the login shell and the user's tools share one cygwin runtime. A separate second installation does not work: two runtimes in one process tree corrupt each other's file descriptors and pacman cannot lock its database. /etc/passwd and /etc/ssh are read-only to the user, /opt/kik is first on PATH so cat README survives even if the user deletes their own /usr/bin/cat, and the restore task rebuilds the protected copy and re-applies the permissions every 30 minutes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
391 lines
22 KiB
PowerShell
391 lines
22 KiB
PowerShell
# Sets up the shell server so the website's root user can install packages with pacman, while the basic user
|
|
# journey - logging in with the key, the prompt, the home directory and the four text files - cannot be broken.
|
|
#
|
|
# The whole MSYS2 installation belongs to the root user: they may write everywhere and run pacman. The login path
|
|
# the journey depends on - the sshd, the login shell zsh and cat - is kept as a protected copy in C:\msys2\opt\kik
|
|
# that only SYSTEM and the administrators may write. The sshd runs as SYSTEM from that copy (the scheduled task
|
|
# "KiK sshd"), so replacing /usr/bin/sshd does not touch the running server. A second scheduled task,
|
|
# "KiK restore shell files", puts the home directory, the four texts and the protected copy back every 30 minutes.
|
|
#
|
|
# opt/kik works because its MSYS2 runtime resolves the same installation root (C:\msys2) as /usr/bin, so the login
|
|
# shell and the user's own tools share one cygwin runtime. A separate second installation cannot: two runtimes in
|
|
# one process tree break each other's file descriptors, and pacman then cannot even lock its database.
|
|
#
|
|
# Run this in an elevated PowerShell on the server, with this script and the ../resources directory next to it:
|
|
# powershell -ExecutionPolicy Bypass -File .\install_restore_task.ps1
|
|
# Remove the tasks and the protected copy again (the installation goes back to a normal cygrunsrv sshd) with:
|
|
# powershell -ExecutionPolicy Bypass -File .\install_restore_task.ps1 -Uninstall
|
|
[CmdletBinding()]
|
|
param(
|
|
[string]$HomeDir = 'C:\msys2\home\root',
|
|
[switch]$Uninstall
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
$msys = 'C:\msys2'
|
|
$kik = "$msys\opt\kik"
|
|
$installDir = 'C:\ProgramData\kik-restore'
|
|
$copiesDir = Join-Path $installDir 'files'
|
|
$binDir = Join-Path $installDir 'bin'
|
|
$sshdBat = Join-Path $installDir 'run-sshd.bat'
|
|
$restoreTask = 'KiK restore shell files'
|
|
$sshdTask = 'KiK sshd'
|
|
$service = 'msys2-sshd'
|
|
|
|
# The programs the journey needs, kept out of the user's reach. sshd-session and sshd-auth are the per-connection
|
|
# helpers OpenSSH 10 re-executes; zsh is the login shell; cat reads the four texts.
|
|
$loginBinaries = "$msys\usr\bin\sshd.exe", "$msys\usr\lib\ssh\sshd-session.exe", "$msys\usr\lib\ssh\sshd-auth.exe",
|
|
"$msys\usr\bin\zsh.exe", "$msys\usr\bin\cat.exe"
|
|
|
|
# The copies the task restores from and the script it runs both live in $installDir, which only SYSTEM and the
|
|
# administrators may read or write.
|
|
$restoreScript = @'
|
|
# Puts the shell server back into shape and keeps the protected login path in place. Runs as SYSTEM every 30 minutes
|
|
# and at startup; the root user can neither stop it nor change the copies it restores from.
|
|
param([string]$HomeDir = 'C:\msys2\home\root')
|
|
$msys = 'C:\msys2'
|
|
$kik = "$msys\opt\kik"
|
|
$log = Join-Path $PSScriptRoot 'restore.log'
|
|
$copies = Join-Path $PSScriptRoot 'files\home'
|
|
$binDir = Join-Path $PSScriptRoot 'bin'
|
|
|
|
# The account of the shell server's root user, whose id the files in its home directory belong to.
|
|
$passwd = Get-Content "$msys\etc\passwd" -ErrorAction SilentlyContinue | Where-Object { $_ -like 'root:*' }
|
|
$sid = if ($passwd) { ($passwd -split ':')[4].Split(',')[-1] }
|
|
|
|
$system = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-18')
|
|
$admins = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-32-544')
|
|
|
|
function Write-Log($message) {
|
|
"$(Get-Date -Format s) $message" | Out-File -FilePath $log -Append
|
|
}
|
|
|
|
# Removes a file, a directory or a link, but never what a link points to.
|
|
function Remove-Anything($path) {
|
|
$item = Get-Item -LiteralPath $path -Force
|
|
if ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) {
|
|
if ($item.PSIsContainer) { [IO.Directory]::Delete($path) } else { [IO.File]::Delete($path) }
|
|
} elseif ($item.PSIsContainer) {
|
|
Remove-Item -LiteralPath $path -Recurse -Force
|
|
} else {
|
|
Remove-Item -LiteralPath $path -Force
|
|
}
|
|
}
|
|
|
|
# A path that only SYSTEM and the administrators own. $userRights is what the root user may do: 'None' (the ssh keys
|
|
# and config), 'Read' (the account database) or 'ReadAndExecute' (the login programs it has to run).
|
|
function Set-LockedAcl($path, $forDirectory, $userRights) {
|
|
$acl = Get-Acl -LiteralPath $path
|
|
$acl.SetAccessRuleProtection($true, $false)
|
|
foreach ($rule in @($acl.Access)) { [void]$acl.RemoveAccessRuleSpecific($rule) }
|
|
$acl.SetOwner($admins)
|
|
$inherit = if ($forDirectory) { 'ContainerInherit,ObjectInherit' } else { 'None' }
|
|
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($system, 'FullControl', $inherit, 'None', 'Allow')))
|
|
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($admins, 'FullControl', $inherit, 'None', 'Allow')))
|
|
if ($userRights -ne 'None' -and $sid) {
|
|
$user = New-Object System.Security.Principal.SecurityIdentifier($sid)
|
|
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($user, $userRights, $inherit, 'None', 'Allow')))
|
|
}
|
|
Set-Acl -LiteralPath $path -AclObject $acl
|
|
}
|
|
|
|
# The website logs in with a key in the authorized_keys of the user. The user may keep keys of their own, so only
|
|
# the key of the website is put back.
|
|
function Restore-Keys($target, $source) {
|
|
$required = @(Get-Content -LiteralPath $source | Where-Object { $_.Trim() })
|
|
$blobs = $required | ForEach-Object { ($_ -split '\s+')[1] }
|
|
$keys = @()
|
|
if (Test-Path -LiteralPath $target) {
|
|
$keys = @(Get-Content -LiteralPath $target | Where-Object { $_.Trim() -and $blobs -notcontains ($_ -split '\s+')[1] })
|
|
}
|
|
[IO.File]::WriteAllText($target, (($keys + $required) -join "`n") + "`n")
|
|
}
|
|
|
|
# The shell configuration and the key the website logs in with must not change, the four texts may be edited.
|
|
$readOnly = '.zshrc', '.zshenv', 'authorized_keys'
|
|
|
|
# The permissions of the home directory and of the files in it that have to stay: the user may read them and write
|
|
# the ones that may change, and delete their own files, but not the home directory itself and not the files the
|
|
# website needs. Only SYSTEM and the administrators own them, so the user cannot take the permissions away again.
|
|
function Set-HomeAcl($path, $forDirectory, $writable) {
|
|
if (-not $sid) { return }
|
|
try {
|
|
$user = New-Object System.Security.Principal.SecurityIdentifier($sid)
|
|
$acl = Get-Acl -LiteralPath $path
|
|
$acl.SetAccessRuleProtection($true, $false)
|
|
foreach ($rule in @($acl.Access)) { [void]$acl.RemoveAccessRuleSpecific($rule) }
|
|
$acl.SetOwner($admins)
|
|
$none = [System.Security.AccessControl.InheritanceFlags]::None
|
|
$children = [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [System.Security.AccessControl.InheritanceFlags]::ObjectInherit
|
|
$inheritOnly = [System.Security.AccessControl.PropagationFlags]::InheritOnly
|
|
$full = [System.Security.AccessControl.FileSystemRights]::FullControl
|
|
$some = [System.Security.AccessControl.FileSystemRights]::ReadAndExecute -bor [System.Security.AccessControl.FileSystemRights]::Write
|
|
$read = [System.Security.AccessControl.FileSystemRights]::ReadAndExecute
|
|
$inherit = if ($forDirectory) { $children } else { $none }
|
|
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $system, $full, $inherit, 'None', 'Allow'))
|
|
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $admins, $full, $inherit, 'None', 'Allow'))
|
|
$userRights = if ($writable) { $some } else { $read }
|
|
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $user, $userRights, $none, 'None', 'Allow'))
|
|
if ($forDirectory) {
|
|
# the files in it are the user's own, they may delete them
|
|
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $user, $full, $children, $inheritOnly, 'Allow'))
|
|
}
|
|
Set-Acl -LiteralPath $path -AclObject $acl
|
|
} catch {
|
|
Write-Log "the permissions of $path are not set: $_"
|
|
}
|
|
}
|
|
|
|
# The home directory is the one from /etc/passwd, which the user cannot change, but which they can delete. A new one
|
|
# gets MSYS2's default files from /etc/skel, which MSYS2 itself only copies into a home that does not exist yet.
|
|
function New-Home($path) {
|
|
New-Item -ItemType Directory -Path $path | Out-Null
|
|
Set-HomeAcl $path $true $true
|
|
Get-ChildItem -LiteralPath "$msys\etc\skel" -Force | Copy-Item -Destination $path -Recurse -Force
|
|
}
|
|
|
|
try {
|
|
# The protected login path: the copy the sshd and the login shell run from. The user cannot write it, so even
|
|
# after they replace or remove their own copies in /usr/bin the server and the journey keep working. The trusted
|
|
# originals are in bin\; anything missing from opt/kik is put back.
|
|
if (Test-Path -LiteralPath $binDir) {
|
|
if (-not (Test-Path -LiteralPath $kik)) { New-Item -ItemType Directory -Path $kik | Out-Null }
|
|
foreach ($file in Get-ChildItem -LiteralPath $binDir -File -Force) {
|
|
$target = Join-Path $kik $file.Name
|
|
if (-not (Test-Path -LiteralPath $target) -or (Get-Item -LiteralPath $target -Force).Length -ne $file.Length) {
|
|
Copy-Item -LiteralPath $file.FullName -Destination $target -Force
|
|
Write-Log "restored opt/kik/$($file.Name)"
|
|
}
|
|
}
|
|
New-Item -ItemType Directory -Force -Path "$kik\dev\shm", "$kik\dev\mqueue" | Out-Null
|
|
Set-LockedAcl $kik $true 'ReadAndExecute'
|
|
# /dev is scratch: the user may write it so POSIX shared memory works, the rest of opt/kik stays read-only
|
|
if ($sid) { icacls "$kik\dev" /grant "*${sid}:(OI)(CI)M" | Out-Null }
|
|
}
|
|
|
|
# The account database and the ssh keys and config the SYSTEM server reads: the user may read passwd (programs
|
|
# map ids through it) but change none of it, and has no business in /etc/ssh at all.
|
|
$rootShell = '/opt/kik/zsh.exe'
|
|
$pw = "$msys\etc\passwd"
|
|
$lines = Get-Content $pw
|
|
if ($lines | Where-Object { $_ -match '^root:' -and $_ -notmatch ':/opt/kik/zsh\.exe$' }) {
|
|
($lines | ForEach-Object { if ($_ -match '^root:') { $_ -replace ':[^:]*$', ":$rootShell" } else { $_ } }) | Set-Content $pw -Encoding ascii
|
|
Write-Log 'set the root login shell to the protected zsh'
|
|
}
|
|
Set-LockedAcl $pw $false 'Read'
|
|
if (Test-Path "$msys\etc\ssh") { Set-LockedAcl "$msys\etc\ssh" $true 'None' }
|
|
|
|
# The whole installation otherwise belongs to the user, so pacman can write it. The home directory keeps its own
|
|
# protected permissions because they are set without inheritance.
|
|
if ($sid) { icacls $msys /grant "*${sid}:(OI)(CI)F" | Out-Null }
|
|
} catch {
|
|
Write-Log "the protected login path is not fully in place: $_"
|
|
}
|
|
|
|
if (-not (Test-Path -LiteralPath $HomeDir)) {
|
|
New-Home $HomeDir
|
|
Write-Log "created $HomeDir"
|
|
} elseif ((Get-Item -LiteralPath $HomeDir -Force).Attributes -band [IO.FileAttributes]::ReparsePoint) {
|
|
Remove-Anything $HomeDir
|
|
New-Home $HomeDir
|
|
Write-Log "replaced the link $HomeDir"
|
|
} else {
|
|
Set-HomeAcl $HomeDir $true $true
|
|
}
|
|
|
|
foreach ($copy in Get-ChildItem -LiteralPath $copies -Recurse -File -Force) {
|
|
$target = Join-Path $HomeDir $copy.FullName.Substring($copies.Length).TrimStart('\')
|
|
try {
|
|
$dir = Split-Path -Parent $target
|
|
if ((Test-Path -LiteralPath $dir) -and -not (Get-Item -LiteralPath $dir -Force).PSIsContainer) {
|
|
Remove-Anything $dir
|
|
}
|
|
if (-not (Test-Path -LiteralPath $dir)) { New-Item -ItemType Directory -Path $dir | Out-Null }
|
|
Set-HomeAcl $dir $true $true
|
|
if (Test-Path -LiteralPath $target) {
|
|
$item = Get-Item -LiteralPath $target -Force
|
|
if ($item.PSIsContainer -or $item.Attributes -band [IO.FileAttributes]::ReparsePoint) {
|
|
# anything but a file is replaced, so that the files are always the text files they should be
|
|
Remove-Anything $target
|
|
} else {
|
|
# the user may have locked the file; taking it back lets us write it
|
|
takeown /f $target /a | Out-Null
|
|
}
|
|
}
|
|
if ($copy.Name -eq 'authorized_keys') {
|
|
Restore-Keys $target $copy.FullName
|
|
} else {
|
|
Copy-Item -LiteralPath $copy.FullName -Destination $target -Force
|
|
}
|
|
Set-HomeAcl $target $false ($readOnly -notcontains $copy.Name)
|
|
# the user may have made the file read-only for themselves, the four texts are theirs to change
|
|
(Get-Item -LiteralPath $target -Force).IsReadOnly = $false
|
|
Write-Log "restored $($copy.Name)"
|
|
} catch {
|
|
Write-Log "failed $($copy.Name) : $_"
|
|
}
|
|
}
|
|
'@
|
|
|
|
if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
|
throw 'Run this in an elevated PowerShell.'
|
|
}
|
|
|
|
$system = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-18')
|
|
$admins = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-32-544')
|
|
|
|
$passwd = Get-Content "$msys\etc\passwd" -ErrorAction SilentlyContinue | Where-Object { $_ -like 'root:*' }
|
|
if (-not $passwd) { throw "No root user in $msys\etc\passwd." }
|
|
$sid = ($passwd -split ':')[4].Split(',')[-1]
|
|
|
|
# A previous install may have locked opt/kik, the account database and the ssh keys. The administrators keep
|
|
# full control over them, so reset the permissions to inherited now and let this run write freely; the restore
|
|
# task locks everything again at the end.
|
|
$prevEA = $ErrorActionPreference; $ErrorActionPreference = 'SilentlyContinue'
|
|
foreach ($p in "$msys\opt\kik", "$msys\etc\ssh", "$msys\etc\passwd") {
|
|
if (Test-Path -LiteralPath $p) { icacls $p /reset /t /q | Out-Null }
|
|
}
|
|
Get-ChildItem "$msys\etc\ssh", "$msys\etc\passwd" -Force | ForEach-Object { try { $_.IsReadOnly = $false } catch {} }
|
|
$ErrorActionPreference = $prevEA
|
|
|
|
function Set-LockedAcl($path, $forDirectory, $userRights) {
|
|
$acl = Get-Acl -LiteralPath $path
|
|
$acl.SetAccessRuleProtection($true, $false)
|
|
foreach ($rule in @($acl.Access)) { [void]$acl.RemoveAccessRuleSpecific($rule) }
|
|
$acl.SetOwner($admins)
|
|
$inherit = if ($forDirectory) { 'ContainerInherit,ObjectInherit' } else { 'None' }
|
|
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($system, 'FullControl', $inherit, 'None', 'Allow')))
|
|
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($admins, 'FullControl', $inherit, 'None', 'Allow')))
|
|
if ($userRights -ne 'None') {
|
|
$user = New-Object System.Security.Principal.SecurityIdentifier($sid)
|
|
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($user, $userRights, $inherit, 'None', 'Allow')))
|
|
}
|
|
Set-Acl -LiteralPath $path -AclObject $acl
|
|
}
|
|
|
|
# The DLLs an MSYS2 program loads, as Windows paths under C:\msys2.
|
|
function Get-Libraries($program) {
|
|
$paths = @()
|
|
$posix = ($program -replace '^C:\\msys2', '') -replace '\\', '/'
|
|
foreach ($line in & "$msys\usr\bin\ldd.exe" $posix 2>$null) {
|
|
foreach ($m in [regex]::Matches($line, '/usr/[^\s]+\.dll')) { $paths += $m.Value }
|
|
}
|
|
$paths | Select-Object -Unique | ForEach-Object { $msys + ($_ -replace '/', '\') }
|
|
}
|
|
|
|
if ($Uninstall) {
|
|
foreach ($t in $restoreTask, $sshdTask) { Unregister-ScheduledTask -TaskName $t -Confirm:$false -ErrorAction SilentlyContinue }
|
|
if (Test-Path -LiteralPath $installDir) { Remove-Item -LiteralPath $installDir -Recurse -Force }
|
|
if (Test-Path -LiteralPath $kik) {
|
|
icacls $kik /reset /t /q 2>$null | Out-Null
|
|
Remove-Item -LiteralPath $kik -Recurse -Force -ErrorAction SilentlyContinue
|
|
}
|
|
# the installation's own cygrunsrv sshd takes over again
|
|
$pw = "$msys\etc\passwd"
|
|
(Get-Content $pw | ForEach-Object { if ($_ -match '^root:') { $_ -replace ':[^:]*$', ':/usr/bin/zsh' } else { $_ } }) | Set-Content $pw -Encoding ascii
|
|
icacls $pw /reset | Out-Null
|
|
if (Test-Path "$msys\etc\ssh") { icacls "$msys\etc\ssh" /reset /t /q 2>$null | Out-Null }
|
|
Set-Service $service -StartupType Automatic -ErrorAction SilentlyContinue
|
|
Start-Service $service -ErrorAction SilentlyContinue
|
|
Write-Host "Removed the tasks and $installDir; the $service service serves ssh again."
|
|
return
|
|
}
|
|
|
|
$resources = Join-Path $PSScriptRoot '..\resources'
|
|
if (-not (Get-ChildItem -LiteralPath $resources -Recurse -File -Force)) { throw "No files in $resources." }
|
|
|
|
$prepared = Get-Item -LiteralPath $installDir -Force -ErrorAction SilentlyContinue
|
|
if ($prepared -and $prepared.Attributes -band [IO.FileAttributes]::ReparsePoint) { throw "$installDir is a link, refusing to install." }
|
|
|
|
New-Item -ItemType Directory -Force -Path $copiesDir, $binDir | Out-Null
|
|
|
|
# Only SYSTEM and the administrators may read or write the copies, the trusted login programs and the scripts.
|
|
$acl = New-Object System.Security.AccessControl.DirectorySecurity
|
|
$acl.SetAccessRuleProtection($true, $false)
|
|
foreach ($owner in $system, $admins) {
|
|
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($owner, 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow')))
|
|
}
|
|
$acl.SetOwner($admins)
|
|
Set-Acl -LiteralPath $installDir -AclObject $acl
|
|
|
|
foreach ($item in Get-ChildItem -LiteralPath $installDir -Recurse -Force) {
|
|
if ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) { throw "$($item.FullName) is a link, refusing to install." }
|
|
}
|
|
Remove-Item -LiteralPath $copiesDir -Recurse -Force
|
|
New-Item -ItemType Directory -Force -Path $copiesDir | Out-Null
|
|
Copy-Item -Path (Join-Path $resources '*') -Destination $copiesDir -Recurse -Force
|
|
|
|
# The Administrator logs in with the key of the people who deploy, which is in the resources. The keys that are
|
|
# already there stay.
|
|
$adminKeys = 'C:\Users\Administrator\.ssh\authorized_keys'
|
|
$required = @(Get-Content -LiteralPath (Join-Path $resources 'admin\.ssh\authorized_keys') | Where-Object { $_.Trim() })
|
|
$keys = @()
|
|
if (Test-Path -LiteralPath $adminKeys) { $keys = @(Get-Content -LiteralPath $adminKeys | Where-Object { $_.Trim() }) }
|
|
if (-not ($keys | Where-Object { ($_ -split '\s+')[1] -eq ($required[0] -split '\s+')[1] })) {
|
|
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $adminKeys) | Out-Null
|
|
[IO.File]::WriteAllText($adminKeys, (($keys + $required) -join "`n") + "`n")
|
|
}
|
|
[IO.File]::WriteAllText((Join-Path $installDir 'restore.ps1'), $restoreScript)
|
|
|
|
# The trusted login programs: the sshd, its helpers, the login shell and cat, with every DLL they load. The restore
|
|
# task puts these into opt/kik, which the user cannot write.
|
|
Remove-Item "$binDir\*" -Force -ErrorAction SilentlyContinue
|
|
$files = @{}
|
|
foreach ($b in $loginBinaries) {
|
|
if (-not (Test-Path -LiteralPath $b)) { throw "$b does not exist." }
|
|
$files[$b] = $true
|
|
foreach ($lib in Get-Libraries $b) { $files[$lib] = $true }
|
|
}
|
|
foreach ($f in $files.Keys) { Copy-Item -LiteralPath $f -Destination $binDir -Force }
|
|
|
|
# The sshd reads its configuration and finds its helpers in opt/kik; nothing here depends on the user's /usr.
|
|
$sshdConfig = "$msys\etc\ssh\sshd_config"
|
|
Set-Content -LiteralPath $sshdConfig -Encoding ascii -Value @(
|
|
'# The website logs in as root, the deployers as Administrator. Both come in over the websocket bridge.',
|
|
'Port 22',
|
|
'AllowUsers root Administrator',
|
|
'# Files are protected by Windows ACLs; under MSYS2''s noacl mounts the POSIX modes sshd checks are fake.',
|
|
'StrictModes no',
|
|
'Subsystem sftp internal-sftp',
|
|
'# The server and its per-connection helpers are the protected copies, not the ones the user can replace.',
|
|
'SshdSessionPath /opt/kik/sshd-session.exe',
|
|
'SshdAuthPath /opt/kik/sshd-auth.exe'
|
|
)
|
|
|
|
# The sshd runs as SYSTEM from the protected copy, with opt/kik first on the path so its own DLLs and the session's
|
|
# programs (zsh, cat) are the protected ones. The log stays in the install directory, out of the user's reach.
|
|
Set-Content -LiteralPath $sshdBat -Encoding ascii -Value @(
|
|
'@echo off',
|
|
'set PATH=C:\msys2\opt\kik;C:\msys2\usr\bin;%PATH%',
|
|
'C:\msys2\opt\kik\sshd.exe -D -e > C:\ProgramData\kik-restore\sshd.log 2>&1'
|
|
)
|
|
|
|
# The installation's own cygrunsrv sshd service would fight for port 22; the scheduled task owns the sshd instead.
|
|
Set-Service $service -StartupType Disabled -ErrorAction SilentlyContinue
|
|
Stop-Service $service -Force -ErrorAction SilentlyContinue
|
|
|
|
$atSystem = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
|
|
|
|
$sshdAction = New-ScheduledTaskAction -Execute $sshdBat
|
|
$sshdSettings = New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew -ExecutionTimeLimit (New-TimeSpan -Seconds 0) -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
|
|
Register-ScheduledTask -TaskName $sshdTask -Action $sshdAction -Trigger (New-ScheduledTaskTrigger -AtStartup) -Principal $atSystem -Settings $sshdSettings -Force | Out-Null
|
|
|
|
$restoreAction = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$installDir\restore.ps1`" -HomeDir `"$HomeDir`""
|
|
$restoreTriggers = @(
|
|
New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 30) -RepetitionDuration (New-TimeSpan -Days 3650)
|
|
New-ScheduledTaskTrigger -AtStartup
|
|
)
|
|
$restoreSettings = New-ScheduledTaskSettingsSet -StartWhenAvailable -MultipleInstances IgnoreNew -ExecutionTimeLimit (New-TimeSpan -Minutes 10) -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 5) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
|
|
Register-ScheduledTask -TaskName $restoreTask -Action $restoreAction -Trigger $restoreTriggers -Principal $atSystem -Settings $restoreSettings -Force | Out-Null
|
|
|
|
# The restore task builds opt/kik and sets every permission; run it before starting the sshd that depends on it.
|
|
Start-ScheduledTask -TaskName $restoreTask
|
|
Start-Sleep -Seconds 6
|
|
$waited = 0
|
|
while ((Get-ScheduledTask -TaskName $restoreTask).State -eq 'Running' -and $waited -lt 60) { Start-Sleep 2; $waited += 2 }
|
|
Start-ScheduledTask -TaskName $sshdTask
|
|
Start-Sleep -Seconds 4
|
|
|
|
Write-Host "The sshd runs as SYSTEM from C:\msys2\opt\kik (task '$sshdTask'); '$restoreTask' keeps it and the home directory in shape."
|
|
Get-Content (Join-Path $installDir 'restore.log') -Tail 10 -ErrorAction SilentlyContinue
|
|
Get-CimInstance Win32_Process -Filter "Name='sshd.exe'" | ForEach-Object { $_.CommandLine }
|