a hX@sjddlZddlZddlZddlZddlZddlZddlZddlZddl Z ddl Z ddl m Z ddl mZmZmZmZmZddlmZddlmZddlmZddlmZddlmZmZdd lmZdd l m!Z!dd l"m#Z#m$Z$dd l%m&Z&dd l'm(Z(m)Z)m*Z*ddl+m,Z,ddl-m.Z.m/Z/ddl0m1Z1m2Z2ddl3m4Z4e5dZ6e dddZ7ee8dddZ9Gddde)Z:ddddej;Z;Gddde2Ze8e?ee8ee8fd"d#d$Z@ee8e8e e8e?ee8ee8fd%d&d'ZAee8ee8e?ee8ee8fd(d)d*ZBGd+d,d,e,ZCGd-d.d.e1ZDddd/d0ZEddd1d2ZFeGd3krfeFdS)4N)Path)DictIterableLiteralOptionalSequence) polyfills)ferny) bootloader)BridgeBeibootHelper)parse_os_release setup_logging)ChannelRoutingRule)PackagesChannel) JsonObjectget_str)supported_oses)PackagesPackagesLoaderpatch_libexecdir)Peer)CockpitProblemCockpitProtocolError)Router RoutingRule)StdioTransportzcockpit.beibootreturnc Cstjtjd}|}tjd}|dur:tj d}tj |ddt |d}t d|z:||krzt d t|jd @st d tWn4ttfyt d |||d Yn0|S)zCreate askpass executable We need this for the flatpak: ssh and thus the askpass program run on the host (via flatpak-spawn), not the flatpak. Thus we cannot use the shipped cockpit-askpass program. zinteraction_client.pyZXDG_CACHE_HOMENz~/.cacheT)exist_okzcockpit-client-askpasszChecking if %s exists...z. ... it exists but is not the same version...@z; ... it has the correct contents, but is not executable...z ... writing contents.i) importlibZ resourcesfilesr __name__ read_bytesosenvirongetpath expandusermakedirsrloggerdebug ValueErrorstatst_modeFileNotFoundError write_byteschmod)Zsrc_pathZsrc_dataZxdg_cache_homeZ dest_pathr23/usr/lib/python3.9/site-packages/cockpit/beiboot.pyensure_ferny_askpass1s(         r4ccs<tD].}|jD]"}|jdvrt|jtr|jVqqdS)N) path-existspath-not-exists)rZload_manifestsZ conditionsname isinstancevaluestr)Zmanifest conditionr2r2r3get_interesting_filesSs  r<c@sFeZdZUeeefed<eeedddZeeefdddZ dS) ProxyPackagesLoader file_status)r;r9rcCsJt|tsJ||jvsJ|dkr.|j|S|dkrB|j| StdS)Nr5r6)r8r:r>KeyError)selfr;r9r2r2r3check_condition]s  z#ProxyPackagesLoader.check_conditionr>cCs ||_dSNrB)r@r>r2r2r3__init__hszProxyPackagesLoader.__init__N) r" __module__ __qualname__rr:bool__annotations__objectrArDr2r2r2r3r=Zs  r=z import os def report_exists(files): command('cockpit.report-exists', {name: os.path.exists(name) for name in files}) z import os def check_os_release(_argv): try: with open('/etc/os-release') as f: command('cockpit.check-os-release', f.read()) except OSError: command('cockpit.check-os-release', "") z import os def force_exec(argv): try: os.execvp(argv[0], argv) except OSError as e: command('cockpit.fail-no-cockpit', str(e)) ) report_existscheck_os_release force_execcsJeZdZUded<edfdd ZeddddZd d d d ZZ S) DefaultRoutingRulez Peer | Nonepeer)routercst|dSrC)superrD)r@rO __class__r2r3rDszDefaultRoutingRule.__init__)optionsrcCs|jSrC)rN)r@rSr2r2r3 apply_ruleszDefaultRoutingRule.apply_ruleNrcCs|jdur|jdSrC)rNcloser@r2r2r3shutdowns zDefaultRoutingRule.shutdown) r"rErFrHrrDrrTrW __classcell__r2r2rQr3rMs rMc@s`eZdZUdZeed<eeedddZeeeeedddZ ee e e ed d d d Z d S) AuthorizeResponder)z ferny.askpasscockpit.report-existscockpit.fail-no-cockpitcockpit.check-os-releaserO)rObasic_passwordcCs||_||_|du|_dSrC)rOr]have_basic_password)r@rOr]r2r2r3rDszAuthorizeResponder.__init__)messagesprompthintrcsvtd||||jrHd|vrH|jdurHtd||j}d|_|S|dkrTdStd|}td|}i}|r|r|d}|dkrtd |d S|d |dd |d <|d|d<t dt } d| } | d t | } |jj| fd|||dd|IdH} | | sBtd| d| | | } t | } tdt| | S)Nz3AuthorizeResponder: prompt %r, messages %r, hint %rz password:z=AuthorizeResponder: sending Basic auth password for prompt %rZnonez$\n(\w+) key fingerprint is ([^.]+)\.zauthenticity of host '([^ ]+) z 127.0.0.1z,auto-accepting fingerprint for 127.0.0.1: %sZyes z login-datazhost-keydefault-zX-Conversation F)timeoutr_r`raZechozAuthorizeResponder: response z does not match challenge zReturning a %d chars response)r*r+r^lowerr]researchgroupr$getpidtimebase64Z b64encodeencodedecoderOZrequest_authorization startswithr removeprefixstrip b64decodelen)r@r_r`raZreplyZfp_matchZ host_matchargshostnameZ challenge_idZchallenge_prefix challengeresponseZb64r2r2r3 do_askpasssN          zAuthorizeResponder.do_askpassN)commandrvfdsstderrrc std||||dkrJ|\}tt|d|j_|jjdt|jt g|dkrbt d|dd|dkrt |dtd td t t D]0}t fd d |Drtd |dSqtdz8td}t |}Wdn1s0YWn4ty>} ztd| WYd} ~ dSd} ~ 00d|dkr|d|dkr|td|dSdddddd} t d| ddS)NzGot ferny command %s %s %srZ)loaderrr[z no-cockpit)messager\z'cockpit.check-os-release: remote OS: %rz,cockpit.check-os-release: supported OSes: %rc3s |]\}}||kVqdSrC)r&).0kvZ remote_osr2r3 z7AuthorizeResponder.do_custom_command..z8cockpit.check-os-release: remote matches supported OS %rz$cockpit.check-os-release: remote: %rz/etc/os-releasezIfailed to read local /etc/os-release, skipping OS compatibility check: %sZIDZ VERSION_IDz7cockpit.check-os-release: remote OS matches local OS %rNAME?rc) unsupported)r*r+rr=rOpackagesZ routing_rulesinsertrrrr rallitemsopenreadOSErrorZwarningr&) r@r{rvr|r}r>ZosinfofZthis_oserr2rr3do_custom_commands6       . , &z$AuthorizeResponder.do_custom_command)r"rErFZcommandsrrHrr:rDrztuplelistintrr2r2r2r3rYs DrY)commentrcCsddd|fdfS)NZpython3z-icz# r2r2)rr2r2r3python_interpreter sr)cmddest ssh_askpassssh_optsrcGs~|d\}}}|rN|dsN|drB|drB|dd}d||g}n|g}dg||t|Rd|d d ffS) N:[]rbz-pZsshz SSH_ASKPASS=z DISPLAY=xzSSH_ASKPASS_REQUIRE=force) rpartitionisdigitendswithrqshlexjoin)rrrrhost_port destinationr2r2r3via_sshs&  rrenvrcCs ddgdd|D|RdfS)Nz flatpak-spawnz--hostcss|]}d|VqdS)z--env=Nr2)rZkvr2r2r3r'rz flatpak_spawn..r2r2)rrr2r2r3 flatpak_spawn$s rcseZdZUded<eeejdfdd Zdddd Z ddd d Z ddd d Z e ee eddddZ ddddZeddddZZS)SshPeerzMLiteral["always"] | Literal["never"] | Literal["supported"] | Literal["auto"]mode)rOrrvcs@||_|j|_t|_t|jjd|_d|_t |dS)Nzuser-known-hosts) r remote_bridgetempfileZTemporaryDirectoryZtmpdirrr7known_hosts_filer]rPrD)r@rOrrvrQr2r3rD0s  zSshPeer.__init__Nrcs.tjdr|IdHn|IdHdS)Nz/.flatpak-info)r$r'existsconnect_from_flatpakconnect_from_bastion_hostrVr2r2r3do_connect_transport8s zSshPeer.do_connect_transportcsNtd\}}|jdkr*t||jt\}}t||\}}|||IdHdS)Ncockpit-bridgeZ localhost)rrrr4rboot)r@rrr2r2r3r?s   zSshPeer.connect_from_flatpakcsXd}ddg}|jdIdH}t|d}|drt|dd}|d\}}}|d\}}|_|rt d ||d |g7}|jdur|dd g7}t d \} } t d } t | tsJt| } | st d| td} | dur|dd| g7}|dur(|j||dd|jg7}t| |j| g|R\} } || | IdHdS)Nz-ozNumberOfPasswordPrompts=1*ryzBasic rz,got username %s and password from Basic authz-lzPasswordAuthentication=norz${libexecdir}/cockpit-askpassz+Could not find cockpit-askpass helper at %rZCOCKPIT_SSH_KNOWN_HOSTS_FILEzGlobalKnownHostsFile=zUserKnownHostsfile=)rOZrequest_authorization_objectrrqrnrtrp partitionr]r*r+rrr8r:rrerrorr$getenvr write_textrrr)r@ known_hostsrvZauthryZdecodedZ user_passwordruserrrZaskpassrZenv_known_hostsr2r2r3rLs6           z!SshPeer.connect_from_bastion_hostrcst|}tt|j|j|g}td|||j|||ddIdH}|j dkr`ddgffg}nN|j dkrzddgffg}n4|j d krddgffd gffg}n|j d ksJg}t j g|d t t gf|jtd }|||IdHdS)Nz Launching command: cmd=%s env=%sT)r}Zstart_new_sessionautoZtry_execralwaysrL supportedrKneverrJ)Zgadgets)r r ZInteractionAgentrYrOr]r*r+Zspawnrr Zmake_bootloaderrr<ZstepsBEIBOOT_GADGETSwritero communicate)r@rrZbeiboot_helperZagentZ transportZexec_cockpit_bridge_stepsZstage1r2r2r3rus.   z SshPeer.bootcCs d|_dSrC)r]rVr2r2r3do_superuser_init_doneszSshPeer.do_superuser_init_done)rrcCs|td||jdut|ddr^t|d}|jdur^td|jd||jdd|_dStd|jd|d d dS) Nz*SshPeer.do_authorize: %r; have password %srxzplain1:cookiez-SshPeer.do_authorize: responded with password authorize)r{rryz0SshPeer.do_authorize: authentication-unavailablezauthentication-unavailable)r{rproblem)r*r+r]rrq write_control)r@rrr2r2r3 do_authorizes    zSshPeer.do_authorize)r"rErFrHrr:argparse NamespacerDrrrrrrrrrXr2r2rQr3r-s  )rcsbeZdZUdZeeed<eed<ej dfdd Z dddd Z d d Z ddd d Z ZS) SshBridgeNrssh_peer)rvcs2t|}t|gt||j||_|j|_dSrC)rMrPrDrrrrN)r@rvZrulerQr2r3rDszSshBridge.__init__rcCsdSrCr2rVr2r2r3 do_send_initszSshBridge.do_send_initcCstd||j|dS)NzSshBridge.do_init: %r)r*r+rr)r@rr2r2r3do_inits zSshBridge.do_initcCs|j|jdSrC)rZadd_done_callbackrUrVr2r2r3 setup_sessionszSshBridge.setup_session)r"rErFrrrrHrrrrDrrrrXr2r2rQr3rs  rc sdtdt|}tt|zt|jIdH}|jj r`|j dddd|jj id| di}t|ts|j dd d d WdSt|tsJd |d <|jrt|jj|d<| ||jWnRtjy}ztjt|}td||t|tjrd}nRt|tjr,d}n>t|tjr@d}n*t|trRd}nt|tjrfd}nd}|jj r|j d|t||jj dn|j d|t|d WYd}~dSd}~0ty}z*td||j |jddWYd}~dSd}~0tjy$tdYdS0td|z| IdHWnt!y^Yn0dS)NzHi. How are you today?rz x-login-datarfz known-hosts)r{rxrZ login_data capabilitiesinitzprotocol-errorzcapabilities must be a dict)r{rrTzexplicit-superuserrz.ferny.InteractionError: %s, interpreted as: %rzauthentication-failedzinvalid-hostkeyzunknown-hostkeyz unknown-hostzinternal-error)r{rrrzCockpitProblem: %s)r{z"Peer bridge got cancelled, exitingz/Startup done. Looping until connection closes.)"r*r+rrasyncioZget_running_loopdictrstartrrr read_text setdefaultr8rfromkeysZ thaw_endpointr ZInteractionErrorZ ssh_errorsZget_exception_for_ssh_stderrr:ZSshAuthenticationErrorZSshChangedHostKeyErrorZSshHostKeyErrorrZSshErrorrattrsZCancelledErrorrrBrokenPipeError)rvZbridgerrexcrrr2r2r3runsl           rcCsrttjdd}|jdgdddd|jdd d |jd d d |}t|jdtj t ||jddS)Nz@cockpit-bridge is run automatically inside of a Cockpit session.) descriptionz--remote-bridge)rrrrrzHow to run cockpit-bridge from the remote host: auto: if installed (default), never: always copy the local one; supported: if not installed, copy local one for compatible OSes, fail otherwise; always: fail if not installed)choicesrehelpz--debug store_true)actionrz5Name of the remote host to connect to, or 'localhost')r)r+) rinstallrArgumentParser add_argument parse_argsr r+rr)parserrvr2r2r3mains  r__main__)HrrrnZimportlib.resourcesr Zloggingr$rirrrmpathlibrtypingrrrrrZcockpitrZcockpit._vendorr Zcockpit._vendor.beir Zcockpit.beipackr Zcockpit.bridger r Zcockpit.channelrZcockpit.channelsrZcockpit.jsonutilrrZcockpit.osinforZcockpit.packagesrrrZ cockpit.peerrZcockpit.protocolrrZcockpit.routerrrZcockpit.transportsrZ getLoggerr*r4r:r<r=rrMZAskpassHandlerrYrrrrrrrrr"r2r2r2r3s\           " u ** vG