#!/usr/bin/env pwsh

# set -e
$ErrorActionPreference = "Stop"

# set -x
#Set-PSDebug -Trace 2
#$VerbosePreference="Continue"

$AdminAuthorizedKeys = "${Env:ProgramData}\ssh\administrators_authorized_keys"

function Repair-AuthorizedKeyPermission {
    Write-Verbose "Setting ssh file permissions ..."
    Repair-AdminAuthorizedKeyPermission
    Repair-UserAuthorizedKeyPermission
    Write-Verbose "All permissions set."
}

function Repair-AdminAuthorizedKeyPermission {
    Write-Verbose "Setting permissions on `$Env:ProgramData\ssh\ ..."

    # add self for the purposes of this script
    icacls.exe "${Env:ProgramData}\ssh\" `
        /t /grant "${Env:UserName}:(F)"

    # Create whitelist for 'sudoer' authorized keys
    if (-not (Test-Path -Path $AdminAuthorizedKeys)) {
        $null = New-Item -Path $AdminAuthorizedKeys -Type 'File'
    }

    # chmod -R go-rwx /etc/ssh/
    # (remove inherited acls)
    icacls.exe "${Env:ProgramData}\ssh\" /t /inheritance:r
    icacls.exe "${Env:ProgramData}\ssh\" /remove "Authenticated Users"

    # chown -R root:wheel
    # (add admins and system back with perms to inherit new files)
    icacls.exe "${Env:ProgramData}\ssh\" `
        /grant "Administrators:(OI)(CI)(F)" `
        /grant "SYSTEM:(OI)(CI)(F)"
    icacls.exe $AdminAuthorizedKeys `
        /grant "Administrators:(F)" `
        /grant "SYSTEM:(F)"
    icacls.exe "${Env:ProgramData}\ssh\logs" `
        /grant "Administrators:(F)" `
        /grant "SYSTEM:(F)"

    # (explicitly add public access to special files)
    icacls.exe "${Env:ProgramData}\ssh\" `
        /grant "Authenticated Users:(RX)"
    icacls.exe "${Env:ProgramData}\ssh\sshd.pid" `
        /grant "Administrators:(F)" `
        /grant "SYSTEM:(F)" `
        /grant "Authenticated Users:(RX)"
    icacls.exe "${Env:ProgramData}\ssh\sshd_config" `
        /grant "Administrators:(F)" `
        /grant "SYSTEM:(F)" `
        /grant "Authenticated Users:(RX)"

    # add self for the purposes of this script
    icacls.exe "${Env:ProgramData}\ssh\" /t /remove "${Env:UserName}"

    Write-Verbose "System permissions set."
}

function Repair-UserAuthorizedKeyPermission {
    Write-Verbose "Setting permissions on `$HOME\.ssh\ ..."

    # mkdir -p ~/.ssh/
    $null = New-Item -Type Directory -Force -Path "$HOME\.ssh\"

    # touch ~/.ssh/authorized_keys
    if (-not (Test-Path -Path "$HOME\.ssh\authorized_keys")) {
        $null = New-Item -Type 'File' -Path "$HOME\.ssh\authorized_keys"
    }

    # chown -R "$(id -u -n)":"$(id -u -n)"
    # (add yourself as a non-inherited user)
    icacls.exe "$HOME\.ssh\" /t /grant "${Env:UserName}:(F)"

    # chmod -R go-rwx ~/.ssh/
    # (remove inherited acls)
    icacls.exe "$HOME\.ssh\" /t /inheritance:r

    Write-Verbose "User permissions set."
}

# Clean up non-key lines, preserving comments and newlines
function Repair-AuthorizedKeyFile($Path) {
    Write-Verbose "Filtering invalid keys entries from ${Path} ...";

    $HasBadLine = $false;
    $FixedPath = "${Path}.fixed.txt";

    # truncates or creates the file
    $null = New-Item -Type 'File' -Force -Path $FixedPath
    foreach ($Line in [System.IO.File]::ReadLines($Path)) {
        $Line = $Line.Trim()
        if ($Line.Length -eq 0) {
            $null = Add-Content -Path $FixedPath -Value $Line
            continue
        }
        if ($Line.StartsWith('#')) {
            $null = Add-Content -Path $FixedPath -Value $Line
            continue
        }
        if ($Line.StartsWith('ssh-')) {
            $null = Add-Content -Path $FixedPath -Value $Line
            continue
        }
        if ($Line.StartsWith('ecdsa-')) {
            $null = Add-Content -Path $FixedPath -Value $Line
            continue
        }
        if (-not $HasBadLine) {
            $HasBadLine = $true
            Write-Host "Skipping lines that do not begin with ssh-, ecdsa- or #:" -ForegroundColor Red -BackgroundColor Black
        }
        Write-Host "    $Line" -ForegroundColor Yellow -BackgroundColor Black
    }

    if ($HasBadLine) {
        Write-Host "Unrecognized line formats were filtered out."

        $FixedPath
        return
    }

    Write-Verbose "No invalid keys were filtered out."
    $null = Remove-Item -Path $FixedPath -Force -ErrorAction Ignore

    $Path
}

function Add-AuthorizedKeyFile($Path) {
    # Give yourself permission to the admin authorized_keys file
    icacls.exe $AdminAuthorizedKeys /grant "${Env:UserName}:(F)"

    Get-Content $Path | Add-Content -Path $AdminAuthorizedKeys

    # Remove your permission to the admin authorized_keys file
    icacls.exe $AdminAuthorizedKeys /remove $Env:UserName | Out-Null

    Get-Content $Path | Add-Content -Path "$HOME\.ssh\authorized_keys"
}

function Add-AuthorizedKey($UrlOrPath) {
    Write-Verbose "Inspecting ${UrlOrPath}..."

    $IsHttp = $UrlOrPath.StartsWith("HTTP://", 'CurrentCultureIgnoreCase')
    $IsHttps = $UrlOrPath.StartsWith("HTTPS://", 'CurrentCultureIgnoreCase')

    if (Test-Path -Path $UrlOrPath -Type 'Leaf') {
        $FixedPath = Repair-AuthorizedKeyFile $UrlOrPath
        $null = Add-AuthorizedKeyFile $FixedPath

        $IsTmp = $UrlOrPath.EndsWith(".TMP.TXT", 'CurrentCultureIgnoreCase')
        if ($IsTmp) {
            $null = Remove-Item -Path "${TmpKeys}.tmp.txt" -Force -ErrorAction Ignore
        }
        return
    }
    if (-not ($IsHttps -or $IsHttp)) {
        throw "'$UrlOrPath' does not exist as a file and doesn't look like a URL (no https://)"
    }

    $TmpKeys = "new_authorized_keys.tmp.txt"

    curl.exe -f -sS $UrlOrPath | Out-File -Force "${TmpKeys}.partial"
    $null = Move-Item -Force "${TmpKeys}.partial" $TmpKeys

    if ($IsHttp) {
        Write-Host ""
        Write-Host "Error: Cowardly refusing to add file downloaded over plain http" -ForegroundColor Yellow -BackgroundColor black
        Write-Host ""
        Write-Host "Please manually inspect ${TmpKeys} and then run"
        Write-Host "    ssh-authorize ${TmpKeys}" -ForegroundColor Yellow -BackgroundColor black
        Write-Host ""
        1
        return
    }

    $FixedPath = Repair-AuthorizedKeyFile $TmpKeys
    $null = Add-AuthorizedKeyFile $FixedPath
    $null = Remove-Item -Path $TmpKeys -Force -ErrorAction Ignore
    $null = Remove-Item -Path $FixedPath -Force -ErrorAction Ignore
}

function Debug-AuthorizedKeyPermission {
    Write-Verbose "Showing ssh file permissions ..."

    # ls -lA /etc/sshd/
    icacls.exe "${Env:ProgramData}\ssh\" /t | Write-Host

    # ls -lA ~/.ssh/
    icacls.exe "$HOME\.ssh\" /t | Write-Host

    # if you really mess things up you can
    # move the 'ssh' and '.ssh' folders into
    # '$HOME\AppData\Local\Temp' and reboot

    Write-Verbose "All ssh file permissions shown."
}

function Show-Help() {
    Write-Host ""
    Write-Host "USAGE"
    Write-Host "    ssh-authorize <url-or-path>" -ForegroundColor Yellow -BackgroundColor black
    Write-Host ""
    Write-Host "EXAMPLES"
    Write-Host "    ssh-authorize https://github.com/johndoe.keys"
    Write-Host "    ssh-authorize http://192.168.1.101:3000/authorized_keys"
    Write-Host "    ssh-authorize ./alice.pub.txt ./bob.pub.txt"
    Write-Host ""
    Write-Host "LOCAL IDENTIFY FILES"
    $Pubs = Get-ChildItem -Path "$HOME\.ssh\" -Filter '*.pub'
    if ($Pubs.Length -eq 0) {
        Write-Host "    (no files match ~/.ssh/*.pub)"
    }
    foreach ($Pub in $Pubs) {
        $RelPath = [System.IO.Path]::GetRelativePath($HOME, $Pub.FullName)
        Write-Host "    ${RelPath}"
    }
}

function Main($Paths) {
    $null = Repair-AuthorizedKeyPermission
    #Debug-AuthorizedKeyPermission | Write-Host

    if ($Paths.Length -eq 0 -or $Paths[0] -eq "help" -or $Paths[0] -eq "--help") {
        Show-Help | Write-Host

        Write-Host ""
        if ($Paths.Length -eq 0) {
            1
            return
        }

        0
        return
    }
    $Uri = $Paths[0]

    Write-Host ""
    Write-Host "Fetching keys from ${Uri}"

    $null = Add-AuthorizedKey $Uri

    Write-Host ""
    Write-Host "Successfully copied the given ssh keys into:"
    Write-Host "    `$HOME\.ssh\authorized_keys"
    Write-Host "    `${Env:ProgramData}\ssh\administrators_authorized_keys"
    Write-Host ""

    0
    return
}

$Status = Main $Args
exit $Status
