PNG  IHDRQgAMA a cHRMz&u0`:pQ<bKGDgmIDATxwUﹻ& ^CX(J I@ "% (** BX +*i"]j(IH{~R)[~>h{}gy)I$Ij .I$I$ʊy@}x.: $I$Ii}VZPC)I$IF ^0ʐJ$I$Q^}{"r=OzI$gRZeC.IOvH eKX $IMpxsk.쒷/&r[޳<v| .I~)@$updYRa$I |M.e JaֶpSYR6j>h%IRز if&uJ)M$I vLi=H;7UJ,],X$I1AҒJ$ XY XzI@GNҥRT)E@;]K*Mw;#5_wOn~\ DC&$(A5 RRFkvIR}l!RytRl;~^ǷJj اy뷦BZJr&ӥ8Pjw~vnv X^(I;4R=P[3]J,]ȏ~:3?[ a&e)`e*P[4]T=Cq6R[ ~ޤrXR Հg(t_HZ-Hg M$ãmL5R uk*`%C-E6/%[t X.{8P9Z.vkXŐKjgKZHg(aK9ڦmKjѺm_ \#$5,)-  61eJ,5m| r'= &ڡd%-]J on Xm|{ RҞe $eڧY XYrԮ-a7RK6h>n$5AVڴi*ֆK)mѦtmr1p| q:흺,)Oi*ֺK)ܬ֦K-5r3>0ԔHjJئEZj,%re~/z%jVMڸmrt)3]J,T K֦OvԒgii*bKiNO~%PW0=dii2tJ9Jݕ{7"I P9JKTbu,%r"6RKU}Ij2HKZXJ,妝 XYrP ެ24c%i^IK|.H,%rb:XRl1X4Pe/`x&P8Pj28Mzsx2r\zRPz4J}yP[g=L) .Q[6RjWgp FIH*-`IMRaK9TXcq*I y[jE>cw%gLRԕiFCj-ďa`#e~I j,%r,)?[gp FI˨mnWX#>mʔ XA DZf9,nKҲzIZXJ,L#kiPz4JZF,I,`61%2s $,VOϚ2/UFJfy7K> X+6 STXIeJILzMfKm LRaK9%|4p9LwJI!`NsiazĔ)%- XMq>pk$-$Q2x#N ؎-QR}ᶦHZډ)J,l#i@yn3LN`;nڔ XuX5pF)m|^0(>BHF9(cզEerJI rg7 4I@z0\JIi䵙RR0s;$s6eJ,`n 䂦0a)S)A 1eJ,堌#635RIgpNHuTH_SԕqVe ` &S)>p;S$魁eKIuX`I4춒o}`m$1":PI<[v9^\pTJjriRŭ P{#{R2,`)e-`mgj~1ϣLKam7&U\j/3mJ,`F;M'䱀 .KR#)yhTq;pcK9(q!w?uRR,n.yw*UXj#\]ɱ(qv2=RqfB#iJmmL<]Y͙#$5 uTU7ӦXR+q,`I}qL'`6Kͷ6r,]0S$- [RKR3oiRE|nӦXR.(i:LDLTJjY%o:)6rxzҒqTJjh㞦I.$YR.ʼnGZ\ֿf:%55 I˼!6dKxm4E"mG_ s? .e*?LRfK9%q#uh$)i3ULRfK9yxm܌bj84$i1U^@Wbm4uJ,ҪA>_Ij?1v32[gLRD96oTaR׿N7%L2 NT,`)7&ƝL*꽙yp_$M2#AS,`)7$rkTA29_Iye"|/0t)$n XT2`YJ;6Jx".e<`$) PI$5V4]29SRI>~=@j]lp2`K9Jaai^" Ԋ29ORI%:XV5]JmN9]H;1UC39NI%Xe78t)a;Oi Ҙ>Xt"~G>_mn:%|~ޅ_+]$o)@ǀ{hgN;IK6G&rp)T2i୦KJuv*T=TOSV>(~D>dm,I*Ɛ:R#ۙNI%D>G.n$o;+#RR!.eU˽TRI28t)1LWϚ>IJa3oFbu&:tJ*(F7y0ZR ^p'Ii L24x| XRI%ۄ>S1]Jy[zL$adB7.eh4%%누>WETf+3IR:I3Xה)3אOۦSRO'ٺ)S}"qOr[B7ϙ.edG)^ETR"RtRݜh0}LFVӦDB^k_JDj\=LS(Iv─aTeZ%eUAM-0;~˃@i|l @S4y72>sX-vA}ϛBI!ݎߨWl*)3{'Y|iSlEڻ(5KtSI$Uv02,~ԩ~x;P4ցCrO%tyn425:KMlD ^4JRxSهF_}شJTS6uj+ﷸk$eZO%G*^V2u3EMj3k%)okI]dT)URKDS 7~m@TJR~荪fT"֛L \sM -0T KfJz+nإKr L&j()[E&I ߴ>e FW_kJR|!O:5/2跌3T-'|zX ryp0JS ~^F>-2< `*%ZFP)bSn"L :)+pʷf(pO3TMW$~>@~ū:TAIsV1}S2<%ޟM?@iT ,Eūoz%i~g|`wS(]oȤ8)$ ntu`өe`6yPl IzMI{ʣzʨ )IZ2= ld:5+請M$-ї;U>_gsY$ÁN5WzWfIZ)-yuXIfp~S*IZdt;t>KūKR|$#LcԀ+2\;kJ`]YǔM1B)UbG"IRߊ<xܾӔJ0Z='Y嵤 Leveg)$znV-º^3Ւof#0Tfk^Zs[*I꯳3{)ˬW4Ւ4 OdpbZRS|*I 55#"&-IvT&/윚Ye:i$ 9{LkuRe[I~_\ؠ%>GL$iY8 9ܕ"S`kS.IlC;Ҏ4x&>u_0JLr<J2(^$5L s=MgV ~,Iju> 7r2)^=G$1:3G< `J3~&IR% 6Tx/rIj3O< ʔ&#f_yXJiގNSz; Tx(i8%#4 ~AS+IjerIUrIj362v885+IjAhK__5X%nV%Iͳ-y|7XV2v4fzo_68"S/I-qbf; LkF)KSM$ Ms>K WNV}^`-큧32ŒVؙGdu,^^m%6~Nn&͓3ŒVZMsRpfEW%IwdǀLm[7W&bIRL@Q|)* i ImsIMmKmyV`i$G+R 0tV'!V)֏28vU7͒vHꦼtxꗞT ;S}7Mf+fIRHNZUkUx5SAJㄌ9MqμAIRi|j5)o*^'<$TwI1hEU^c_j?Е$%d`z cyf,XO IJnTgA UXRD }{H}^S,P5V2\Xx`pZ|Yk:$e ~ @nWL.j+ϝYb퇪bZ BVu)u/IJ_ 1[p.p60bC >|X91P:N\!5qUB}5a5ja `ubcVxYt1N0Zzl4]7­gKj]?4ϻ *[bg$)+À*x쳀ogO$~,5 زUS9 lq3+5mgw@np1sso Ӻ=|N6 /g(Wv7U;zωM=wk,0uTg_`_P`uz?2yI!b`kĸSo+Qx%!\οe|އԁKS-s6pu_(ֿ$i++T8=eY; צP+phxWQv*|p1. ά. XRkIQYP,drZ | B%wP|S5`~́@i޾ E;Չaw{o'Q?%iL{u D?N1BD!owPHReFZ* k_-~{E9b-~P`fE{AܶBJAFO wx6Rox5 K5=WwehS8 (JClJ~ p+Fi;ŗo+:bD#g(C"wA^ r.F8L;dzdIHUX݆ϞXg )IFqem%I4dj&ppT{'{HOx( Rk6^C٫O.)3:s(۳(Z?~ٻ89zmT"PLtw䥈5&b<8GZ-Y&K?e8,`I6e(֍xb83 `rzXj)F=l($Ij 2*(F?h(/9ik:I`m#p3MgLaKjc/U#n5S# m(^)=y=đx8ŬI[U]~SцA4p$-F i(R,7Cx;X=cI>{Km\ o(Tv2vx2qiiDJN,Ҏ!1f 5quBj1!8 rDFd(!WQl,gSkL1Bxg''՞^ǘ;pQ P(c_ IRujg(Wz bs#P­rz> k c&nB=q+ؔXn#r5)co*Ũ+G?7< |PQӣ'G`uOd>%Mctz# Ԫڞ&7CaQ~N'-P.W`Oedp03C!IZcIAMPUۀ5J<\u~+{9(FbbyAeBhOSܳ1 bÈT#ŠyDžs,`5}DC-`̞%r&ڙa87QWWp6e7 Rϫ/oY ꇅ Nܶըtc!LA T7V4Jsū I-0Pxz7QNF_iZgúWkG83 0eWr9 X]㾮݁#Jˢ C}0=3ݱtBi]_ &{{[/o[~ \q鯜00٩|cD3=4B_b RYb$óBRsf&lLX#M*C_L܄:gx)WΘsGSbuL rF$9';\4Ɍq'n[%p.Q`u hNb`eCQyQ|l_C>Lb꟟3hSb #xNxSs^ 88|Mz)}:](vbۢamŖ࿥ 0)Q7@0=?^k(*J}3ibkFn HjB׻NO z x}7p 0tfDX.lwgȔhԾŲ }6g E |LkLZteu+=q\Iv0쮑)QٵpH8/2?Σo>Jvppho~f>%bMM}\//":PTc(v9v!gոQ )UfVG+! 35{=x\2+ki,y$~A1iC6#)vC5^>+gǵ@1Hy٪7u;p psϰu/S <aʸGu'tD1ԝI<pg|6j'p:tպhX{o(7v],*}6a_ wXRk,O]Lܳ~Vo45rp"N5k;m{rZbΦ${#)`(Ŵg,;j%6j.pyYT?}-kBDc3qA`NWQū20/^AZW%NQ MI.X#P#,^Ebc&?XR tAV|Y.1!؅⨉ccww>ivl(JT~ u`ٵDm q)+Ri x/x8cyFO!/*!/&,7<.N,YDŽ&ܑQF1Bz)FPʛ?5d 6`kQձ λc؎%582Y&nD_$Je4>a?! ͨ|ȎWZSsv8 j(I&yj Jb5m?HWp=g}G3#|I,5v珿] H~R3@B[☉9Ox~oMy=J;xUVoj bUsl_35t-(ՃɼRB7U!qc+x4H_Qo֮$[GO<4`&č\GOc[.[*Af%mG/ ňM/r W/Nw~B1U3J?P&Y )`ѓZ1p]^l“W#)lWZilUQu`-m|xĐ,_ƪ|9i:_{*(3Gѧ}UoD+>m_?VPۅ15&}2|/pIOʵ> GZ9cmíتmnz)yߐbD >e}:) r|@R5qVSA10C%E_'^8cR7O;6[eKePGϦX7jb}OTGO^jn*媓7nGMC t,k31Rb (vyܴʭ!iTh8~ZYZp(qsRL ?b}cŨʊGO^!rPJO15MJ[c&~Z`"ѓޔH1C&^|Ш|rʼ,AwĴ?b5)tLU)F| &g٣O]oqSUjy(x<Ϳ3 .FSkoYg2 \_#wj{u'rQ>o;%n|F*O_L"e9umDds?.fuuQbIWz |4\0 sb;OvxOSs; G%T4gFRurj(֍ڑb uԖKDu1MK{1^ q; C=6\8FR艇!%\YÔU| 88m)֓NcLve C6z;o&X x59:q61Z(T7>C?gcļxѐ Z oo-08jہ x,`' ҔOcRlf~`jj".Nv+sM_]Zk g( UOPyεx%pUh2(@il0ݽQXxppx-NS( WO+轾 nFߢ3M<;z)FBZjciu/QoF 7R¥ ZFLF~#ȣߨ^<쩡ݛкvџ))ME>ώx4m#!-m!L;vv#~Y[đKmx9.[,UFS CVkZ +ߟrY٧IZd/ioi$%͝ب_ֶX3ܫhNU ZZgk=]=bbJS[wjU()*I =ώ:}-蹞lUj:1}MWm=̛ _ ¾,8{__m{_PVK^n3esw5ӫh#$-q=A̟> ,^I}P^J$qY~Q[ Xq9{#&T.^GVj__RKpn,b=`żY@^՝;z{paVKkQXj/)y TIc&F;FBG7wg ZZDG!x r_tƢ!}i/V=M/#nB8 XxЫ ^@CR<{䤭YCN)eKOSƟa $&g[i3.C6xrOc8TI;o hH6P&L{@q6[ Gzp^71j(l`J}]e6X☉#͕ ׈$AB1Vjh㭦IRsqFBjwQ_7Xk>y"N=MB0 ,C #o6MRc0|$)ف"1!ixY<B9mx `,tA>)5ػQ?jQ?cn>YZe Tisvh# GMމȇp:ԴVuږ8ɼH]C.5C!UV;F`mbBk LTMvPʍϤj?ԯ/Qr1NB`9s"s TYsz &9S%U԰> {<ؿSMxB|H\3@!U| k']$U+> |HHMLޢ?V9iD!-@x TIî%6Z*9X@HMW#?nN ,oe6?tQwڱ.]-y':mW0#!J82qFjH -`ѓ&M0u Uγmxϵ^-_\])@0Rt.8/?ٰCY]x}=sD3ojަЫNuS%U}ԤwHH>ڗjܷ_3gN q7[q2la*ArǓԖ+p8/RGM ]jacd(JhWko6ڎbj]i5Bj3+3!\j1UZLsLTv8HHmup<>gKMJj0@H%,W΃7R) ">c, xixј^ aܖ>H[i.UIHc U1=yW\=S*GR~)AF=`&2h`DzT󑓶J+?W+}C%P:|0H܆}-<;OC[~o.$~i}~HQ TvXΈr=b}$vizL4:ȰT|4~*!oXQR6Lk+#t/g lԁߖ[Jڶ_N$k*". xsxX7jRVbAAʯKҎU3)zSNN _'s?f)6X!%ssAkʱ>qƷb hg %n ~p1REGMHH=BJiy[<5 ǁJҖgKR*倳e~HUy)Ag,K)`Vw6bRR:qL#\rclK/$sh*$ 6덤 KԖc 3Z9=Ɣ=o>X Ώ"1 )a`SJJ6k(<c e{%kϊP+SL'TcMJWRm ŏ"w)qc ef꒵i?b7b('"2r%~HUS1\<(`1Wx9=8HY9m:X18bgD1u ~|H;K-Uep,, C1 RV.MR5άh,tWO8WC$ XRVsQS]3GJ|12 [vM :k#~tH30Rf-HYݺ-`I9%lIDTm\ S{]9gOڒMNCV\G*2JRŨ;Rҏ^ڽ̱mq1Eu?To3I)y^#jJw^Ńj^vvlB_⋌P4x>0$c>K†Aļ9s_VjTt0l#m>E-,,x,-W)سo&96RE XR.6bXw+)GAEvL)͞K4$p=Ũi_ѱOjb HY/+@θH9޼]Nԥ%n{ &zjT? Ty) s^ULlb,PiTf^<À] 62R^V7)S!nllS6~͝V}-=%* ʻ>G DnK<y&>LPy7'r=Hj 9V`[c"*^8HpcO8bnU`4JȪAƋ#1_\ XϘHPRgik(~G~0DAA_2p|J묭a2\NCr]M_0 ^T%e#vD^%xy-n}-E\3aS%yN!r_{ )sAw ڼp1pEAk~v<:`'ӭ^5 ArXOI驻T (dk)_\ PuA*BY]yB"l\ey hH*tbK)3 IKZ򹞋XjN n *n>k]X_d!ryBH ]*R 0(#'7 %es9??ښFC,ՁQPjARJ\Ρw K#jahgw;2$l*) %Xq5!U᢯6Re] |0[__64ch&_}iL8KEgҎ7 M/\`|.p,~`a=BR?xܐrQ8K XR2M8f ?`sgWS%" Ԉ 7R%$ N}?QL1|-эټwIZ%pvL3Hk>,ImgW7{E xPHx73RA @RS CC !\ȟ5IXR^ZxHл$Q[ŝ40 (>+ _C >BRt<,TrT {O/H+˟Pl6 I B)/VC<6a2~(XwV4gnXR ϱ5ǀHٻ?tw똤Eyxp{#WK qG%5],(0ӈH HZ])ג=K1j&G(FbM@)%I` XRg ʔ KZG(vP,<`[ Kn^ SJRsAʠ5xՅF`0&RbV tx:EaUE/{fi2;.IAwW8/tTxAGOoN?G}l L(n`Zv?pB8K_gI+ܗ #i?ޙ.) p$utc ~DžfՈEo3l/)I-U?aԅ^jxArA ΧX}DmZ@QLےbTXGd.^|xKHR{|ΕW_h] IJ`[G9{).y) 0X YA1]qp?p_k+J*Y@HI>^?gt.06Rn ,` ?);p pSF9ZXLBJPWjgQ|&)7! HjQt<| ؅W5 x W HIzYoVMGP Hjn`+\(dNW)F+IrS[|/a`K|ͻ0Hj{R,Q=\ (F}\WR)AgSG`IsnAR=|8$}G(vC$)s FBJ?]_u XRvύ6z ŨG[36-T9HzpW̞ú Xg큽=7CufzI$)ki^qk-) 0H*N` QZkk]/tnnsI^Gu't=7$ Z;{8^jB% IItRQS7[ϭ3 $_OQJ`7!]W"W,)Iy W AJA;KWG`IY{8k$I$^%9.^(`N|LJ%@$I}ֽp=FB*xN=gI?Q{٥4B)mw $Igc~dZ@G9K X?7)aK%݅K$IZ-`IpC U6$I\0>!9k} Xa IIS0H$I H ?1R.Чj:4~Rw@p$IrA*u}WjWFPJ$I➓/6#! LӾ+ X36x8J |+L;v$Io4301R20M I$-E}@,pS^ޟR[/s¹'0H$IKyfŸfVOπFT*a$I>He~VY/3R/)>d$I>28`Cjw,n@FU*9ttf$I~<;=/4RD~@ X-ѕzἱI$: ԍR a@b X{+Qxuq$IЛzo /~3\8ڒ4BN7$IҀj V]n18H$IYFBj3̵̚ja pp $Is/3R Ӻ-Yj+L;.0ŔI$Av? #!5"aʄj}UKmɽH$IjCYs?h$IDl843.v}m7UiI=&=0Lg0$I4: embe` eQbm0u? $IT!Sƍ'-sv)s#C0:XB2a w I$zbww{."pPzO =Ɔ\[ o($Iaw]`E).Kvi:L*#gР7[$IyGPI=@R 4yR~̮´cg I$I/<tPͽ hDgo 94Z^k盇΄8I56^W$I^0̜N?4*H`237}g+hxoq)SJ@p|` $I%>-hO0eO>\ԣNߌZD6R=K ~n($I$y3D>o4b#px2$yڪtzW~a $I~?x'BwwpH$IZݑnC㧄Pc_9sO gwJ=l1:mKB>Ab<4Lp$Ib o1ZQ@85b̍ S'F,Fe,^I$IjEdù{l4 8Ys_s Z8.x m"+{~?q,Z D!I$ϻ'|XhB)=…']M>5 rgotԎ 獽PH$IjIPhh)n#cÔqA'ug5qwU&rF|1E%I$%]!'3AFD/;Ck_`9 v!ٴtPV;x`'*bQa w I$Ix5 FC3D_~A_#O݆DvV?<qw+I$I{=Z8".#RIYyjǪ=fDl9%M,a8$I$Ywi[7ݍFe$s1ՋBVA?`]#!oz4zjLJo8$I$%@3jAa4(o ;p,,dya=F9ً[LSPH$IJYЉ+3> 5"39aZ<ñh!{TpBGkj}Sp $IlvF.F$I z< '\K*qq.f<2Y!S"-\I$IYwčjF$ w9 \ߪB.1v!Ʊ?+r:^!I$BϹB H"B;L'G[ 4U#5>੐)|#o0aڱ$I>}k&1`U#V?YsV x>{t1[I~D&(I$I/{H0fw"q"y%4 IXyE~M3 8XψL}qE$I[> nD?~sf ]o΁ cT6"?'_Ἣ $I>~.f|'!N?⟩0G KkXZE]ޡ;/&?k OۘH$IRۀwXӨ<7@PnS04aӶp.:@\IWQJ6sS%I$e5ڑv`3:x';wq_vpgHyXZ 3gЂ7{{EuԹn±}$I$8t;b|591nءQ"P6O5i }iR̈́%Q̄p!I䮢]O{H$IRϻ9s֧ a=`- aB\X0"+5"C1Hb?߮3x3&gşggl_hZ^,`5?ߎvĸ%̀M!OZC2#0x LJ0 Gw$I$I}<{Eb+y;iI,`ܚF:5ܛA8-O-|8K7s|#Z8a&><a&/VtbtLʌI$I$I$I$I$I$IRjDD%tEXtdate:create2022-05-31T04:40:26+00:00!Î%tEXtdate:modify2022-05-31T04:40:26+00:00|{2IENDB` sh-3ll

HOME


sh-3ll 1.0
DIR:/opt/imunify360/venv/lib/python3.11/site-packages/restore_infected/backup_backends/
Upload File :
Current File : //opt/imunify360/venv/lib/python3.11/site-packages/restore_infected/backup_backends/plesk.py
import base64
import glob
import os
import pwd
from abc import abstractmethod
from pathlib import Path
from typing import Dict, Iterable, List, Union
from xml.etree import ElementTree

import pymysql
from Crypto.Cipher import AES

from .. import helpers
from ..backup_backends_lib import (
    BackupBase,
    BaseResource,
    FtpBackupBase,
    TarFile,
    TarResourceMixin,
    tarfile_open,
)

PLESK_CONFIG = '/etc/psa/psa.conf'
PLESK_CONFIG_DEFAULT = '/etc/psa/psa.conf.default'
PLESK_SECRET_KEY = '/etc/psa/private/secret_key'
PLESK_SHADOW = '/etc/psa/.psa.shadow'

# TODO implement other resources (not only from /var/www/vhosts)


class DomainUserDataResource(TarResourceMixin, BaseResource):
    """
    User domain data resource
    """
    root = '/var/www/vhosts'

    def __init__(self, path, domain):
        # type: (str, str) -> None
        resource = os.path.join(self.root, domain)
        super().__init__(path, resource)

    def _normalize_path(self, path):
        # type: (str) -> str
        resource_path = path[len(self.resource):].lstrip(os.sep)
        return resource_path


class DomainUserDataTarResource(DomainUserDataResource):
    """
    User domain data resource in tar archive
    """

    def __init__(self, path, domain, tar):
        # type: (str, str, TarFile) -> None
        super().__init__(path, domain)
        fileobj = tar.extractfile(path)
        self.fileobj = tarfile_open(fileobj=fileobj)


Resource = Union[DomainUserDataResource, DomainUserDataTarResource]


class BackupInfo:
    def __init__(self, path, domain: str, resources: Iterable[str]):
        self.path = path
        self.domain = domain
        self.resources = list(resources)

    @classmethod
    def parse_xml(cls, path, xml_content: str):
        resources = []
        tree = ElementTree.fromstring(xml_content)
        domain_node = tree.find("domain")
        # FIXME: domain can be None
        domain: str = domain_node.get("name")
        phosting = domain_node.find("phosting")
        dir_name = os.path.dirname(path)
        if phosting:
            content = phosting.find("content")
            if content:
                for cid in content:
                    if (cid_type := cid.get("type")) != "user-data":
                        continue
                    cid_path = cid.get("path", default="")
                    filename = cid.find("content-file").text
                    resource_path = os.path.join(dir_name, cid_path, filename)
                    resources.append(resource_path)
        return cls(path, domain, resources)


class PleskDomainBackup(BackupBase):
    """
    Plesk backup of user domain
    """
    def __init__(self, path, created):
        # type: (str, helpers.DateTime) -> None
        super().__init__(path, created)

        with open(path) as xml:
            xml_content = xml.read()

        backup_info = BackupInfo.parse_xml(path, xml_content)
        self.domain = backup_info.domain
        self.resources: List[Resource] = [
            DomainUserDataResource(resource_path, self.domain)
            for resource_path in backup_info.resources
        ]


class PleskFtpBackupBase(FtpBackupBase):
    """
    Base class for Plesk backups on FTP server
    """
    FTP_DIR_NAME = '.ri-plesk-ftp'

    def __init__(self, ftp, path, created, tmp_dir):
        # type: (helpers.Ftp, str, helpers.DateTime, str) -> None
        super().__init__(ftp, path, created, tmp_dir=tmp_dir)
        self.tar = None
        self._resources = None

    @staticmethod
    @abstractmethod
    def _check_path(path):
        """ Check that this is a path to this backup's xml description """

    def _retrieve_resources(self):
        self._resources = []  # type: List[Resource]

        path = self._retrieve()
        if path is None:
            return

        # FIXME: initialize in the __init__.
        # If there is a reason for a lazy loading, decouple the laziness logic
        # from the backup classes
        self.tar = tarfile_open(path)

        for path in self.tar.getnames():
            if not self._check_path(path):
                continue

            with self.tar.extractfile(path) as xml:
                xml_content = xml.read()

            backup_info = BackupInfo.parse_xml(path, xml_content)
            for resource_path in backup_info.resources:
                self._resources.append(
                    DomainUserDataTarResource(
                        resource_path, backup_info.domain, self.tar
                    )
                )

    @property
    def resources(self):
        if self._resources is None:
            self._retrieve_resources()
        return self._resources

    def close(self):
        # type: () -> None
        super().close()
        self._resources = None
        if self.tar:
            self.tar.close()
            self.tar = None


class PleskDomainFtpBackup(PleskFtpBackupBase):
    """
    Plesk FTP backup of user domain
    """

    @staticmethod
    def _check_path(path):
        # type: (str) -> bool
        try:
            _, = path.split('/')
        except ValueError:
            return False
        if not path.endswith('.xml'):
            return False
        return True


class PleskFtpBackup(PleskFtpBackupBase):
    """
    Plesk FTP all-in-one backup
    """

    @staticmethod
    def _check_path(path):
        # type: (str) -> bool
        try:
            c, _, d, _, _ = path.split('/')
        except ValueError:
            return False
        if (c, d) != ('clients', 'domains') or not path.endswith('.xml'):
            return False
        return True


def _backup_date(path):
    # type: (str) -> helpers.DateTime
    base, _ = os.path.splitext(os.path.basename(path))
    _, created = base.rsplit('_', 1)
    return helpers.DateTime('20' + created)


def _decrypt_password(password):
    # type: (str) -> str
    try:
        _, encryption, iv, content = password.split("$")
    except ValueError:
        return password

    if encryption != 'AES-128-CBC':
        raise ValueError('Unsupported encryption: ' + encryption)

    with open(PLESK_SECRET_KEY, 'rb') as f:
        key = f.read()

    iv = base64.b64decode(iv)
    content = base64.b64decode(content)
    cipher = AES.new(key, mode=AES.MODE_CBC, IV=iv)
    plain = cipher.decrypt(content)
    plain = plain.rstrip(b'\0')
    return plain.decode()


def _is_true(s):
    # type: (str) -> bool
    return s == 'true'


PLESK_BACKUP_SETTINGS = {
    'backup_ftp_settingactive': ('active', _is_true),
    'backup_ftp_settinghost': ('host', str),
    'backup_ftp_settinglogin': ('login', str),
    'backup_ftp_settingpassword': ('password', _decrypt_password),
    'backup_ftp_settingdirectory': ('directory', str),
    'backup_ftp_settingpassive_mode': ('passive_mode', _is_true),
    'backup_ftp_settinguse_ftps': ('use_ftps', _is_true),
    # 'backup_ftp_settinguse_backup_password': ('use_backup_password', _is_true),  # noqa: E501
    # 'backup_ftp_settingbackup_password': ('backup_password', _decrypt_password)  # noqa: E501
}

DomainBackupSettings = Dict[str, Union[bool, str]]
UserBackupSettings = Dict[str, DomainBackupSettings]
BackupSettings = Dict[str, UserBackupSettings]


def _get_backup_settings():
    # type: () -> BackupSettings
    query = (
        'SELECT b.type, c.login, d.name, b.param, b.value '
        'FROM BackupsSettings AS b '
        'LEFT JOIN domains AS d ON b.id=d.id '
        'LEFT JOIN clients as c ON d.cl_id=c.id '
    )

    with open(PLESK_SHADOW) as f:
        password = f.read()

    with pymysql.connect(user='admin', password=password, db='psa') as cur:
        cur.execute(query)

        settings = {}
        for setting_type, login, domain, param, value in cur:
            if param in PLESK_BACKUP_SETTINGS:
                if setting_type == 'server':
                    login = 'admin'
                    domain = ''
                key, value_type = PLESK_BACKUP_SETTINGS[param]
                user = settings.setdefault(login, {})
                domain_settings = user.setdefault(domain, {'active': True})
                domain_settings[key] = value_type(value)

        return settings


def _get_config():
    # type: () -> Dict[str, str]
    try:
        return _parse_config(PLESK_CONFIG)
    except FileNotFoundError:
        return _parse_config(PLESK_CONFIG_DEFAULT)


def _parse_config(path):
    # type: (str) -> Dict[str, str]
    config = {}
    with open(path) as f:
        for line in f:
            line = line.strip()
            if line and not line.startswith('#'):
                key, value = line.split()
                config[key] = value
    return config


PleskBackup = Union[PleskDomainBackup, PleskDomainFtpBackup, PleskFtpBackup]


def _domains_backups(domains, until=None, usernames=()):
    # type: (str, helpers.DateTime, Iterable[str]) -> List[PleskBackup]
    backup_list = []  # type: List[PleskBackup]
    if os.path.isdir(domains):
        for domain in os.listdir(domains):
            if usernames:
                domain_home = os.path.join(DomainUserDataResource.root, domain)
                domain_owner = _get_domain_owner(domain_home)
                if domain_owner not in usernames:
                    continue
            domain_dumps = os.path.join(domains, domain)
            domain_dumps_xml = os.path.join(domain_dumps, '*.xml')
            for backup_xml in glob.glob(domain_dumps_xml):
                backup_date = _backup_date(backup_xml)
                if until is None or until <= backup_date:
                    backup = PleskDomainBackup(backup_xml, backup_date)
                    if backup.resources:
                        backup_list.append(backup)
    return backup_list


def backups_local(until=None, usernames=()):
    # type: (helpers.DateTime, Iterable[str]) -> List[PleskBackup]
    """
    Get list of local backups
    """
    backup_list = []  # type: List[PleskBackup]

    config = _get_config()
    dump_d = config['DUMP_D']

    dump_clients_d = os.path.join(dump_d, 'clients')
    if os.path.isdir(dump_clients_d):
        for client in os.listdir(dump_clients_d):
            if usernames and client not in usernames:
                continue
            client_domains = os.path.join(dump_clients_d, client, 'domains')
            client_backups = _domains_backups(client_domains, until=until)
            backup_list.extend(client_backups)

    dump_domains_d = os.path.join(dump_d, "domains")
    domains_backups = _domains_backups(
        dump_domains_d, until=until, usernames=usernames
    )
    backup_list.extend(domains_backups)

    backup_list = sorted(backup_list, reverse=True)
    return backup_list


def backups_ftp(until=None, tmp_dir=None):
    # type: (helpers.DateTime) -> List[PleskBackup]
    """
    Get list of remote backups
    """
    backup_settings = _get_backup_settings()

    backup_list = []  # type: List[PleskBackup]
    for _, domains in backup_settings.items():
        for domain, ftp_settings in domains.items():
            if ftp_settings['active']:
                backup_cls = PleskDomainFtpBackup if domain else PleskFtpBackup
                try:
                    ftp = helpers.Ftp(**ftp_settings)
                except TypeError:  # missing required positional arguments
                    continue
                try:
                    ftp.connect()
                except helpers.FtpError:
                    helpers.warning('Error connecting to %s' % ftp)
                    continue
                try:
                    ftp_dir = ftp.listdir(ftp_settings['directory'])
                except helpers.FtpError:
                    helpers.warning('Error listing of %s/%s' %
                                    (ftp, ftp_settings['directory']))
                    continue
                for path in ftp_dir:
                    if path.endswith('.tar'):
                        backup_date = _backup_date(path)
                        if until is None or until <= backup_date:
                            backup = backup_cls(
                                ftp, path, backup_date, tmp_dir=tmp_dir
                            )
                            backup_list.append(backup)

    backup_list = sorted(backup_list, reverse=True)
    return backup_list


def _get_domain_owner(filename):
    root = Path(DomainUserDataResource.root)
    path = Path(filename)
    try:
        rel = path.relative_to(root)
        domain = root / rel.parts[0]
        st = domain.stat()
        pw = pwd.getpwuid(st.st_uid)
        return pw.pw_name
    except Exception:
        return None


def pre_backups(files, until=None):
    usernames = []
    for f in files:
        user = _get_domain_owner(f)
        if user is None:
            return
        usernames.append(user)

    return {
        'usernames': usernames,
    }


def backups(until=None, *, usernames=(), tmp_dir=None):
    # type: (helpers.DateTime, Iterable[str]) -> List[PleskBackup]
    """
    Get list of all available backups
    """

    backup_list = []  # type: List[PleskBackup]

    backup_list.extend(backups_local(until, usernames))
    backup_list.extend(backups_ftp(until, tmp_dir=tmp_dir))

    backup_list = sorted(backup_list, reverse=True)
    return backup_list


def cleanup():
    # type: () -> None
    """
    Remove all temp files
    """
    helpers.Ftp.cache_clear()


def is_suitable():
    return os.path.isfile(PLESK_CONFIG)