§ WèÄj~Gã ón—UdZddlZddlZddlZddlZddlZeje¦«ZdZ e e d<dZ e e d<dZdZd Zd Zd e dzd e dzfd „Zd efd„Zd efd„Zd efd„Zde de de d e dzfd„Zde d e dzfd„Zde d e dzfd„Zde de d e dzfd„Zd e dzfd„Zde fd„Zd de de de dzd ee fd„ZdS)!uýConnect-target resolution for the per-domain phpinfo probe. The probe in :mod:`xray.internal.phpinfo_utils` fetches a temporary file from a vhost hosted on THIS machine. The hostname is tenant-controlled and resolves via public DNS, so following DNS would let a tenant repoint their domain at an arbitrary host and turn the root daemon's probe into an SSRF primitive (CLPRO-3179 finding F-08 — the repo also uses a bare "F-08" for an unrelated CLPRO-3240 finding in `cron/xray-tasks`, so this one is always qualified here). The probe therefore never resolves the hostname: it connects to an address this module returns and passes the domain in the ``Host`` header. Pinning to 127.0.0.1 *alone* (the original CLPRO-3179 F-08 fix) silently assumed the local webserver routes purely by name. That holds for a wildcard listener (the nginx-fronted default) but NOT for the per-IP vhosts DirectAdmin and cPanel generate by default:: # the account's vhost Listen 80 # but the server listens everywhere A request arriving on the loopback address falls into the ``127.0.0.1:80`` address bucket, matches no vhost there, and is answered by that bucket's default vhost (whose DocumentRoot is the server's, not the tenant's) — so the probe file is not found and the probe 404s. Hence the probe needs a list of candidate addresses, not a single hardcoded one. Every address returned here is either loopback or an address CONFIGURED ON THIS MACHINE, so the CLPRO-3179 F-08 property is preserved by construction: a tenant who repoints public DNS, or who tampers with the ``ip=`` line of their own panel domain conf, cannot steer the probe off-box — an address this machine does not own is dropped before any socket is opened. éNz 127.0.0.1ÚLOOPBACK_ADDRESSéÚMAX_CANDIDATESz/etc/userdatadomainsz(/var/cpanel/userdata/{username}/{domain}zB/usr/local/directadmin/data/users/{username}/domains/{domain}.conf)Údockerzbr-ÚvirbrÚvethÚpodmanzcni-ÚlxcbrÚkubeÚrawÚreturncó—|sdSt|¦« ¦« dd¦«d} tj|¦«}n#t $rYdSwxYw|js|js|jrdSt|¦«S)a´Canonical text form of an IP address, or None when unusable. Rejects anything that is not a literal IP address, so a hostname or a garbage value read out of a panel conf can never become a connect target. Link-local, unspecified and multicast addresses are dropped too: a vhost is never usefully reachable through them, and IPv6 link-local additionally needs a scope id we deliberately do not carry into a URL. Nú%ér) ÚstrÚstripÚsplitÚ ipaddressÚ ip_addressÚ ValueErrorÚ is_link_localÚis_unspecifiedÚ is_multicast)r Ú candidateÚparseds úR/opt/cloudlinux/venv/lib64/python3.11/site-packages/xray/internal/probe_targets.pyÚ_normalize_addressrUs¤€ð ð؈tå�C‘”—’Ñ Ô ×&Ò& s¨AÑ.Ô.¨qÔ1€IðÝÔ% iÑ0Ô0ˆˆøÝ ððð؈tˆtðøøøà Ôð˜vÔ4ð¸Ô8Kð؈tÝ ˆv‰;Œ;ÐsÁAÁ A$Á#A$cót— tj¦«}n4#t$r'}t d|¦«gcYd}~Sd}~wwxYwg}| ¦«D]W\}}|D]O}|jtjtj fvrŒ"t|j ¦«}|�|  ||f¦«ŒPŒX|S)uw(interface name, address) for every IP configured on THIS machine. Derived from the machine itself and never from anything a tenant can write — ``psutil.net_if_addrs()`` reads the kernel's interface table directly. The interface name is carried so the sweep can skip container bridges while validation still accepts every address the host really owns. zFUnable to enumerate local addresses, falling back to loopback only: %sN) ÚpsutilÚ net_if_addrsÚ ExceptionÚloggerÚwarningÚitemsÚfamilyÚsocketÚAF_INETÚAF_INET6rÚaddressÚappend)Ú interfacesÚeÚfoundÚnameÚentriesÚentryr)s rÚ_interface_addressesr1ksá€ðÝÔ(Ñ*Ô*ˆ ˆ øÝ ðððÝ�ŠÐ_ÐabÑcÔcÐc؈ ˆ ˆ ˆ ˆ ˆ øøøøðøøøð €EØ#×)Ò)Ñ+Ô+ð.ð.‰ ˆˆgØð .ð .ˆEØŒ|¥F¤NµF´OÐ#DÐDÐDØÝ(¨¬Ñ7Ô7ˆGØÐ"Ø— ’ ˜d G˜_Ñ-Ô-Ð-øð  .ð €Ls‚– A A¼AÁAcó^—tthd„t¦«D¦«z¦«S)a/Every IP address configured on an interface of THIS machine. This is the allowlist that keeps the probe on-box. It is deliberately the FULL set, container bridges included: if a panel record names such an address the panel is asserting the vhost is there, and refusing it would fail closed on a legitimate target. Only the blind sweep filters them (see `connect_candidates`). Loopback is always included, so a total enumeration failure degrades to the pre-existing loopback-only behaviour instead of leaving no candidate at all. có—h|]\}}|’ŒS©r4)Ú.0Ú_namer)s rú z"local_addresses..�s€Ð*`Ð*`Ð*`±~°u¸g¨7Ð*`Ð*`Ð*`ó)Ú frozensetrr1r4r8rÚlocal_addressesr:„s1€õ Õ&Ð'Ð*`Ð*`ÕI]ÑI_ÔI_Ð*`Ñ*`Ô*`Ñ`Ñ aÔ aÐar8có2—d„t¦«D¦«S)zCLocal addresses worth probing blind, container/VM bridges excluded.cóL—g|]!\}}| t¦«°|‘Œ"Sr4)Ú startswithÚ_NON_HOSTING_INTERFACE_PREFIXES)r5r.r)s rú z(_sweepable_addresses..”s.€Ð xÐ xÐ x™ ˜˜gÀtÇÂÕWvÑGwÔGwÐ xˆGÐ xÐ xÐ xr8)r1r4r8rÚ_sweepable_addressesr@’s€à xÐ xÕ)=Ñ)?Ô)?Ð xÑ xÔ xÐxr8ÚpathÚkeyÚ separatorcó\—||z} t|dd¬¦«5}|D]e}| |¦«sŒ|t|¦«d… ¦«}| d¦«pdccddd¦«S ddd¦«n #1swxYwYn#t$rYdSwxYwdS)uŸFirst value of a top-level ```` line in ``path``. Deliberately line-based rather than a full YAML/conf parse: we need one scalar out of two different panel formats and must not gain a parser dependency on this path. The key is required to start at column 0, so an indented key nested under some other mapping cannot be mistaken for the top-level one. A missing or unreadable file yields None — the caller simply loses one candidate. NB: not `clcommon.clconfpars.load_fast`, which clcommon does apply to these same files. Three behaviours here are deliberate and it does not share them: it returns the LAST match rather than the first, it strips the key so an INDENTED `ip:` under another mapping would be accepted (rejecting that is pinned by a test), and it lets OSError propagate where we want one lost candidate rather than a failed collection. úutf-8Úreplace©ÚencodingÚerrorsNz'")Úopenr=ÚlenrÚOSError)rArBrCÚprefixÚfhÚlineÚvalues rÚ_read_conf_valuerQ—s2€ð"�9‰_€Fð Ý �$ °Ð ;Ñ ;Ô ;ð 2¸rØð 2ð 2�Ø—’ vÑ.Ô.ðØð �S ™[œ[˜]˜]Ô+×1Ò1Ñ3Ô3�à—{’{ 5Ñ)Ô)Ð1¨TÐ1Ð1ð 2ð 2ð 2ð 2ñ 2ô 2ð 2ð 2ð 2ð 2ð 2ð 2ñ 2ô 2ð 2ð 2ð 2ð 2ð 2ð 2øøøð 2ð 2ð 2ð 2øøõ ððð؈tˆtðøøøà ˆ4s;‡B™ABÁ5 B BÂBÂBÂBÂB B)Â(B)Údomaincó— ttdd¬¦«5}|D]Â}| d¦«\}}}|r| ¦«|krŒ6| ¦« d¦«}t |¦«dkrddd¦«dS|d dd ¦«d  d ¦«pdccddd¦«S ddd¦«n #1swxYwYn#t$rYdSwxYwdS) unThe vhost address cPanel's flat domain index records for ``domain``. `/etc/userdatadomains` has one line per domain of EVERY type (main, addon, parked, sub), which is what makes it the right cPanel source — the per-domain userdata file exists for main domains only. Format, per clcommon's own reader (`cpapi/plugins/cpanel._parse_userdatadomains`):: sub.example.com: owner==reseller==sub==example.com==/home/o/docroot==10.0.0.8:80==10.0.0.8:443==... i.e. ``: `` then ``==``-separated fields, of which index 5 is the HTTP ``ip:port``. We parse it here rather than calling that helper: it is private, it is callback-shaped (it hands every line to a `parser` argument), and it walks a ';'-separated path list we do not want. Index 5 missing or empty is normal for a partially provisioned domain, so it just yields None. rErFrGz: z==éNú:rrz[])rJÚ_CPANEL_USERDATADOMAINSÚ partitionrrrKÚrsplitrL)rRrNrOr.rCr Úfieldss rÚ_cpanel_userdatadomains_addressrZºs¼€ð Ý Õ)°GÀIÐ NÑ NÔ Nð GÐRTØð Gð G�Ø'+§~¢~°dÑ';Ô';Ñ$��i Ø ð D§J¢J¡L¤L°FÒ$:Ð$:ØØŸš™œ×*Ò*¨4Ñ0Ô0�Ý�v‘;”; !Ò#Ð#Øð Gð Gð Gñ Gô Gð Gð Gð Gð˜a”y×'Ò'¨¨QÑ/Ô/°Ô2×8Ò8¸Ñ>Ô>ÐFÀ$ÐFÐFð Gð Gð Gð Gñ Gô Gð Gð Gð Gð Gð Gð Gñ Gô Gð Gð Gð Gð Gð Gð Gøøøð Gð Gð Gð Gøøõ ððð؈tˆtðøøøà ˆ4sG‚C8™A4C, C8Â8C,à C8à C8Ã,C0Ã0C8Ã3C0Ã4C8Ã8 DÄDÚ conf_pathcój—|rtj |¦«sdSt|dd¦«S)aèThe ``ip=`` a DirectAdmin per-domain conf records, read from a given path. Exposed for `manager/directadmin.py`, which has already resolved the RIGHT conf for the requested name: DirectAdmin stores a subdomain's and an alias's address in the PARENT domain's conf, and only the manager knows that mapping (its `all_sites` maps both to the parent's file). Rebuilding the path from the domain name here would look for `domains/.conf`, which never exists. NÚipú=)ÚosrAÚisfilerQ)r[s rÚdirectadmin_conf_addressraÛs8€ð ð�BœGŸNšN¨9Ñ5Ô5ð؈tÝ ˜I t¨SÑ 1Ô 1Ð1r8Úusernamecó —||fD]}|rd|vs|dvrdSŒt|¦«}|r|StdftdffD]S\}}| ||¬¦«}tj |¦«sŒ '', a custom panel raising NotSupported) degrades to "no candidate from this source". r)Ú get_server_ipNz1Control panel did not report a main server IP: %s)Úclcommon.cpapirnr!r"Údebug)rnr,s rÚserver_main_addressrqsk€ðØ0Ð0Ð0Ð0Ð0Ð0àˆ}‰ŒÐ& $Ð&øÝ ðððÝ� Š ÐHÈ!ÑLÔLÐL؈tˆtˆtˆtˆtøøøøðøøøs‚” Až?¿Ar)cóF—tj|¦«}|j|jfS)zHNumeric, family-grouped ordering so the candidate list is deterministic.)rrÚversionÚpacked)r)rs rÚ _sort_keyru.s!€å Ô ! 'Ñ *Ô *€FØ ŒN˜FœMÐ *Ð*r8Ú panel_addresscó(‡‡‡—t¦«ŠtgŠdtdzdtddfˆˆˆfd„ }||pt|‰¦«d¦«|t ¦«d¦«t t t¦«¦«t¬¦«D](}tj |¦«j rŒ||d ¦«Œ)t‰¦«tkr9t d tt‰¦«‰¦«‰td…=‰S) u5Ordered connect targets to try for ``domain``'s phpinfo probe. Ordering is by decreasing confidence, so the common case resolves on the first or second attempt: 1. ``127.0.0.1`` — unchanged first attempt; the only target a wildcard-listener vhost needs. 2. the panel's per-domain address — on a per-IP-vhost host, the exact address whose bucket holds this account's vhost. Taken from ``panel_address`` when the caller resolved it (DirectAdmin must: it records a subdomain's address in the parent domain's conf), else looked up here. 3. the panel's main server address — right on a single-address host, and on a multi-address host when the domain sits on the primary. 4. this machine's remaining addresses — the panel-independent safety net that covers Plesk and custom panels (no per-domain record to read) and a stale panel record. Container/VM bridges are excluded from this blind sweep; a panel record naming one is still honoured via source 2. Sources 2 and 3 are panel-reported and therefore validated against :func:`local_addresses` before being accepted; source 4 is drawn from it. So every returned address is one this machine owns and no off-box address is ever connected to, whatever a panel record or DNS says. r NÚsourcer c󪕗t|¦«}|€dS|‰vrt d||‰¦«dS|‰vr‰ |¦«dSdS)NzIIgnoring %s address %s for domain %s probe: not configured on this server)rr"r#r*)r rxr)Ú candidatesrRÚlocals €€€rÚacceptz"connect_candidates..acceptOs}ø€Ý$ SÑ)Ô)ˆØ ˆ?Ø ˆFØ ˜%Ð Ð õ �NŠNØ[ØØØñ  ô ð ð ˆFØ ˜*Ð $Ð $Ø × Ò ˜gÑ &Ô &Ð &Ð &Ð &ð %Ð $r8zpanel per-domainzpanel main server)rBzlocal interfaceznProbing only the first %d of %d connect candidates for domain %s; the vhost address may be among those dropped)r:rrrlrqÚsortedÚsetr@rurrÚ is_loopbackrKrr"r#)rbrRrvr|r)rzr{s ` @@rÚconnect_candidatesr€4sGøøø€õ0 Ñ Ô €EÝ"Ð#€Jð'•C˜$‘Jð'­ð'°ð'ð'ð'ð'ð'ð'ð'ð'ð& €Fˆ=Ð BÕ0°¸6ÑBÔBÐDVÑWÔWÐWØ €FÕ Ñ Ô Ð"5Ñ6Ô6Ð6Ý�#Õ2Ñ4Ô4Ñ5Ô5½9ÐEÑEÔEð+ð+ˆõ Ô  Ñ (Ô (Ô 4ð Ø ØˆˆwÐ)Ñ*Ô*Ð*Ð*å ˆ:�„�Ò'Ð'õ �Šð ;å Ý � ‰OŒOØ ñ  ô ð ð •~��Ð 'Ø Ðr8)N)Ú__doc__rÚloggingr_r&rÚ getLoggerÚ__name__r"rrÚ__annotations__rÚintrVrfrgr>rÚlistr1r9r:r@rQrZrarlrqrur€r4r8rúrˆsbðð ððð@ÐÐÐØ€€€Ø € € € Ø € € € à € € € à ˆÔ ˜8Ñ $Ô $€ð $Ð�#Ð#Ð#Ñ#ð€�ÐÐÑð1ÐØFÐð%iÐ!ð#hÐð˜C $™Jð¨3°©:ððððð,˜dððððð2 b˜ð bð bð bð bðy˜dðyðyðyðyð ˜3ð  Sð °Sð ¸SÀ4¹Zð ð ð ð ðF¨Cð°C¸$±JðððððB 2¨ð 2°°d± ð 2ð 2ð 2ð 2ð0 3ð0°ð0¸¸d¹ ð0ð0ð0ð0ðf˜S 4™Zððððð$+�sð+ð+ð+ð+ð FðF ðF¨cðFÀ#ÈÁ*ðFÐX\Ð]`ÔXaðFðFðFðFðFðFr8