PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYsodtIME MeqIDATxw]Wug^Qd˶ 6`!N:!@xI~)%7%@Bh&`lnjVF29gΨ4E$|>cɚ{gk= %,a KX%,a KX%,a KX%,a KX%,a KX%,a KX%, b` ǟzeאfp]<!SJmɤY޲ڿ,%c ~ع9VH.!Ͳz&QynֺTkRR.BLHi٪:l;@(!MԴ=žI,:o&N'Kù\vRmJ雵֫AWic H@" !: Cé||]k-Ha oݜ:y F())u]aG7*JV@J415p=sZH!=!DRʯvɱh~V\}v/GKY$n]"X"}t@ xS76^[bw4dsce)2dU0 CkMa-U5tvLƀ~mlMwfGE/-]7XAƟ`׮g ewxwC4\[~7@O-Q( a*XGƒ{ ՟}$_y3tĐƤatgvێi|K=uVyrŲlLӪuܿzwk$m87k( `múcE)"@rK( z4$D; 2kW=Xb$V[Ru819קR~qloѱDyįݎ*mxw]y5e4K@ЃI0A D@"BDk_)N\8͜9dz"fK0zɿvM /.:2O{ Nb=M=7>??Zuo32 DLD@D| &+֎C #B8ַ`bOb $D#ͮҪtx]%`ES`Ru[=¾!@Od37LJ0!OIR4m]GZRJu$‡c=%~s@6SKy?CeIh:[vR@Lh | (BhAMy=݃  G"'wzn޺~8ԽSh ~T*A:xR[ܹ?X[uKL_=fDȊ؂p0}7=D$Ekq!/t.*2ʼnDbŞ}DijYaȲ(""6HA;:LzxQ‘(SQQ}*PL*fc\s `/d'QXW, e`#kPGZuŞuO{{wm[&NBTiiI0bukcA9<4@SӊH*؎4U/'2U5.(9JuDfrޱtycU%j(:RUbArLֺN)udA':uGQN"-"Is.*+k@ `Ojs@yU/ H:l;@yyTn}_yw!VkRJ4P)~y#)r,D =ě"Q]ci'%HI4ZL0"MJy 8A{ aN<8D"1#IJi >XjX֔#@>-{vN!8tRݻ^)N_╗FJEk]CT՟ YP:_|H1@ CBk]yKYp|og?*dGvzنzӴzjֺNkC~AbZƷ`.H)=!QͷVTT(| u78y֮}|[8-Vjp%2JPk[}ԉaH8Wpqhwr:vWª<}l77_~{s۴V+RCģ%WRZ\AqHifɤL36: #F:p]Bq/z{0CU6ݳEv_^k7'>sq*+kH%a`0ԣisqにtү04gVgW΂iJiS'3w.w}l6MC2uԯ|>JF5`fV5m`Y**Db1FKNttu]4ccsQNnex/87+}xaUW9y>ͯ骵G{䩓Գ3+vU}~jJ.NFRD7<aJDB1#ҳgSb,+CS?/ VG J?|?,2#M9}B)MiE+G`-wo߫V`fio(}S^4e~V4bHOYb"b#E)dda:'?}׮4繏`{7Z"uny-?ǹ;0MKx{:_pÚmFמ:F " .LFQLG)Q8qN q¯¯3wOvxDb\. BKD9_NN &L:4D{mm o^tֽ:q!ƥ}K+<"m78N< ywsard5+вz~mnG)=}lYݧNj'QJS{S :UYS-952?&O-:W}(!6Mk4+>A>j+i|<<|;ر^߉=HE|V#F)Emm#}/"y GII웻Jі94+v뾧xu~5C95~ūH>c@덉pʃ1/4-A2G%7>m;–Y,cyyaln" ?ƻ!ʪ<{~h~i y.zZB̃/,雋SiC/JFMmBH&&FAbϓO^tubbb_hZ{_QZ-sύodFgO(6]TJA˯#`۶ɟ( %$&+V'~hiYy>922 Wp74Zkq+Ovn錄c>8~GqܲcWꂎz@"1A.}T)uiW4="jJ2W7mU/N0gcqܗOO}?9/wìXžΏ0 >֩(V^Rh32!Hj5`;O28؇2#ݕf3 ?sJd8NJ@7O0 b־?lldщ̡&|9C.8RTWwxWy46ah嘦mh٤&l zCy!PY?: CJyв]dm4ǜҐR޻RլhX{FƯanшQI@x' ao(kUUuxW_Ñ줮[w8 FRJ(8˼)_mQ _!RJhm=!cVmm ?sFOnll6Qk}alY}; "baӌ~M0w,Ggw2W:G/k2%R,_=u`WU R.9T"v,<\Ik޽/2110Ӿxc0gyC&Ny޽JҢrV6N ``یeA16"J³+Rj*;BϜkZPJaÍ<Jyw:NP8/D$ 011z֊Ⱳ3ι֘k1V_"h!JPIΣ'ɜ* aEAd:ݺ>y<}Lp&PlRfTb1]o .2EW\ͮ]38؋rTJsǏP@芎sF\> P^+dYJLbJ C-xϐn> ι$nj,;Ǖa FU *择|h ~izť3ᤓ`K'-f tL7JK+vf2)V'-sFuB4i+m+@My=O҈0"|Yxoj,3]:cо3 $#uŘ%Y"y죯LebqtҢVzq¼X)~>4L׶m~[1_k?kxֺQ`\ |ٛY4Ѯr!)N9{56(iNq}O()Em]=F&u?$HypWUeB\k]JɩSع9 Zqg4ZĊo oMcjZBU]B\TUd34ݝ~:7ڶSUsB0Z3srx 7`:5xcx !qZA!;%͚7&P H<WL!džOb5kF)xor^aujƍ7 Ǡ8/p^(L>ὴ-B,{ۇWzֺ^k]3\EE@7>lYBȝR.oHnXO/}sB|.i@ɥDB4tcm,@ӣgdtJ!lH$_vN166L__'Z)y&kH;:,Y7=J 9cG) V\hjiE;gya~%ks_nC~Er er)muuMg2;֫R)Md) ,¶ 2-wr#F7<-BBn~_(o=KO㭇[Xv eN_SMgSҐ BS헃D%g_N:/pe -wkG*9yYSZS.9cREL !k}<4_Xs#FmҶ:7R$i,fi!~' # !6/S6y@kZkZcX)%5V4P]VGYq%H1!;e1MV<!ϐHO021Dp= HMs~~a)ަu7G^];git!Frl]H/L$=AeUvZE4P\.,xi {-~p?2b#amXAHq)MWǾI_r`S Hz&|{ +ʖ_= (YS(_g0a03M`I&'9vl?MM+m~}*xT۲(fY*V4x@29s{DaY"toGNTO+xCAO~4Ϳ;p`Ѫ:>Ҵ7K 3}+0 387x\)a"/E>qpWB=1 ¨"MP(\xp߫́A3+J] n[ʼnӼaTbZUWb={~2ooKױӰp(CS\S筐R*JغV&&"FA}J>G֐p1ٸbk7 ŘH$JoN <8s^yk_[;gy-;߉DV{c B yce% aJhDȶ 2IdйIB/^n0tNtџdcKj4϶v~- CBcgqx9= PJ) dMsjpYB] GD4RDWX +h{y`,3ꊕ$`zj*N^TP4L:Iz9~6s) Ga:?y*J~?OrMwP\](21sZUD ?ܟQ5Q%ggW6QdO+\@ ̪X'GxN @'4=ˋ+*VwN ne_|(/BDfj5(Dq<*tNt1х!MV.C0 32b#?n0pzj#!38}޴o1KovCJ`8ŗ_"]] rDUy޲@ Ȗ-;xџ'^Y`zEd?0„ DAL18IS]VGq\4o !swV7ˣι%4FѮ~}6)OgS[~Q vcYbL!wG3 7띸*E Pql8=jT\꘿I(z<[6OrR8ºC~ډ]=rNl[g|v TMTղb-o}OrP^Q]<98S¤!k)G(Vkwyqyr޽Nv`N/e p/~NAOk \I:G6]4+K;j$R:Mi #*[AȚT,ʰ,;N{HZTGMoּy) ]%dHء9Պ䠬|<45,\=[bƟ8QXeB3- &dҩ^{>/86bXmZ]]yޚN[(WAHL$YAgDKp=5GHjU&99v簪C0vygln*P)9^͞}lMuiH!̍#DoRBn9l@ xA/_v=ȺT{7Yt2N"4!YN`ae >Q<XMydEB`VU}u]嫇.%e^ánE87Mu\t`cP=AD/G)sI"@MP;)]%fH9'FNsj1pVhY&9=0pfuJ&gޤx+k:!r˭wkl03׼Ku C &ѓYt{.O.zҏ z}/tf_wEp2gvX)GN#I ݭ߽v/ .& и(ZF{e"=V!{zW`, ]+LGz"(UJp|j( #V4, 8B 0 9OkRrlɱl94)'VH9=9W|>PS['G(*I1==C<5"Pg+x'K5EMd؞Af8lG ?D FtoB[je?{k3zQ vZ;%Ɠ,]E>KZ+T/ EJxOZ1i #T<@ I}q9/t'zi(EMqw`mYkU6;[t4DPeckeM;H}_g pMww}k6#H㶏+b8雡Sxp)&C $@'b,fPߑt$RbJ'vznuS ~8='72_`{q纶|Q)Xk}cPz9p7O:'|G~8wx(a 0QCko|0ASD>Ip=4Q, d|F8RcU"/KM opKle M3#i0c%<7׿p&pZq[TR"BpqauIp$ 8~Ĩ!8Սx\ւdT>>Z40ks7 z2IQ}ItԀ<-%S⍤};zIb$I 5K}Q͙D8UguWE$Jh )cu4N tZl+[]M4k8֦Zeq֮M7uIqG 1==tLtR,ƜSrHYt&QP윯Lg' I,3@P'}'R˪e/%-Auv·ñ\> vDJzlӾNv5:|K/Jb6KI9)Zh*ZAi`?S {aiVDԲuy5W7pWeQJk֤#5&V<̺@/GH?^τZL|IJNvI:'P=Ϛt"¨=cud S Q.Ki0 !cJy;LJR;G{BJy޺[^8fK6)=yʊ+(k|&xQ2`L?Ȓ2@Mf 0C`6-%pKpm')c$׻K5[J*U[/#hH!6acB JA _|uMvDyk y)6OPYjœ50VT K}cǻP[ $:]4MEA.y)|B)cf-A?(e|lɉ#P9V)[9t.EiQPDѠ3ϴ;E:+Օ t ȥ~|_N2,ZJLt4! %ա]u {+=p.GhNcŞQI?Nd'yeh n7zi1DB)1S | S#ًZs2|Ɛy$F SxeX{7Vl.Src3E℃Q>b6G ўYCmtկ~=K0f(=LrAS GN'ɹ9<\!a`)֕y[uՍ[09` 9 +57ts6}b4{oqd+J5fa/,97J#6yν99mRWxJyѡyu_TJc`~W>l^q#Ts#2"nD1%fS)FU w{ܯ R{ ˎ󅃏џDsZSQS;LV;7 Od1&1n$ N /.q3~eNɪ]E#oM~}v֯FڦwyZ=<<>Xo稯lfMFV6p02|*=tV!c~]fa5Y^Q_WN|Vs 0ҘދU97OI'N2'8N֭fgg-}V%y]U4 峧p*91#9U kCac_AFңĪy뚇Y_AiuYyTTYЗ-(!JFLt›17uTozc. S;7A&&<ԋ5y;Ro+:' *eYJkWR[@F %SHWP 72k4 qLd'J "zB6{AC0ƁA6U.'F3:Ȅ(9ΜL;D]m8ڥ9}dU "v!;*13Rg^fJyShyy5auA?ɩGHRjo^]׽S)Fm\toy 4WQS@mE#%5ʈfFYDX ~D5Ϡ9tE9So_aU4?Ѽm%&c{n>.KW1Tlb}:j uGi(JgcYj0qn+>) %\!4{LaJso d||u//P_y7iRJ߬nHOy) l+@$($VFIQ9%EeKʈU. ia&FY̒mZ=)+qqoQn >L!qCiDB;Y<%} OgBxB!ØuG)WG9y(Ą{_yesuZmZZey'Wg#C~1Cev@0D $a@˲(.._GimA:uyw֬%;@!JkQVM_Ow:P.s\)ot- ˹"`B,e CRtaEUP<0'}r3[>?G8xU~Nqu;Wm8\RIkբ^5@k+5(By'L&'gBJ3ݶ!/㮻w҅ yqPWUg<e"Qy*167΃sJ\oz]T*UQ<\FԎ`HaNmڜ6DysCask8wP8y9``GJ9lF\G g's Nn͵MLN֪u$| /|7=]O)6s !ĴAKh]q_ap $HH'\1jB^s\|- W1:=6lJBqjY^LsPk""`]w)󭃈,(HC ?䔨Y$Sʣ{4Z+0NvQkhol6C.婧/u]FwiVjZka&%6\F*Ny#8O,22+|Db~d ~Çwc N:FuuCe&oZ(l;@ee-+Wn`44AMK➝2BRՈt7g*1gph9N) *"TF*R(#'88pm=}X]u[i7bEc|\~EMn}P瘊J)K.0i1M6=7'_\kaZ(Th{K*GJyytw"IO-PWJk)..axӝ47"89Cc7ĐBiZx 7m!fy|ϿF9CbȩV 9V-՛^pV̌ɄS#Bv4-@]Vxt-Z, &ֺ*diؠ2^VXbs֔Ìl.jQ]Y[47gj=幽ex)A0ip׳ W2[ᎇhuE^~q흙L} #-b۸oFJ_QP3r6jr+"nfzRJTUqoaۍ /$d8Mx'ݓ= OՃ| )$2mcM*cЙj}f };n YG w0Ia!1Q.oYfr]DyISaP}"dIӗթO67jqR ҊƐƈaɤGG|h;t]䗖oSv|iZqX)oalv;۩meEJ\!8=$4QU4Xo&VEĊ YS^E#d,yX_> ۘ-e\ "Wa6uLĜZi`aD9.% w~mB(02G[6y.773a7 /=o7D)$Z 66 $bY^\CuP. (x'"J60׿Y:Oi;F{w佩b+\Yi`TDWa~|VH)8q/=9!g߆2Y)?ND)%?Ǐ`k/sn:;O299yB=a[Ng 3˲N}vLNy;*?x?~L&=xyӴ~}q{qE*IQ^^ͧvü{Huu=R|>JyUlZV, B~/YF!Y\u_ݼF{_C)LD]m {H 0ihhadd nUkf3oٺCvE\)QJi+֥@tDJkB$1!Đr0XQ|q?d2) Ӣ_}qv-< FŊ߫%roppVBwü~JidY4:}L6M7f٬F "?71<2#?Jyy4뷢<_a7_=Q E=S1И/9{+93֮E{ǂw{))?maÆm(uLE#lïZ  ~d];+]h j?!|$F}*"4(v'8s<ŏUkm7^7no1w2ؗ}TrͿEk>p'8OB7d7R(A 9.*Mi^ͳ; eeUwS+C)uO@ =Sy]` }l8^ZzRXj[^iUɺ$tj))<sbDJfg=Pk_{xaKo1:-uyG0M ԃ\0Lvuy'ȱc2Ji AdyVgVh!{]/&}}ċJ#%d !+87<;qN޼Nفl|1N:8ya  8}k¾+-$4FiZYÔXk*I&'@iI99)HSh4+2G:tGhS^繿 Kتm0 вDk}֚+QT4;sC}rՅE,8CX-e~>G&'9xpW,%Fh,Ry56Y–hW-(v_,? ; qrBk4-V7HQ;ˇ^Gv1JVV%,ik;D_W!))+BoS4QsTM;gt+ndS-~:11Sgv!0qRVh!"Ȋ(̦Yl.]PQWgٳE'`%W1{ndΗBk|Ž7ʒR~,lnoa&:ü$ 3<a[CBݮwt"o\ePJ=Hz"_c^Z.#ˆ*x z̝grY]tdkP*:97YľXyBkD4N.C_[;F9`8& !AMO c `@BA& Ost\-\NX+Xp < !bj3C&QL+*&kAQ=04}cC!9~820G'PC9xa!w&bo_1 Sw"ܱ V )Yl3+ס2KoXOx]"`^WOy :3GO0g;%Yv㐫(R/r (s } u B &FeYZh0y> =2<Ϟc/ -u= c&׭,.0"g"7 6T!vl#sc>{u/Oh Bᾈ)۴74]x7 gMӒ"d]U)}" v4co[ ɡs 5Gg=XR14?5A}D "b{0$L .\4y{_fe:kVS\\O]c^W52LSBDM! C3Dhr̦RtArx4&agaN3Cf<Ԉp4~ B'"1@.b_/xQ} _߃҉/gٓ2Qkqp0շpZ2fԫYz< 4L.Cyυι1t@鎫Fe sYfsF}^ V}N<_`p)alٶ "(XEAVZ<)2},:Ir*#m_YӼ R%a||EƼIJ,,+f"96r/}0jE/)s)cjW#w'Sʯ5<66lj$a~3Kʛy 2:cZ:Yh))+a߭K::N,Q F'qB]={.]h85C9cr=}*rk?vwV렵ٸW Rs%}rNAkDv|uFLBkWY YkX מ|)1!$#3%y?pF<@<Rr0}: }\J [5FRxY<9"SQdE(Q*Qʻ)q1E0B_O24[U'],lOb ]~WjHޏTQ5Syu wq)xnw8~)c 쫬gٲߠ H% k5dƝk> kEj,0% b"vi2Wس_CuK)K{n|>t{P1򨾜j>'kEkƗBg*H%'_aY6Bn!TL&ɌOb{c`'d^{t\i^[uɐ[}q0lM˕G:‚4kb祔c^:?bpg… +37stH:0}en6x˟%/<]BL&* 5&fK9Mq)/iyqtA%kUe[ڛKN]Ě^,"`/ s[EQQm?|XJ߅92m]G.E΃ח U*Cn.j_)Tѧj̿30ڇ!A0=͜ar I3$C^-9#|pk!)?7.x9 @OO;WƝZBFU keZ75F6Tc6"ZȚs2y/1 ʵ:u4xa`C>6Rb/Yм)^=+~uRd`/|_8xbB0?Ft||Z\##|K 0>>zxv8۴吅q 8ĥ)"6>~\8:qM}#͚'ĉ#p\׶ l#bA?)|g g9|8jP(cr,BwV (WliVxxᡁ@0Okn;ɥh$_ckCgriv}>=wGzβ KkBɛ[˪ !J)h&k2%07δt}!d<9;I&0wV/ v 0<H}L&8ob%Hi|޶o&h1L|u֦y~󛱢8fٲUsւ)0oiFx2}X[zVYr_;N(w]_4B@OanC?gĦx>мgx>ΛToZoOMp>40>V Oy V9iq!4 LN,ˢu{jsz]|"R޻&'ƚ{53ўFu(<٪9:΋]B;)B>1::8;~)Yt|0(pw2N%&X,URBK)3\zz&}ax4;ǟ(tLNg{N|Ǽ\G#C9g$^\}p?556]/RP.90 k,U8/u776s ʪ_01چ|\N 0VV*3H鴃J7iI!wG_^ypl}r*jɤSR 5QN@ iZ#1ٰy;_\3\BQQ x:WJv츟ٯ$"@6 S#qe딇(/P( Dy~TOϻ<4:-+F`0||;Xl-"uw$Цi󼕝mKʩorz"mϺ$F:~E'ҐvD\y?Rr8_He@ e~O,T.(ފR*cY^m|cVR[8 JҡSm!ΆԨb)RHG{?MpqrmN>߶Y)\p,d#xۆWY*,l6]v0h15M˙MS8+EdI='LBJIH7_9{Caз*Lq,dt >+~ّeʏ?xԕ4bBAŚjﵫ!'\Ը$WNvKO}ӽmSşذqsOy?\[,d@'73'j%kOe`1.g2"e =YIzS2|zŐƄa\U,dP;jhhhaxǶ?КZ՚.q SE+XrbOu%\GتX(H,N^~]JyEZQKceTQ]VGYqnah;y$cQahT&QPZ*iZ8UQQM.qo/T\7X"u?Mttl2Xq(IoW{R^ ux*SYJ! 4S.Jy~ BROS[V|žKNɛP(L6V^|cR7i7nZW1Fd@ Ara{詑|(T*dN]Ko?s=@ |_EvF]׍kR)eBJc" MUUbY6`~V޴dJKß&~'d3i WWWWWW
Current Directory: /opt/imunify360/venv/lib/python3.11/site-packages/clcommon
Viewing File: /opt/imunify360/venv/lib/python3.11/site-packages/clcommon/utils.py
# -*- coding: utf-8 -*- # Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2022 All Rights Reserved # # Licensed under CLOUD LINUX LICENSE AGREEMENT # http://cloudlinux.com/docs/LICENSE.TXT # import configparser import datetime import grp import io import os import platform import pwd import re import stat import subprocess import sys from configparser import ConfigParser, Error from functools import lru_cache from typing import Any, AnyStr, Dict, Optional, Tuple, Union # NOQA import psutil import secureio from lxml import etree # These commands used to be in this file, but is_ubuntu cannot be imported if they're here # due to a circular import with clcommon.lib.__init__ -> clcommon.lib.mysql_governor_lib. # To avoid that, we move them to a separate file and import into this namespace # so that things like `from clcommon.utils import run_command` still work. from clcommon.utils_cmd import ( # NOQA ExternalProgramFailed, check_command, exec_utility, run_command, ) # Not all imports are used in this file - they are made so that other # modules can import them from utils, not utils_cmd. Therefore, NOQA. RHN_SYSTEMID_FILE = "/etc/sysconfig/rhn/systemid" WEEK_DAYS = ("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun") LITESPEED_STATUS_FOR_USERS = "/opt/cloudlinux/litespeed_status" def create_symlink(link_value, link_path): """ Create symlink link_path -> link_value if it does not exist or points to different location :param link_value: path that symlink should point to (symlink value) :type link_value: str :param link_path: path where to create symlink :type link_path: str """ link_to = None if os.path.islink(link_path): try: link_to = os.readlink(link_path) except OSError: pass if link_value != link_to: try: os.unlink(link_path) except OSError: pass os.symlink(link_value, link_path) def get_file_lines(path, unicode_errors_handle=None): """ Read file and return file's lines errors param may be passed to define how handle unicode errors, errors=None is default value of open() :param path: path to file :param unicode_errors_handle: how to handle unicode errors :return: list of file's lines """ content = [] if os.path.isfile(path): with open(path, "r", encoding="utf-8", errors=unicode_errors_handle) as f: content = f.readlines() return content def write_file_lines(path, content, mode): """ Write lines to file :param content: list of lines for writing to file :param path: path to file :param mode: open mode :return: None """ with open(path, mode, encoding='utf-8') as f: f.writelines(content) def delete_line_from_file(path, line): """ Delete line from file. Return True when line(s) have been deleted, False otherwise (specified line is not found) :param path: path to file :type path: string :param line: line to delete without EOL ('\n') :type line: string :rtype bool """ file_lines = get_file_lines(path) out_file_lines = [item for item in file_lines if line != item.rstrip("\n")] found = len(file_lines) != len(out_file_lines) write_file_lines(path, out_file_lines, "w+") return found def is_root_or_exit(): """ Check whether current user is effectively root and exit if not """ euid = os.geteuid() if euid != 0: try: # Additional admins placed in this special group # by lvemanager hooks to add root-like privileges to them gr_gid = grp.getgrnam("clsupergid").gr_gid if gr_gid in os.getgroups() or os.getegid() == gr_gid: return except KeyError: pass # No group - no privileges print("Error: root privileges required. Abort.", file=sys.stderr) sys.exit(-1) def is_ea4(): """ Detects is EA4 installed :return: True - EA4 present; False - EA4 absent """ return os.path.isfile("/etc/cpanel/ea4/is_ea4") def grep( pattern, path=None, fixed_string=False, match_any_position=True, multiple_search=False, data_from_file=None, ): """ Grep pattern in file :param multiple_search: if True - search all match, False - search first match :param pattern: pattern for search :param path: path to file :param data_from_file: read data from file for parsing :param fixed_string: if True - search only fixed string, False - search by regexp :param match_any_position: if True - search any match position, False - search only from string begin :return: Generator with matched strings """ if data_from_file is None: data_from_file = get_file_lines(path) result = None if not fixed_string: # It's append the symbol ^ to the regexp # if we are searching from the begin of a string and by the regexp if not pattern.startswith("^") and not match_any_position: pattern = f"^{pattern}" pattern_comp = re.compile(pattern) else: pattern_comp = None for line in data_from_file: if fixed_string: if match_any_position and line.find(pattern) != -1: result = line elif line.startswith(pattern): result = line else: if pattern_comp.search(line): result = line if multiple_search and result is not None: yield result elif result is not None: break result = None if result is not None: yield result def _parse_systemid_file(): """ :rtype: lxml.etree._ElementTree obj """ return etree.parse(RHN_SYSTEMID_FILE) # pylint: disable=c-extension-no-member def get_rhn_systemid_value(name) -> Optional[str]: """ find a member in xml by name and return value :type name: str :rtype: str|None """ try: rhn_systemid_xml = _parse_systemid_file() for member in rhn_systemid_xml.iter("member"): if member.find("name").text == name: return member.find("value")[0].text except (IOError, IndexError, KeyError, etree.ParseError): # pylint: disable=c-extension-no-member return None return None def get_file_system_in_which_file_is_stored_on(file_path): # type: (str) -> Dict[str, Any] """ This function is written for detect file system in which file is stored on. E.g., the file can be stored in NFS and this can affect the normal operation of the file. We want to receive information about FS in emergency situations during reading or writing :param file_path: path to file, for which we want to detect file system :return: dict, which contains two keys: key 'success' can be equals to False if we got error or True if we got normal result key 'details' can contais error string if key 'success' is False or result if key 'success' is True """ result = { "success": False, "details": f'File "{file_path}" isn\'t exists', } if not os.path.exists(file_path): return result # Command: mount | grep "on $(df <file_name> | tail -n 1 | awk '{print $NF}') type" # Result: /usr/tmpDSK on /var/tmp type ext3 (rw,nosuid,noexec,relatime,data=ordered) try: mount_point = subprocess.check_output( [f"df {file_path} | tail -n 1 | awk '{{print $NF}}'"], shell=True, executable="/bin/bash", text=True, ).strip() data = subprocess.check_output( [f'mount | grep "on {mount_point} type"'], shell=True, executable="/bin/bash", text=True, ).strip() result["success"] = True result["details"] = data except (subprocess.CalledProcessError, OSError) as err: result["details"] = f'We can\'t get file system for file "{file_path}". Exception "{err}"' return result def is_testing_enabled_repo() -> bool: """ Checks if testing is enabled in /etc/yum.repos.d/cloudlinux.repo config :return: bool value if testing enabled or not """ parser = ConfigParser(interpolation=None, strict=False) try: parser.read("/etc/yum.repos.d/cloudlinux.repo") res = parser.getboolean("cloudlinux-updates-testing", "enabled") # use base exception for config parser class except Error: res = False return res def get_cl_version() -> Optional[str]: """ Returns cl version taking into account release version E.g: release = 2.6.32-896.16.1.lve1.4.54.el6.x86_64 5.14.0-162.6.1.el9_1.x86_64 el6 = cl6 el8 = cl8 ........ ubuntu+cl_extesions = cl8 :return appropriate version string """ if is_ubuntu(): return "cl8" check_vals_decoder = { "el6.": "cl6", "el6h.": "cl6h", "el7.": "cl7", "el7h.": "cl7h", "el8.": "cl8", ".el9": "cl9", # note the dot at the beginning because of possible `el9_1` and so on } release: Optional[str] = platform.release() for check_val, cl_version in check_vals_decoder.items(): if release and check_val in release: return cl_version release = get_rhn_systemid_value("os_release") ret_val = None if release is None: ret_val = None elif "6" in release: ret_val = "cl6" elif "7" in release: ret_val = "cl7" elif "8" in release: ret_val = "cl8" elif "9" in release: ret_val = "cl9" return ret_val @lru_cache(maxsize=None) def get_virt_type() -> Optional[str]: """ Returns virtualization type on current system. It is reachable via virt-what utility. E.g.: 'kvm', 'bhyve', 'openvz', 'qemu' All acceptable outputs are listed here: https://people.redhat.com/~rjones/virt-what/virt-what.txt Output will be returned with at least two rows Sample: > kvm > Furthermore, there is a possibility for multiple text rows Sample: > xen > xen-domU That's why, the result will be taken from a first row. If the output is empty, and there were no errors, the machine is either non-virtual, or virt-what tool isn't familiar with it's hypervisor. But the list of supported hypervisors and containers covers all popular VMs types. :return: virt_type - Optional[AnyStr] - appropriate virtualization type string, - 'physical' if there is no virtualization, - None if there was an error """ try: virt_what_output = run_command(["/usr/sbin/virt-what"]).strip() except (subprocess.CalledProcessError, FileNotFoundError): return None # Check for the non-empty output - virtualization exists if virt_what_output != "": return virt_what_output.split("\n", maxsplit=1)[0] else: return "physical" def check_pid(pid: int): """ Checks for a process existence by os.kill command If os.kill will be used as os.kill(pid, 0), it will just check for a presence of such PID And if such pid can't be reached with kill method, there will be raised OSError """ try: os.kill(pid, 0) except OSError: return False else: return True def is_process_running(pid_file_path: str) -> bool: """Check if process running using pid file Arguments: pid_file_path: path to the pid file of service Returns: bool: True or False """ if os.path.isfile(pid_file_path): with open(pid_file_path, encoding="utf-8") as f: try: return check_pid(int(f.read().strip())) except ValueError: pass return False def is_litespeed_running() -> bool: """ Detects that server works under Litespeed. Note: be careful when modifying this method. It is used in X-Ray, ask @dkavchuk or someone else from C-Projects team for details. return: True - LS working; False - LS not running (stopped or absent) """ if os.geteuid() == 0: pid_file_path = "/tmp/lshttpd/lshttpd.pid" return is_process_running(pid_file_path) else: if not os.path.exists(LITESPEED_STATUS_FOR_USERS): return False else: with open(LITESPEED_STATUS_FOR_USERS, encoding="utf-8") as f: status = f.read().strip() return status == "0" def is_nginx_running() -> bool: """Check if nginx is running Returns: Bool: True or False """ pid_file_path = "/run/nginx.pid" return is_process_running(pid_file_path) def get_passenger_package_name(): """ Return proper passenger package according to apache version :rtype: str """ if is_ea4(): # Package that brings passenger on cPanel has different names on different systems: # CL6 - ea-ruby24-mod_passenger # CL7/8 - ea-ruby27-mod_passenger # CL9 - ea-apache24-mod-passenger # But they all provide "apache24-passenger" package return "apache24-passenger" return "alt-mod-passenger" def is_package_installed(package_name): """ Checks that package installed on server :param package_name: str :rtype: bool """ try: if is_ubuntu(): run_command(["dpkg", "-s", package_name]) else: run_command(["rpm", "-q", package_name]) except ExternalProgramFailed: return False return True def get_package_db_errors(): """ Pick the package manager to check depending on the OS. If we're not on Ubuntu, assume a CL variant with RPM. :return: Error string, if any, None otherwise. """ if is_ubuntu(): return get_apt_db_errors() else: return get_rpm_db_errors() def get_apt_db_errors(): """ Check the dpkg DB as described in https://man7.org/linux/man-pages/man1/dpkg.1.html See `--audit`. :return: Error string, if any, None otherwise. """ try: with subprocess.Popen( ["dpkg", "--audit"], shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, ) as proc: std_out, std_err = proc.communicate() # Should return 1 if the audit fails. if proc.returncode != 0: # Check error return f"dpkg audit error: {std_out}\n{std_err}." except (OSError, IOError,) as err: return str(err) # There is no dpkg DB errors return None def get_rpm_db_errors(): """ Check RPM DB consistency. :return: None - No RPM DB errors string_message - Error description """ doc_link = ( "https://cloudlinux.zendesk.com/hc/en-us/articles/" "115004075294-Fix-rpmdb-Thread-died-in-Berkeley-DB-library" ) try: with subprocess.Popen( ["/bin/rpm", "--dbpath", "/var/lib/rpm", "--verifydb"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, ) as proc: std_out, std_err = proc.communicate() if proc.returncode != 0: # Check error return f"RPM DB check error: {std_out}\n{std_err}.\nSee doc: {doc_link}" except (OSError, IOError,) as err: return str(err) # There is no RPM DB errors return None def silence_stdout_until_process_exit(): """ Upon process exit, Sentry sometimes prints: Sentry is attempting to send 1 pending error messages Waiting up to 10 seconds Press Ctrl-C to quit This causes broken JSON in output. See also this issue: https://github.com/getsentry/raven-python/issues/904 """ sys.stdout = io.StringIO() sys.stderr = io.StringIO() def mod_makedirs(path, mod): """ Create directories with desired permissions Changed in version 3.7: The mode argument no longer affects the file permission bits of newly-created intermediate-level directories. Because it we use umask while creating dirs :param mod: desired permissions """ inverted_mod = 0o777 - (mod & 0o777) with secureio.set_umask(inverted_mod): os.makedirs(path, mod) def is_user_present(username: str) -> bool: """ Check user existence in the system """ try: pwd.getpwnam(username) except KeyError: return False return True def is_uid_present(uid: int) -> bool: """ Check uid existence in the system """ try: pwd.getpwuid(uid) except KeyError: return False return True def is_socket_file(path: AnyStr) -> Optional[bool]: """ Check that file by path is socket """ try: mode = os.lstat(path).st_mode except (FileNotFoundError, IOError, OSError): return None is_socket = stat.S_ISSOCK(mode) return is_socket def get_system_runlevel() -> int: """ Get number of system run level by command `runlevel`. """ output = subprocess.check_output( "/sbin/runlevel", shell=True, executable="/bin/bash", text=True, ) # output: N 5 # there is N - previous value of runlevel, 5 is current runlevel result = output.strip().split()[1] # `S` means single-mode. Equals to level `1` level = 1 if result == "S" else int(result) return level def _get_service_state_on_init_d_system(service_name: str) -> Tuple[bool, bool]: """ Returns state of a service (present and enabled) for init.d system. Returns False, False if a service doesn't exist Returns True, False if a service exists and it's not enabled Returns True, True if a service exists and it's enabled """ runlevel = get_system_runlevel() try: # LANG=C parameter allows to use C programming language (en-US) # locale instead of current, since there can be non-English # results in the chkconfig output, while we search for the `on` output = subprocess.check_output( # the command return non-zero code if a service doesn't exist f"LANG=C /sbin/chkconfig --list {service_name}", shell=True, executable="/bin/bash", text=True, ) except (subprocess.CalledProcessError, FileNotFoundError,): return False, False # split output: # `cl_plus_sender 0:off 1:off 2:on 3:on 4:on 5:on 6:off` output_list = output.strip().split() for state_info in output_list[1:]: state_list = state_info.strip().split(":") is_active = state_list[1] == "on" state_runlevel = int(state_list[0]) if runlevel == state_runlevel: return True, is_active return True, False def _get_service_state_on_systemd_system(service_name: str) -> Tuple[bool, bool]: """ Returns state of service (present and enabled) for systemd system Returns False, False if a service doesn't exist Returns True, False if a service exists and it's not enabled Returns True, True if a service exists and it's enabled """ try: subprocess.check_call( # the command return non-zero code if a service isn't enabled or # it's not present f"/usr/bin/systemctl is-enabled {service_name} &> /dev/null", shell=True, executable="/bin/bash", ) return True, True except (subprocess.CalledProcessError, FileNotFoundError,): try: with subprocess.Popen( [ "/usr/bin/systemctl", "status", service_name, ], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL, ) as proc: proc.communicate() # 0 - service is run # 3 - service is stopped # 4 - service doesn't exist if proc.returncode in (0, 3): return True, False else: return False, False except (subprocess.CalledProcessError, FileNotFoundError,): return False, False def service_is_enabled_and_present(service_name: str) -> Tuple[bool, bool]: """ Returns state of service (present and enabled) :param service_name: name of a service """ cl_ver = get_cl_version() if cl_ver and "cl6" in cl_ver: is_present, is_enabled = _get_service_state_on_init_d_system( service_name=service_name, ) else: is_present, is_enabled = _get_service_state_on_systemd_system( service_name=service_name, ) return is_present, is_enabled def process_is_running(process_file_path: str, strict_match: bool,) -> bool: """ Check that a file in path is running. You can get false-postive if parameter `strict_match` == False, process is not running, but someone on server open file by path `process_file_path` in an editor :param process_file_path: path to a file which is run :param strict_match: we use parameter `process_file_path` as full cmd line with args for comparing if `strict_match` == True. :return: True if it's running, False - is not, """ if not os.path.exists(process_file_path): raise FileNotFoundError( f'Process file in path "{process_file_path}" does not exist' ) for process in psutil.process_iter(["cmdline"]): try: if ( not strict_match and process_file_path in process.cmdline() ) or ( strict_match and process_file_path == ' '.join(process.cmdline()) ): return True except psutil.NoSuchProcess: continue return False def get_weekday(dt: Union[datetime.datetime, datetime.date]) -> str: """ Getting string representation of weekday from datetime.datetime or datetime.date. Returns shortened version of weekday from WEEK_DAYS. """ if not (isinstance(dt, (datetime.datetime, datetime.date))): raise TypeError( f"Require object of type datetime.datetime or datetime.date, but passed {type(dt)}" ) return WEEK_DAYS[dt.weekday()] def find_module_param_in_config( config_path: str, apache_module_name: str, param_name: str, default: int = None, ) -> Tuple[int, str]: """ Helper to parse httpd config for details about mpm module used :param config_path: path for configuration file with modules :param apache_module_name: expected mpm module. Can be `event`, `worker`, `prefork` :param param_name: name of parameter to find :param default: default value for parameter, if there won't be record :return: tuple with param value and text result of operation Example of config file content: <IfModule mpm_prefork_module> ................. MaxRequestWorkers 450 </IfModule> -- <IfModule mpm_worker_module> ................. MaxRequestWorkers 300 </IfModule> -- <IfModule mpm_event_module> ................. MaxRequestWorkers 2048 </IfModule> """ if_module_line = f"<IfModule mpm_{apache_module_name}_module>" section_lines = [] mpm_lines = get_file_lines(config_path) is_section_found = False for line in mpm_lines: line = line.strip() if line == if_module_line: is_section_found = True continue if is_section_found and line == "</IfModule>": # End of section break if is_section_found: section_lines.append(line) # 2. Find directive in found section grep_result_list = list( grep( param_name, multiple_search=True, fixed_string=False, data_from_file=section_lines, ) ) mrw_list = [ directive.strip() for directive in grep_result_list if directive.strip().startswith(param_name) ] # 3. Parse all lines with directive and find correct # There is no custom setting for parameter if not mrw_list and default is not None: # Plesk case, when we can use defaults return default, "OK" elif not mrw_list and default is None: # DA case, when we don't know about defaults return ( 0, f"MaxRequestWorkers directive not found for " f"mpm_{apache_module_name}_module module in {config_path}", ) # There can be few records with MaxRequestWorkers, so we need # to take the last one parts = mrw_list[-1].split(" ") max_request_workers = int(parts[-1]) return max_request_workers, "OK" def get_kmodlve_module_version() -> str: """ Return kmodlve module's version. Content of '/sys/module/kmodlve/version' looks like '2.0-30.el8'. """ kmodlve_module_file_path = "/sys/module/kmodlve/version" if not os.path.isfile(kmodlve_module_file_path): return "" try: with open(kmodlve_module_file_path, "r", encoding="utf-8") as f: module = f.read().strip() module_version, _ = module.rsplit(".", 1) except (OSError, IOError, ValueError): return "" return module_version def proc_can_see_other_uid_and_hidepid_synced() -> bool: """ Find out if system has version of the kernel (according to kmodlve module's version) where fs.proc_can_see_other_uid and hidepid options are synchronized. They are only synchronized if kmodlve module's version is equal to version in synced_kmodlve_versions. """ synced_kmodlve_versions = {"cl8": "2.0-30"} cl_version = get_cl_version() if cl_version not in synced_kmodlve_versions: return False synced_version = synced_kmodlve_versions[cl_version] current_version = get_kmodlve_module_version() if current_version == synced_version: return True return False def get_process_pid(pid_filename: str) -> Optional[int]: """ Detrmines working daemon process pid :param pid_filename: PID filename :return: PID from file or None if error (file not found, etc) """ try: with open(pid_filename, "r", encoding="utf-8") as pf: pid = int(pf.read().strip()) # check that pid is still running os.kill(pid, 0) except (OSError, IOError, ValueError): pid = None return pid def write_pid_file(pid_filename: str): """ Writes pid file """ with open(pid_filename, "w+", encoding="utf-8") as pf: pf.write(f"{os.getpid()}") def remove_pid_file(pid_filename: str): """ Remove PID file """ try: os.remove(pid_filename) except (OSError, IOError): pass def demote(uid, gid): """ Set user's real uid and gid to specified ones. Checking equality of real and effective uids is needed because this function may be used by root with effective uid dropped to user's uid. In that case it is needed to set effective uid back to 0 first. """ def func(): real_uid = os.getuid() eff_uid = os.geteuid() if real_uid != eff_uid: os.seteuid(real_uid) os.setgid(gid) os.setuid(uid) return func def get_mount_point(dirpath: str) -> str: """ Get mount point for dirpath directory from output of `df` utility """ if not os.path.isdir(dirpath): raise OSError(f'Directory "{dirpath}" does not exist') fs_info_cmd = ['/bin/df', '-h', dirpath] fs_info = run_command(fs_info_cmd) # example stdout: # [root@localhost ~] # df -h /home # Filesystem Size Used Avail Use% Mounted on # /dev/vda1 10G 3.1G 7.0G 31% / try: mountpoint_info = fs_info.split('\n')[1] mounted_on = mountpoint_info.split(' ')[-1] except IndexError as e: raise OSError(f'Utility "df" returned unexpected output:\n{fs_info}') from e return mounted_on def is_may_detach_mounts_enabled() -> bool: """ Detect if 'may_detach_mounts' kernel option is enabled. More info on the option: https://cloudlinux.atlassian.net/browse/KMODLVE-512 """ may_detach_mounts_file = "/proc/sys/fs/may_detach_mounts" if not os.path.isfile(may_detach_mounts_file): # Missing option means its enabled for all OS except CL6 return get_cl_version() != "cl6" with open(may_detach_mounts_file, encoding="utf-8") as f: val = int(f.read()) return val == 1 def get_filesystem_type(device: str) -> str: """ Return the file system type for the given device. """ partitions = psutil.disk_partitions() for partition in partitions: if partition.device == device: return partition.fstype return '' def get_os_version() -> Tuple[Optional[str], Optional[str]]: """ Detect system name and version :return: tuple (os_name, os_ver) """ # # cat /etc/os-release | grep -E '^NAME|^VERSION_ID' # NAME="Ubuntu" # VERSION_ID="20.04" # # cat /etc/os-release | grep -E '^NAME|^VERSION_ID' # NAME="CloudLinux" # VERSION_ID="7.9" try: os_release_filename = '/etc/os-release' section_name = 'top' config = configparser.ConfigParser() # config.read('/etc/os-release') with open(os_release_filename, encoding='utf-8') as stream: config.read_string(f"[{section_name}]\n" + stream.read()) os_name = config.get(section_name, 'NAME').strip('"') os_ver = config.get(section_name, 'VERSION_ID').strip('"') return os_name, os_ver except (OSError, IOError, configparser.Error): pass return None, None def is_ubuntu() -> bool: """ Detertmines is this system Ubuntu :return: bool flag is_ubuntu """ os_name, _ = get_os_version() return os_name == 'Ubuntu' def is_secureboot_enabled() -> bool: """ Determines if secure boot is turned on :return: bool flag is_secureboot_enabled """ enabled = False if os.path.exists('/sys/firmware/efi'): enabled = subprocess.call('mokutil --sb-state | grep enabled', shell=True, executable='/bin/bash', stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) == 0 return enabled def get_username(): """ Get username of current user. """ try: return pwd.getpwuid(os.getuid())[0] except Exception: return None