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/imav
Viewing File: /opt/imunify360/venv/lib/python3.11/site-packages/imav/server.py
""" This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program.  If not, see <https://www.gnu.org/licenses/>. Copyright © 2019 Cloud Linux Software Inc. This software is also available under ImunifyAV commercial license, see <https://www.imunify360.com/legal/eula> """ import argparse import asyncio import gc import logging import os import signal import sys import time from concurrent.futures import ThreadPoolExecutor from contextlib import contextmanager, suppress from functools import partial from pathlib import Path from subprocess import CalledProcessError, check_output from typing import Tuple import daemon from lockfile import AlreadyLocked import daemon.pidfile import psutil import defence360agent.internals.logger from defence360agent import files from defence360agent.api import health, inactivity from defence360agent.contracts.config import ( ConfigsValidator, Core, Merger, Model, SimpleRpc, ) from defence360agent.contracts.hook_events import HookEvent from defence360agent.contracts.license import LicenseCLN from defence360agent.contracts.plugins import MessageSink, MessageSource from defence360agent.internals.global_scope import g from defence360agent.internals.iaid import IndependentAgentIDAPI from defence360agent.internals.the_sink import TheSink from defence360agent.model import instance, simplification, tls_check from defence360agent.simple_rpc import ( NonRootRpcServer, NonRootRpcServerAV, RpcServer, RpcServerAV, is_running, ) from defence360agent.subsys import persistent_state, systemd_notifier from defence360agent.utils import ( Task, create_task_and_log_exceptions, is_root_user, is_systemd_boot, ) from defence360agent.utils.check_db import is_db_corrupted from defence360agent.utils.cli import EXITCODE_GENERAL_ERROR from defence360agent.utils.common import DAY, rate_limit from defence360agent.sentry import flush_sentry from imav.malwarelib.config import ( MalwareHitStatus, MalwareScanResourceType, VulnerabilityHitStatus, ) from imav.malwarelib.model import MalwareHit, VulnerabilityHit import sentry_sdk # Increase recursion depth to allow malware scanner into deeply nested # directories with absolute path length up to 4096 symbols _MAX_RECURSION_DEPTH = 2100 _DB_IS_CORRUPTED_FLAG = Path("%s.is_corrupted" % Model.PATH) _DB_IS_CORRUPTED_MSG = ( "Imunify360 database is corrupt. " "Application cannot run with corrupt database. " "Please, contact Imunify360 support team at " "https://cloudlinux.zendesk.com" ) logger = logging.getLogger(__name__) throttled_log_error = rate_limit(period=DAY)(logger.error) class TaskFactory: def __init__(self): self.pool = set() def __call__(self, loop, coro): task = Task(coro, loop=loop) self.pool.add(task) task.add_done_callback(self.pool.discard) return task @contextmanager def log_and_suppress_error(message): """Log *message* on any error & suppress it.""" try: yield except Exception as e: logger.error("caught error %r on %s", e, message) sentry_sdk.capture_exception(e) async def _shutdown_task(loop, the_sink, plugin_list): with log_and_suppress_error("marking the start of the shutdown process"): health.sensor.shutting_down(time.time()) logger.info("shutdown task starting, pid=%s", os.getpid()) # Phase 1: Stop external sources from sending new messages with log_and_suppress_error("closing external sources"): async with asyncio.timeout(10): if "sensor_server" in g: g.sensor_server.close() await g.sensor_server.wait_closed() # Phase 2: Shutdown plugins by priority (they can still send to TheSink) for plugin in sorted(plugin_list, key=lambda p: p.SHUTDOWN_PRIORITY): with log_and_suppress_error("shutting down plugin"): async with asyncio.timeout(10): logger.info( "Shutting down %s.%s (priority %d)...", plugin.__class__.__module__, plugin.__class__.__name__, plugin.SHUTDOWN_PRIORITY, ) await plugin.shutdown() # Phase 3: Shutdown TheSink after all plugins have flushed with log_and_suppress_error("draining message queue"): logger.info("Shutting down TheSink...") async with asyncio.timeout(30): await the_sink.shutdown() with log_and_suppress_error("shutting down IAID API"): await IndependentAgentIDAPI.shutdown() if (restart_task := g.get("web_server_restart_task")) is not None: with log_and_suppress_error("waiting for web server restart"): await asyncio.wait_for(restart_task, timeout=60) with log_and_suppress_error("stopping loop"): loop.stop() flush_sentry() logger.info("shutdown task finished, pid=%s", os.getpid()) def _daemonize(pidfilepath): logger.info("Run as daemon [pidfile = %s]", pidfilepath) dc = daemon.DaemonContext() dc.pidfile = daemon.pidfile.PIDLockFile(pidfilepath) dc.prevent_core = False dc.umask = Core.FILE_UMASK if is_systemd_boot(): dc.detach_process = False else: dc.detach_process = True dc.files_preserve = defence360agent.internals.logger.get_fds() try: dc.open() except AlreadyLocked: logger.error("PID file already locked by another process") sys.exit(EXITCODE_GENERAL_ERROR) gc.collect() # quirk: somehow this is needed for root logger messages to do not # propagate to specialized loggers, e.g. 'perf', 'nework' defence360agent.internals.logger.reconfigure() async def _initial_files_update(): """Perform update files on start.""" await files.update_all_no_fail_if_files_exist() def _tls_check_reset(loop): # init thread id for simplification.run_in_executor() worker thread loop.run_until_complete( simplification.run_in_executor(loop, tls_check.reset) ) # mark current thread as "main_thread" for more informative error messages # PSSST! simplification.run_in_executor() is main thread now! :-X # tls_check.reset("main_thread") def plugin_instances(objs, pclass): return [p for p in objs if isinstance(p, pclass)] def _start_plugins(loop, plugin_classes) -> Tuple[TheSink, list, list]: plugins = [plugin_class() for plugin_class in plugin_classes] # instantiate sinks sinks = plugin_instances(plugins, MessageSink) for s in sinks: logger.info("Creating sink %r", s) loop.run_until_complete(s.create_sink(loop)) # instantiate sources the_sink = TheSink(sinks, loop) sources = plugin_instances(plugins, MessageSource) for s in sources: logger.info("Creating source %r", s) loop.run_until_complete(s.create_source(loop, the_sink)) the_sink.start() return the_sink, sinks, sources def _start_rpc(loop, the_sink: TheSink): logger.info("Starting RpcServers...") if SimpleRpc.SOCKET_ACTIVATION: rpc_servers = (RpcServerAV, NonRootRpcServerAV) else: rpc_servers = (RpcServer, NonRootRpcServer) for rpc in rpc_servers: loop.run_until_complete(rpc.create(loop, the_sink)) def _get_pids_open(*files): try: out = check_output( ["lsof", "+wt"] + list(files), env={"PATH": "/usr/sbin:/usr/bin", **os.environ}, ) except CalledProcessError as e: out = bytes(e.output) except FileNotFoundError: logger.warning("There is no lsof in /usr/sbin:/usr/bin") return [] except IOError: return [] lines = out.strip().split(b"\n") pids = [int(line) for line in lines if line] return list(set(pids)) def _check_able_to_start(pidfile): if is_running(): # get parent process info ppid = os.getppid() if ppid != 0: parent = psutil.Process(ppid).name() pids_used_socket = _get_pids_open( SimpleRpc.SOCKET_PATH, SimpleRpc.NON_ROOT_SOCKET_PATH ) process_used_socket = [] for pid in pids_used_socket: try: _pr = psutil.Process(pid) except psutil.NoSuchProcess: continue _local_parent = _pr.parent() if _local_parent: _parent_name = _local_parent.name() else: _parent_name = "None" process_used_socket.append( ( pid, _pr.name(), "parent process = %s" % str(_parent_name), ) ) try: with open(pidfile) as file: written_pid = file.read() except (OSError, IOError): written_pid = None throttled_log_error( "Instance of %s is already running. " 'Parent process "%s" with pid "%s". ' "Sockets are in use by %s. " "%s file contents %s pid" % ( Core.SVC_NAME, parent, ppid, str(process_used_socket), pidfile, written_pid, ) ) sys.exit(EXITCODE_GENERAL_ERROR) if is_db_corrupted(db_path=Model.PATH): if not _DB_IS_CORRUPTED_FLAG.exists(): logger.error(_DB_IS_CORRUPTED_MSG) _DB_IS_CORRUPTED_FLAG.touch() else: logger.warning(_DB_IS_CORRUPTED_MSG) sys.exit(EXITCODE_GENERAL_ERROR) else: with suppress(FileNotFoundError): _DB_IS_CORRUPTED_FLAG.unlink() def start(plugin_classes: list, init_actions) -> None: """Common function for agent service startup. plugin_classes is a list of classes implementing message processing plugins. init_actions is a coroutine that will be called prior to starting RPC and message processing.""" if not is_root_user(): logger.info("Imunify agent could be started by the root user only!") sys.exit(EXITCODE_GENERAL_ERROR) args = parse_cli() defence360agent.internals.logger.setLogLevel(args.verbose) if args.log_config or os.environ.get("IMUNIFY360_LOGGING_CONFIG_FILE"): defence360agent.internals.logger.update_logging_config_from_file( args.log_config or os.environ.get("IMUNIFY360_LOGGING_CONFIG_FILE") ) sys.setrecursionlimit(_MAX_RECURSION_DEPTH) _check_able_to_start(args.pidfile) if args.daemon: _daemonize(args.pidfile) systemd_notifier.notify(systemd_notifier.AgentState.DAEMONIZED) health.sensor.starting(time.time()) if not LicenseCLN.is_registered(): health.sensor.unregistered() loop = asyncio.get_event_loop() _cpu = os.cpu_count() # https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor # default's in Python 3.8 loop.set_default_executor( ThreadPoolExecutor(max_workers=min(32, _cpu + 4 if _cpu else 5)) ) loop.set_task_factory(TaskFactory()) try: _tls_check_reset(loop) instance.db.init(Model.PATH) validate_configs_on_start(loop) Merger.update_merged_config() loop.run_until_complete(init_actions()) try: for _stop_outdated in [_stop_pending_cleanup, _stop_pending_patch]: _stop_outdated() except simplification.PeeweeException as e: # we intentionally capture all exceptions here and log them # it may happened on package update or other reasons, we don't # want to start agent in such case logger.error( "Failed to stop pending cleanup/patch. Reason: %s", repr(e) ) sys.exit(EXITCODE_GENERAL_ERROR) # If this is first agent run - we SHOULD download # all of the static files # If it isn't first agent run - essential files already downloaded # and will be updated asynchronously if not loop.run_until_complete(files.essential_files_exist()): logger.info( "Essential files are missing. Performing initial files update." ) try: loop.run_until_complete(_initial_files_update()) except files.UpdateError as e: logger.error("Failed to perform initial files update: %s", e) sys.exit(EXITCODE_GENERAL_ERROR) inactivity.track.set_timeout(SimpleRpc.INACTIVITY_TIMEOUT) the_sink, sinks, sources = _start_plugins(loop, plugin_classes) _start_rpc(loop, the_sink) logger.info("Message Bus started") agent_started = HookEvent.AgentStarted( version=Core.VERSION, resident=False ) create_task_and_log_exceptions( loop, the_sink.process_message, agent_started ) try: persistent_state.remove_unused_locks() except Exception as e: logger.error("Failed to remove unused locks: %s", e) # note: plugins are started before the shutdown task has been setup # therefore plugin.shutdown() won't be called before create_source() _setup_signal_handlers( loop, partial(_shutdown_task, loop, the_sink, sinks + sources) ) loop.run_forever() logger.info("loop stopped") finally: # closing the loop after loop.stop() cuts off pending tasks stacktraces loop.close() def validate_configs_on_start(loop): try: ConfigsValidator.validate_config_layers() except Exception as e: from defence360agent.hooks.execute import execute_hooks agent_misconfig = HookEvent.AgentMisconfig(error=repr(e)) loop.run_until_complete(execute_hooks(agent_misconfig)) logger.warning(str(e)) sys.exit(EXITCODE_GENERAL_ERROR) def _setup_signal_handlers(loop, shutdowntask): called = False # whether the signal handler was called already def _sighandler(loop, sig): nonlocal called if not called: called = True logger.info("Caught %s", sig) # note: store ref, to keep the task alive, just in case called = create_task_and_log_exceptions(loop, shutdowntask) else: logger.info( "Caught %s. Shutdown task is already running, please wait.", sig, ) for sig in (signal.SIGINT, signal.SIGTERM, signal.SIGUSR1, signal.SIGUSR2): loop.add_signal_handler(sig, _sighandler, loop, sig) def parse_cli(): parser = argparse.ArgumentParser(description="Run imunify agent") parser.add_argument( "-v", dest="verbose", action="count", default=0, help=( "Level of logging. Each value corresponds to:" "1 - console only log level," "2 - previous plus add network log," "3 - all previous plus add process message log," "4 - all previous plus add debug log" ), ) parser.add_argument("--daemon", action="store_true", help="run as daemon") parser.add_argument( "--pidfile", default="/var/run/imunify360.pid", help="use with --daemon", ) parser.add_argument("--log-config", help="logging config filename") return parser.parse_args(sys.argv[1:]) def _stop_pending_cleanup(): """ Get back to FOUND all malware hits which have stuck in CLEANUP_STARTED """ hits = MalwareHit.select().where( MalwareHit.status == MalwareHitStatus.CLEANUP_STARTED, MalwareHit.resource_type == MalwareScanResourceType.FILE.value, ) MalwareHit.set_status(hits, MalwareHitStatus.FOUND) def _stop_pending_patch(): """ Get back to VULNERABLE all vulnerabilities which have stuck in PATCH_IN_PROGRESS """ hits = VulnerabilityHit.select().where( VulnerabilityHit.status == VulnerabilityHitStatus.PATCH_IN_PROGRESS, ) VulnerabilityHit.set_status(hits, VulnerabilityHitStatus.VULNERABLE)