Ferreteria/v0.6/clade/IO/Aspect/Connx/Plug/Shell/Remote/SSH

From Woozle Writes Code
< Ferreteria‎ | v0.6‎ | clade‎ | IO‎ | Aspect‎ | Connx‎ | Plug‎ | Shell‎ | Remote
Jump to navigation Jump to search
clade: IO\Aspect\Connx\Plug\Shell\Remote\SSH
Clade Family
Remote SSH (none)
Clade Aliases
Alias Clade
ActionIface Sys\Events\ItWent
Base* [ca,i] IO\Aspect\Connx\Plug\Shell\Remote
BufferIface IO\Aspect\Connx\Buffer
CommOpIface Sys\Events\ItWent\CommOp
CredsIface IO\Aspect\Creds
ProcIface IO\Aspect\Connx\Process
QRes* [c,i] Data\Mem\QVar\Res
SockIface IO\Aspect\Socket
Tunnel* [c,i] IO\Aspect\Connx\aux\Tunnel
Subpages

About

  • Purpose: a secure shell (ssh) connection to a remote machine

History

  • 2024-11-02 started
  • 2024-11-19 moved from [WFe]Config\Def\Connex\Shell -> [WFe]IO\Connex\Shell
  • 2024-11-26 moved from [WFe]IO\Connex\Shell\xSecure -> [WFe]IO\Connex\Aux\ssh\xShell
  • 2025-03-15 moved from [WFe]IO\Connx\Aux\ssh\xRemote -> [WFe]IO\Connx\Shell\Remote\xSSH
    • ...and re-parented from [WFe]IO\Connx\xShell ⇒ [WFe]IO\Connx\Shell\Remote
  • 2025-03-16 Removing the $oICreds parameter from NewTunnel(), because as far as I can tell it's just supposed to be the same creds stored in Creds()
    • Also not sure if the $nPortLocal parameter still makes sense... shouldn't that be in the Creds data as well?
  • 2025-05-27 more re-namespacing

Code

interface iSSH extends BaseIface {
    // OBJECTS
    function OCred() : CredsIface;
}
class cSSH extends BaseClass implements iSSH {

    // ++ CONFIG ++ //

    public function SType()             : string { return 'ssh plug'; }

    // -- CONFIG -- //
    // ++ OBJECTS ++ //

    public function OCred() : CredsIface { return $this->OSock()->OCred(); }

    // -- OBJECTS -- //
    // ++ LIFECYCLE ++ //

    protected function ActualOpen() : ActionIface {

        $oAct = $this->HowItWent();

        // [+STANDARD CODE]: get connection specs

        $oCred = $this->OCred();
        $oSock = $this->OSock();
        $oHost = $oSock->OHost();
        $sUser = $oCred->QSUser()->GetIt();
        $sHost = $oHost->QSAddr()->GetIt();
        $onPort = $oHost->QIPort();

        // [-STANDARD CODE]

        if ($onPort->HasIt()) {
            $nPort = $onPort->GetIt();
            $ftRem = "$sUser@$sHost:$nPort";
        } else {
            $ftRem = "$sUser@$sHost";
        }

        echo "Opening SSH connection to [$ftRem]...".CRLF;
        try {
            if ($onPort->HasIt()) {
                $rSSH = @ssh2_connect($sHost, $nPort);
            } else {
                $rSSH = @ssh2_connect($sHost); // use ssh2_connect's default port
            }
        } catch (\Exception $e) {
            $rSSH = FALSE;
            echo $e;
            $this->AmHere('TODO 2025-12-07: better diagnostics');
        }
        if ($rSSH === FALSE) {
            $arErr = error_get_last();
            $oScrn = self::Screen();
            echo $oScrn->ErrorIt('Connection error').': '.$arErr['message'].CRLF;
            die();
        }
        $this->QNative()->SetIt($rSSH);

        $ftUser = self::Screen()->GreenIt($sUser);
        echo "Authenticating remote user '$ftUser'...".CRLF;
        @$rok = ssh2_auth_agent($rSSH,$sUser);
        $ok = ($rok !== FALSE);
        $oAct->SetOkay($ok);
        if ($ok) {
            $oAct->AddMsgString("User authenticated.");
        } else {
            $oAct->AddMsgString("SSH connection failed.");
        }
        return $oAct;
    }
    protected function ActualShut() : ActionIface {
        $rConn = $this->QNative()->GetIt();
        $ok = ssh2_disconnect($rConn);
        $oAct = $this->HowItWent();
        $oAct->SetOkay($ok);
        return $oAct;
    }

    // -- LIFECYCLE -- //
    // ++ UI ++ //

    public function DescribeInline() : string { return 'ssh'; }

    // -- UI -- //
    // ++ OBJECTS ++ //

    private $osNat = NULL; protected function QNative() : QResIface { return $this->osNat ?? ($this->osNat = QResClass::AsNew()); }

    public function NewTunnel(CredsIface $oICreds, ?int $nPortLocal=NULL) : TunnelIface {
        return new TunnelClass($this->Creds(),$oICreds,$nPortLocal);
    }

    // -- OBJECTS -- //
    // ++ ACTION ++ //

    public function DoCommand(string|array $saCmd, BufferIface $oBuff, ?CommOpIface $oAct=NULL) : CommOpIface {
        $sCmdFull = $this->WrapCommand($saCmd);
        $oAct = parent::DoCommand($sCmdFull,$oBuff,$oAct);

        return $oAct;
    }
    public function RunProcess(string|array $saCmd) : ProcIface {
        $sCmdFull = $this->WrapCommand($saCmd);
        return parent::RunProcess($sCmdFull);
    }

    // -- ACTION -- //
    // ++ FIGURING ++ //

    // 2025-03-29 FUTURE: maybe this should always return an array -- but that's harder to debug.
    protected function WrapCommand(string|array $saCmd) : string {
        // [+STANDARD CODE]: get connection specs

        $oCred = $this->OCred();
        $oSock = $this->OSock();
        $oHost = $oSock->OHost();
        $sUser = $oCred->QSUser()->GetIt();
        $sHost = $oHost->QSAddr()->GetIt();
        $onPort = $oHost->QIPort();

        // [-STANDARD CODE]

        if ($onPort->HasIt()) {
            $nPort = $onPort->GetIt();
            $sPort = ' -p '.$nPort;
        } else {
            $sPort = '';
        }

        $sCmdSafe = escapeshellarg($saCmd);

        $sCmdFull = "ssh$sPort $sUser@$sHost $sCmdSafe";
        return $sCmdFull;
    }
}