JFIF     "" $(4,$&1'-=-157:::#+?D?8C49:7 7%%77777777777777777777777777777777777777777777777777"H !1AQ"2aqB#R3b$Cr4Ss%Tt&c$!1AQ"a#2B ? }XuAo)8^ IƟ`vUp9jY0Ǧ w)E허2jU`SEKw5]kSno!]:?jc\غV7/9N+{t#8zd/޲3F/=ź3GNquV"/4:{z%ۣI'D@ %88^f}VV)S_2ed^Mx"͟?UC62Q%чmO͓ cq0rŖJ\Õ_Sݶ'|G.q޾D U]nP%EF>˲E"d&'f2s6H]4w IS˶4VbaQ+9]XtNx:M0JNxϙ⟟"{nr;|{%vo\z-wc,*|k}-m55o4W9ؓw߱Yzk .=/oϡȴ^9ҧʹamtQԬZ]4?egjrQ}+)MleE]MPEn!`IK2RUEwVIoͷcp;lśe7΄uN ;rПV8|e\׹9Y-V_G.)XԢOv<;_"ڜ]ߙEr݊'K{KuBJ}KI}24|"v)/ʻo5)6-Tjd7.C]Q&lU,Yk1P4~UKZs|$kX6+屷CUq+N(jlGrpG&UB3#k3\9qfg7O8Kim(AJOO~C#e`i0wĦij$cWh<dtQߺ"NOtG+ZǪ]b5%]v5$)u|qZ柡s-rۖu$MKڎCmN_V'/1u,21pvlc>қeNnֺ|bkl=lǷNOʣlz*]»vȎ[)j[fs[]:s#m6Qt6*Q+`};ßj[F_jcv`r#w}|k<ڞ/r53N8>Kh q_-_??@enſEܥ\D\YAEo+ ޟd}IcY7+t{=ɩ>}i\\JfxzVdSzᔢ]Q^CJի\iceitMM5hڦg')^ et#ۯ"ÿfF->4iؤ2ݷ6#p6^-R̫gETj^I.kӽUp~D9[:/>h> \gJ|ۿؘ>ml9jMK =+*2i=0RiͶۗV{"u]IH`9J_˹KƼK$X-|=ve/ bjxw.9i%NqVJcFYKcTtO,F;%67vYb8֝qq0tUt=DvawsS~~Edzr^F-v{c++ݔ\|9Iy #nOavOY=3690Tcrilwa\˓m$?箵S6U c(.~R7suMhqcMOnKoc*ȣȩEd'J ܜk*_q}%M/7c.|;trddbsdcJev85̤iW Ę 8C# .딖e$sk80^\J众2)Nm~|Idj_ O+6ǻ#(MIz4Qo:օY,:q]̌"lK}{F]ζ)h>ʶ ^ue78_G#rqv$wkk[Q c+վ+ĸZΝFB]VzoiJRke&Kgom_7Wef_7,osJɽE%lzBt>mRs)v8'P0ֲtrOg4p_2`GlhYڦDF/ӚKmtm'P2kqU765fJY:y؊.ox%8V_ִ̌ܞjpqwЮQ;iUcNoOoٸcY w*4soӵkqf$?-jy~0{>?DaL8XL/ɞo+'8 {ʸxգj#Dy)wk̘e۩+%}~;ڼ5xek|y-%ڱ-ʜe:EEScÚ5z|r'&I&яF*F7|[nRF =(4ۖ@. n7@xx:N^8Bg%u/ny6&dR{?8U_Q6Z߯-oh.NR]} qi6~H(j7*uF&l&o8ts]/P89:jW*$w׹Ӌ FxpsCJi.7N q4WU_}7*M#qWiصnk'4ݍl*t^ c<'d:~͗enFQRz9v~ddoTZ̚k7X(wUswO̙fոҁՕ[$IAI>WW~ĪEѢNoeutYߑ-Eixιpxq{FnyfRrjqU᫤]>wPU8)Y-7Wbq㛋w:7ܣ].j%K:y4] %9$I%pT(󨪙VqiYٓ4y~5S/XTDZM2lȪ; S~Kx:(Mn0';-{*qV&|W3S+\֔a{R{s=lYmN9Fn&o'}Vi( ?*qV5ѼCNsM饏zߴ$^O69@ ,$y|jE;gW/u|M?3+ZՕN86յw%|QO㏏S\E#ddsgl+Scl3~~CԕQľ?5_ z߿t11OĶ0>oB9E/SOSk+b&Yn>$툧eg) "!܉(1 uBoJ)/t/,:=7M+1ܺ#CmS^Nz 6[u&]+|Dfj:uZ5-Z^TjMtm>cȳ NdT_,M#Ex;pt۴ͮ#!N iKl!zPծ~$1SiO} HI&g Bf)b%Ko̧kumEnص;V?j>nltOMVۆl>.WueYaw2+qK,?uHiqqSM}~gu3xbcWSy/Xc{%sZ]uaUM;7:cb5G97'7þյW,;$ܛyVjl޻y7S;o6gf.Tг[7/i1Z^rE cUF'P1-?%u&q{fw~27ޡ ^w$?SwP[=R3Y73 4x(Kk&rLȫMKn:RjcI?3Al`vض[POĖSYujj6v+-[xҵ=~zNN>\ɲQ/uufo*e6l;31붏.>w6=7#7dFDc%ƶTbd;2/=?Asr! ~ZSS~I"9y]Hn,ĊJ7S}cK"amCg3yP=RQɤW}t;-{F+v+RɔڎB?º{SV묖kۏmK~%.Q;OfEf_Y/F-V-MdD)m.ZՍ8Y*h[g/6ydmCc[rdfʾ䖗gd$^֍^ʅѻL|<[݉\߯RiJUo';œN?B smS ܹkس,mRE^ѣlJ&.ċ԰YO:޼f\Z'HCѯU[ʩ1ff4S-٥YxTIGLiыr }L)edׂ*l|ٚuoxӿnWkTbbVm zT_'"x5Vިxo1ج^Fq6Sd3ws'/ڞ6m?}1OsRGݝ+,~ڬ%^p1ef5c25vq~﹉ă[r-eq] 8+/ESj}?mUE.xYK3"oƔ^Y9I]I ޑ" &*4.Jâ}ټQbXKJ񽼀ncg`+riܭ_'Bֽp%bX'7cB}WPm|zHָLJhj~E>i~Z$297|_hyΕ&s}ZϷ *j]:v.HK<SP8`Pƣ)r ,}8Wk[ArHgn=о7:J]TTP>OOj J_KyB\Ԥrm嬷ȫr{ݙ5R(FRЪ6q}KLmR'eޖz6[YތesYYL5Tr7s\^rؙV͸컬j5d?yk'b S }kra^ߚRH)[sg.fLM\u= vJQ]rVkZuoN}#G?yjO%|i2fKoӰღC P_Ϳ6Zr{e/m$i}9 G2')YG9KY>|1ӫ +v+i;h\Q@˿Lӭn˖ 7ck>Vr.D0)hC<˄4"0[eԬݭe+l2s3ss oX]1r]+VK vI;mZ')R6e5=/i@]H^Z۬՝EW.jƆf{8mXMV~_̝z^VR}T63}}k3+k3:j1Phlpi{欍BȽ}6w73GtUZv>4eUj$ xz$$D/߇ߟI"uk̜aƪ*ke/F:dһ_PE1ݡkp(5ʏ-ɮ{Yllԧg!ܝ g]i-umεŸxOê^=PR ##XeMy%2L~󜺶Hm ݙ2t_ƶz7'\Z4T<"AM-&xaC]a5.huQ۫$cMμ|h;.J.o߸sE-zU{d];|YLSMvSEneNKr1B[]NeonNߪ$4̘FPrkxޱ=0lr7Q%=$KQ;0r*XKdGۃ*]w-npᬶ\tt4>Dc[Ouo3/)-WҴ xs71eԤm*ٖ웗H''.Cnmy]݊Kra[9)Y#2U6d7tf.[R.GdE>#O_.+-K`{KonR_ÕM/)?:F,Xo1ƽRmz8C]lD %(x+d2Ah+\CCLJ!D65x\ȼv)\Nrp*[YُfL*PyVΚuWA K4hyYdwihNIy#ub?4NDϐ'4 :nFe(o%ve@@xl-k%QƭRP&kεMŪ-Ys2u ]T!}8*TQnZ}v =~mԧyDM&8K>2|Bnugܷ.wvCs̼5F^ubES7ݢM&4Ź-~mKx1((sr!M5uy\q)oy|a)ˣ,A?w"T휳2\F}PR-<2%`~4Z5\W"(USkGpT(~Qj>ɰ쏳ǓSKKx's]nEf'.iݙL>Moƹk7ݭ[.г6lk<;?)#E]xFU7'>vF%R;t:Җs}NSBWX=Y8ث}~G)S^^ƽwR[)/Fm-ڞTK~˓Z]U;RQ=M/"NԝP[-Y9t_8V+}P?Ue{M/O&WWKvc#r'KM'p[±vtpRC/W|7K2Rfm;ljm%Z]^T[6}6iTC }L[uxg7(Z}. SRI)jҞzȶ쳢oYRw$ŷ"J\ǭw{u'R taF{;3hHB\RP(*ZQ]y;;k٥nWbGKv-V?NDҞkd9@z LJ}Kc9C*?V-*[*۸-0.|󲝳ߗZK#%_OFGF$kC$[NNJ7Yn[k~Xzc+Sʲuhsw^^4+nElbƮKD,}YLV=i=|p|_=b5mȵ(~,em#Xƥ.sVoEaWXc.lY uG\m';'*\ӆ}|˯UfQBvo}/"zw + qvMrQ[[AdU2ٽCGgjؖS~Ev%9">$_2Sߚ%ѽ7jX(t#21r{̬F]b()?r[Rı)W[O/6]XL9 vuLh-Ȃ9"'7f!Փ䮿Bf}[lag֧]?Pc#D9EmfK7o*})+n!]qIo^FrNVNo!Eƃd#OP?%ۋ(mPu93ۣ{}2&$%cZ߯LҚY);U afԶd,*'6_?B:R~}^̬~mJ+vC}Ѩe"MY+mi :s쥸;iJeYvBddeK|#5/mzR]F2 JHUU )/S{Ic$=: W)>} @0#URsR=w"L{+ɞ)d|*qq2>[nƨDۋ-G[6½J|{Ѿ4MwyG-Σ Ze{ug>2|'zΤ2%xՑ*<Q̥T')uLkjn(zF-JOR}wn~FV5zq2m'^VS=7Y^RdfeO)>EpX붚w*r*w˿^kڴ{J;K۔sRŶU]p\zn@dx6[+yeH[_m_/I&mv|M5&&-G"v۴^{vg8Y(K_~h0e AxfrzڬkhS/Vy1ϯdW3'͹}{'V-:MW(V/ͷ*E7s\EmEW}bUr'k,P{9?B֫ #[uNrB,wo^{fdF(5tRf.2J-/:~ t0M"d_/c^32*q]yLl^2[ݥZc*vtm213r'tSuM-Խ#o/HF+2VEpmǦޟS?Rs+t:u G8n,Ԛf,hY8SX*rKf>+cpruɬ=DMrXgϸ:~ɲ ~]'5'kElw\=ڞAG&')G9R\_̝1K;nPg&T(ի[^Jҟ"qoӸ.W}3mF>'$<\U6-~?x?B~{^xkpv-vlߣe빹j\(ښsuu6lH(qoaYt?x8}Ie '@b%TݲygV.+O9/W4MsCMuFjYzG.{ds.k(>G~K?ni-=R r}r ?s̥%l5Ϛ9IN6~۩RĢWNʾE[|nb.HY—קWkr1ҺշMNDp)^¸R:w;u1 12]T/Uiʹd%2OC2K*r5S]g凫5 UQ.ȫ– /i91njFkQxuJ1rn%XDžy?s˗վuMGƋ/m^J*RsF))uF,'l{=|nFm9:N\%u#tnXE->e2Y0PũjUȨEŭ|'eʹ[o{Ցms%CGg/}t|snzrvm\g}cÊ94Pvg'L}ّg궮ԱߢO^f.W-sT]M˔ېе<^Н'KuNn_Vl8*Kж^ xsuW51-ᅱFzƉT-kY/9wzDޯ/XlW)gypǚjDɨ~{ݤHCim.[>rqE_Uرx/>|L64%aj;fxӱF(K֓J9՞ -K> I_5Enn´&=Oc%o̟IJZF$۲5I9Wݚ n.WTuѲӏ[4U/9.2zX5\j3ĎEsMq4%9.d[7јc9eNa+sjE';%s#ɤ`ףS=WI쫢.Mv:j/[3:rTF_zt:.z%udW%]xܮVz$Vŗ49[^y.խN~M&mx+wGR~_4KC[ʻ:v>03߶v9x-Mȧ$c:lrCWjeg%ֹ_Nh՝Qɏj^ϛr^.>WhlE5yֵ6\W^确]*гc&^NI[oCDn.ߑ!,m&M_/'Mn$s\r^8|uSZZ1|LV<(zq׮xmٚZƏ%.Ԁs^2𱸒O#&,s[mײ9kޖCoSq&俙qxP.N] 2UǎsM2iN.f r[mcQZmFُE{#[TbҔ*sfaSrn^8N<\_'MarJ6 EQғ|F[S'[~q~kmn[_x?B f5Q١X=g(~[Cx}GO ĺo'e)~dq(Ot`sN=~heu ::m'Cjj>~5V柙cyQD%uqEc{[l^U O]b~eŦۑ'W3&' 2V.^D%G S6\wYNO$. O+^ŵG~haEs^=1*bICzFF4O#,Wu3허ekB\I'tWMߩOG3iFz{rgeM9g r] i3gk&u1r/1kVgR-ɿuF .^;3;?3큦bN̂r4ovMkڞ}[:,IVG<};*-2",>K%bK2Ƨ[w!)ˤ;d?4%Ul2ږec4#ōIw^R_/TFX+*FM[F|a'ߚ2SIMeVGn ~&Y Ym(?ԛ],=|сG4yjk"Q^~ԗ^c,qqrg^-:Uc[E8>>k|nS..LBIc>3i|ZEZXAqm nuOm<; X~mrK=~ ƱrSN<U!F΋WS/|t?K)zd} ,C"ovx?bբs3mX3桭X֖˦kFddhg}$ggSo5jL*NdJis$ EQ\v=0HxzyW~FT_Ƶccg,&=_V(%kq+_÷O'[_[Uڽv F $Ξ9n5EN/4Yy/%*} .jΔ`V_6\VͲohzfOgޯzpj}y}v:34WH;+x7ӻu<ݦ"mJ/=>eoD֣c4kXW-[}٬6;t[Na_• _5i5˗sٴ]+e;Joj㼶ۙyLumo5&F)F\ {(sm_M>gzcr)KU̠Ħ=VDd'h;-aŤ9KٰqQܫަazMp4bk9 UX.ͮ]KeS5Uq[¹X0ɦ6]roFjʧ2׏6/C6eQE5KӰmsFnIz&`z팡-ٯ.ixyك?c2//z6M4W[]_"?Õ[? Vfvӳq]I5(d|MʝzcC*mN>B2gD+><e:Gh %UkW%zJ8k_ˠ=KFRfw{sŖ^q\/{v[Ω}gLjT[t_ޕg6G~rkkMcSRKբ54?SAûO1o%[>5/R~CioNdNʛćh>f6H8c/<1xd[ŦCEk.9"ej?w&O6^ژR[vrQ.z㎩f6:V8}hi2z~ s-w]+|I9s_C~>-S&9ZFVLf7-d'pՠplJ#mm؎s(?Ʋ?/A%_sXuGNnR}_dq>1ʍ|У3]NXYZʷ/&ܛ彖LS? 6]"_t5qP5Kq]^m91jW暹U6-5WU澦M0˵f2ӪǮ.P~? _nEJTcTei)ٳrۣ%x %gs}7l9'tb~dXst# r?}Weaq>=+to)7،E*vn\e_,\NFxcivz]tM˼?Oԝ2Zrλs-ĺEtonIIfm/9^[^EBUjOnr6vI& l]%0")2䒶-+R*zyX<> -X9GUo^xYQ8ιvixٔa\t)hv}ьոVU~tK,=_wLLa?TYIo]$`N6cbi?#7;MRt<.~Q-mob\\g5췍 ڌ_?8nfJN/Y͢n3?_sϩ{HiְPo'yS??_jߡWi5q? MWȲ)8a]lLˏ--b[TXlΫRy;o5뜾$HW.mm?շG[Ƀ seo5Q}Le%*،«~uU{R$t\^%!weX:G('6WupTS&~8=jo?2_PϖE[nf6Tٯ;GLW)NM[o*\j%.gb|䭹noOX:1R)UTj74˓]D_bʝkzNI.9|^G`KeQ{mOjX/sR7evdgi7qm}ތW&4=~|YY)?7Oj}xXkF×4c.l?i|b[5Ή5j-[Y\z<茲Z$Ff&o;gErǩݦ̪/q[&[/9uuzi;PS^_/?]=ΕqK~ӛ5'NM[m_Ϲc'[oӯE#g߂vvGNRo϶o5Ǩ[ɉtov2~i<7iSȜN(G5+/ٛMTܣukj鷣/$1˒!Mxr\ߤs1ZuMQȌ^]c$CXrj#N/˦Ķ9]Nzê5zi;W,v!ŧD6zğ7uR5^MW}>igl2U2nXo{}_w]&vte\Z3 MEEe/ 2s㗼S_bIղTI}|[Ye/c]*̪9u/DmyNxSDgi `Z?.RFj۪'~.[KVb޺o濡to?E#[.^y=q4F8ڎ/GX\.YW!Z.ѕtt:?gYYyU%Uw~ri>ȦKhg,5/=>V?TrN4aWO,oӕ7-SRi*"dܽpuaVQÞd-#J2Nr:#``ѧWR-F?I-T -cOT2pr?þזgE\Ij~L9%EMoџUؙt8_eYΧWjU}e9y9z/#TT-2dLt3H=ڼcKb'"uIٓ'[[߱F~\2]r%C]^VCLjm[cJNryf}ջ.[DEoRՒb'>fVy_c6[K4Na5>{ɳaw/Uj.Զ_K~?IeJ7OQx3IgFc*جɊǽ-o3Ӭp / ]7V*ENܜ[r/tOJΉw*ʨ*JFN^.WZeLgUwKi/M9y8dkOᛊHxGĶM*&#h/U|6D(uFyE5hYxiSEVm^D|,ۿCj;<*ouOkYpΔ2{x-L] !k2ا#IM'a7:M}M1Y儭Mnk[/;4Uwkkɫ%aɔoXVV$m;2Z4i9:>\Yů= ?[{t6,~!c`Un+dW.gKyIB]l+3kض(\MZ\}>k\C~閹l[ů]VNtƸr몮X+U>v'nv{y7s[г̭9Ctvt% GqT8=wa(6\Rd柮YWv^Fd^\+緉,+=-^S"k:NVu o[_TIѝ椯bF/G㿏dΙ?T}K-T)W>s?3M)V*,;P\,}B u{rDexڥVFfw}47׋w}]Դ 1dmk1V%/'T:Fǒ_TEe[l/l/ٯc{Ƀ[~`zj⾥r}Vܪ{M8Qv]$mU]8J2MngcxY?鑞.9HjxSy.fS(|]MgcK2$(jRQ3XO|<f:Jq4& fw|$N )A8ת99 mFNM*Dϒ NoIa9i9y?:D⻧߇\7ɧ]mu"-˥5/w̨_ 7DK['[2"(%xzT\*GT"+<,yX.lEJrfo?.4N;l>jmZߣ5FdB3\r,t,./S]Q{tm5lӕT~A [fv7Iہc: ΪN7I]2(|o$NLW"#~Dͭ=v-Mv{-lqn{I3xn'6.=DƟܖަ~deQV;k2Ei\[bӴ1_]OhZl朠&t3xkei+c\'ZԪ'hK梿X@cTԫ#emIz6e^i?8 NBc̆f+MׇdC]YSd%lώ8-c7eι/}_con/no\핍~[WNReXMo+اn ?#Ͷ-AUFN1V4!y,{1a$S﹑;Ǚr"__[o) xk}7EI/riwؙ7mR}`|yrEVdo/B# uٳiNQKQkᑑ^d@/=ˑɒ768fsuor9=7ףܹճpMr-$1uySOZN?đrqզ9F q=.!T?ػ bf{¯q=$^:!ES߿ Fu\OS,8e^UוS^hF4BQƺȪw-kF39@X06 Fv=Q^|ƞ5}2tnmG_|Λ(|%](-5>KȁN$=6lq).12 V6m$ׇlOcҫܸ K{;ľ>+Q?Rx-Keu uMy$i B}G*h$Q -W[-&a"[i\}~Ek$<~c{MffS eS.#\^lMiytު]9S{u4 {DFޅSź}R ]R$y;r/P̙3niXMt;&!rxw\ZFmQ"w\L{^۔K&/gr:m=2%5bwE"^e[\$ɟPi!U_rdS2d?=[!(I.rC QZEim%}|YmzZ_ά<ۡLQM|` ybPȏ}?]Eu[`kҫgFb~F}Q8NP>5lӳ^-K%Q}$sx7SvnfTƸ|Kzd'_ⰽח$4L Y?qy32t j2e ȜrJ{mبhۍUU'p#8y'ѝ=i+Tĩo7WYyČkL5؝M=%"Nt}eXW)N.~sv5pɮ sSQ[+-/}kVk'FEɩ9SE&T=&\緵 --tf.9Ѳ4_##_ɱTFV؞~YTddS&s=䟚Fb1._5}~gM'p#,U hs--XG wtԹTi7M:GYK5'^W?C>_Gq/S&d| k_gO ӊiJeHU G_ Êg#),}-:5>V1emq}t}q?meKU:BqJeiPɗ#\$sI} Z生ƫoo=V=pVcUg"%wEm叡vIdhrȔ~F]p58_.,O|'Ɇ^L!c6OWӷ{x9?Fp?ceOuT+Uɵݹ&gx9i퓃sxGIm}_3Īr#:ԣ?4בc[jö#B7KʌWNo)=+c }YvP{lv^r+5Vxx_:~=̌Q}CTy+Wh鸚f$101뢊F[#--Y\i@l)W8/E>8nlj/ktOľ,q*[sE[]:?ZeQvŔɺ|j(Wx,LW=:S?κq%81c)jJvODLiW,{96vr-2}-EH,}%3k#l5gl~x__W Sڎ 8YJQvA=QIWju6-X9$kWЩCI4UWd'&O/Cf=Pi/#+>n$KYst܅y4ʷD^~%~myj,s_4Q}΍Cή;SW:h=Ff{.B/inȇo=-T͸OY2}hlK}.m7-z?,f-/^b\QWs/_͔/3In[6M;l ygؼ!WUË_)D9YL4_>f}ϵ3hV5Oѣ(l8?L4蹥[-Э=7V{&ʢPʼ*3cMz>u4@[oM gKS[jy"Lھzɵfx)GE`ֿ.=kJ>/iˢ[j-qץQC B@o V(ʯG?Bܻ\I>=K-].(vOE.5׮=/Pf^&$caY9{3މ%YOxZ~6Z;;ԗ.NJzş/YϖĜ%ѿO^tY$ν4|e}2ɶU9A؜h˺LrIm%J.|I]kG|DzU k4'(T\9߱^!z -:mW^ <= <^2*;Seq(6ªsHf5ʸO{Ilr~G uJY^k5X_y;5'59O@ƣ̶>pnCOvNwX4oUUf]Џe%MV9Xm9]x'Q=82z)c/~1\~LSow>ﺍƻUql~Sqo羘sk}VjG71kYؽ]b4qnMӡ; w@̇IL㿗[43)]=v*)EH'a񖳋ҎTkxuXGK& ZIR(M8?:ixJp-dmckpu*%N^-7E3='ceE&';_J'Mw𶥏Y9+d9+>!e_Sn|VX -TZu]Ģ/6\ckr /ޗ/z[y.N:*k$ }Yǭ}GUm^-%dm;K_#ctBsg2:8rz-VE|T w.}w9NEPGnoCe8/&3qT}MJ̙Mۗ~哳,-WI_Bsh+~͛vN{ZdYKݲkr%+lo*re-ه?:vYqFfCsqMXRķ{yqgrx.oǓ\xdڗ_ZC9WomX|KmV_%UJܷr$drȳL~MoKyYLic Jq<1$UuٯTד374s<ĕ96춉r9 pGc9=p^:)ZJb&VӝXٽ 0/X& ۳*_ԙƏ.5J 6<$$6B0d_d?hqd>XCe- wO@pg:.>$.Ϣ~L޲|,{-ɪ2.u/Ds-[ُiVIWK5M#Fܭ3?x.)ۣ,wJ)Ȳڣ-#fbdq&Tͧ8Q,YqQ)/R­?\k˔[p_+ogzP[6r^o}_kT}JiJ;<ivEH8wI@MOPʊ\#+$%PDF-1.7 GIF89;
ANDA PELER
Server IP : 182.253.108.180  /  Your IP : 18.222.110.70
Web Server : Apache
System : Linux sma1wiradesa.sch.id 4.15.0-213-generic #224-Ubuntu SMP Mon Jun 19 13:30:12 UTC 2023 x86_64
User : wijaya ( 1017)
PHP Version : 7.3.33-10+ubuntu18.04.1+deb.sury.org+1
Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : ON  |  Sudo : ON  |  Pkexec : ON
Directory :  /usr/bin/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ HOME ]     

Current File : /usr/bin/postgreyreport
#!/usr/bin/perl

# postgreyreport by tbaker@bakerfl.org
# bits and peices of code taken from postgrey 1.11 ( http://isg.ee.ethz.ch/tools/postgrey/ )

package postgreyreport;
use strict;
use BerkeleyDB;
use Getopt::Long 2.25 qw(:config posix_default no_ignore_case);
use Net::Server::Daemonize qw( get_uid get_gid set_uid set_gid );
use Pod::Usage;
#use Net::RBLClient;
my $VERSION='1.14.3 (20100321)';

# used in maillog processing
my $RE_revdns_ip   	= qr/ ([^\[\s]+)\[(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\]/;	# ptr[1.2.3.4]
my $RE_reject 		= qr/reject: /;
my $RE_triplet 		= qr/$RE_revdns_ip: 450 .+from=<([^>]+)> to=<([^>]+)>/;

my $dns; my %dns_cache; 		# used for --check_sender 
my $rbl = undef;			# Net::RBLClient object
select((select(STDOUT), $| = 1)[0]); 	# Unbuffer standard output.

# default options, override via command line
my %opt = ( 	
	user 			=> 'postgrey',
	dbdir 			=> '/var/lib/postgrey',
	delay			=> 300,
	return_string		=> 'Greylisted',	# match on this string

	check_sender		=> '',			# = mx,a,mx/24,a/24 # todo=spf - uses Net::DNS
	show_tries		=> 0,			# number of greylist attempts within --delay
	separate_by_subnet	=> '',			# if not blank output this string for every new /24
	separate_by_ip	   	=> '',			# if not blank output this string for every new IP
	single_line	   	=> 1,			# output everything on a single line? (grouping enabled if false )
	tab			=> 0,			# use tabs as separators, not spaces (only in single line mode)
	show_time		=> 0,			# show entry time in maillog
	
	skip_dnsbl		=> [],			# list of DNSBL servers to check and skip reporting for
	skip_clients		=> [],			# files of clients to skip reporting	
	skip_pool		=> 0,			# skip entries that appear to be a provider pool (last 2 ips in ptr)
	match_clients		=> [], 			# files of ONLY clients to report on

	v 			=> 0,			# verbose? used mainly for script debugging
	debug_db		=> 0,			# output time() values from btree db
	debug_re		=> '',			# but only for these hosts (separate by commas )
	);

# start here 
sub main
{
	GetOptions(\%opt, 
		'help|h', 'version', 'man',
		'delay=s', 'user|u=s', 'dbdir=s', 
		'debug_db', 'debug_re=s', 'v+',
		'return_string|greylist-text=s',
		'show_tries', 
		'check_sender=s',
		'separate_by_subnet=s', 'separate_by_ip=s',  
		'single_line!', 'tab', 'show_time',
		'skip_dnsbl=s@','skip_clients=s@', 'match_clients=s@', 'skip_pool', 
		) or exit(1);
	if($opt{help})     { pod2usage(1) }
	if($opt{man})      { pod2usage(-exitstatus => 0, -verbose => 2) }
	if ($opt{version})	{ print "postgreyreport $VERSION\n"; exit(0) }

	if (scalar(@{$opt{skip_dnsbl}}) > 0) {
		require Net::RBLClient;
		$rbl = Net::RBLClient->new ( lists => $opt{skip_dnsbl} );
	}


	setup_debug();		 # display key/value pairs from db
	read_client_files();
	
	postgrey_fatal_report(); # do the work
}

#######################################################
# postgrey_fatal(): report on all fatal triplets
#
sub postgrey_fatal_report()
{
	umask 0077;							# mode 600
	my %triplets;							# hash of all triplets we will look at
	drop_priv($opt{user});						# change UID to 'postgrey'
	
	# convert --check_sender into hash: opt{do_checks}{VAL}
	if ($opt{check_sender})	{ 
		use Net::DNS; 
		$dns = Net::DNS::Resolver->new;
		$opt{check_sender} = lc $opt{check_sender};
		foreach my $check ( split(/,/,$opt{check_sender}) ) {
			$opt{do_checks}{$check}=1;	
			print "Enabling Check: opt{do_checks}{$check} \n" if ($opt{v});
		}
	}

	my $db = setup_dbm($opt{dbdir});				# connect to BerkeleyDB
	my @greyfatal = find_and_sort_fatal( \%{$db}, \%triplets );	# read STDIN and sort the fatal triplets
	
	# foreach: loop through (sorted) fatal triplets and display to STDOUT
	my ($last_ip,$last_subnet);					# define now

	$opt{separate_by_ip} 		=~ s|\\n|\n|g;			# do it once before the for loop
	$opt{separate_by_subnet} 	=~ s|\\n|\n|g;			# ""
	
	foreach my $key (@greyfatal)
	{
		my ($ip,$sender,$recipient) = split(/\//,$key);		# separate the triplet
		
		my $revdns = $triplets{$key}{revdns};			# we saved revdns during maillog parse, so we dont have to look it up

		# --check_sender=mx,mx/24,a,a/24
		# dns lookups from Net::DNS are cached and only performed once per sender's @domain
		my $check_sender = '';
		if 	( $opt{do_checks}{mx} 		and check_sender_mx( $sender,$ip,'mx') 		) {
			$check_sender='MX';
		} elsif	( $opt{do_checks}{'mx/24'} 	and check_sender_mx( $sender,$ip,'mx/24')	) {
			$check_sender='MX/24';
		} elsif	( $opt{do_checks}{a} 		and check_sender_a(  $sender,$ip,'a') 		) {
			$check_sender='A';
		} elsif	( $opt{do_checks}{'a/24'} 	and check_sender_a(  $sender,$ip,'a/24') 	) {
			$check_sender='A/24';
		}

		# if separate_by_ip or separate_by_subnet display configured text
		if ($last_subnet eq $triplets{$key}{subnet}) {
			print "$opt{separate_by_ip}" 			if ( ($last_ip ne $ip) and $opt{separate_by_ip}) ;
		} else  {
			if 	( $opt{separate_by_subnet}	) {
			 print    $opt{separate_by_subnet};
			} elsif ( $opt{separate_by_ip} 		) { 
			 print     $opt{separate_by_ip};
			}
		}

		# display output on single line or multi-line
		if ($opt{single_line})
		{
			if ($opt{tab}) {
				printf "%s\t", $triplets{$key}{entrytime}	if($opt{show_time})	;
				printf "%s\t", $triplets{$key}{counter}  	if($opt{show_tries})	;
				printf "%s\t", $check_sender			if($opt{check_sender})	;
				printf "%s\t", $ip							;
				printf "%s\t", $revdns							;
				printf "%s\t", $sender							;
			} else {
				printf "%s ", $triplets{$key}{entrytime}	if($opt{show_time})	;
				printf "%s ", $triplets{$key}{counter}  	if($opt{show_tries})	;
				printf "%5s ", $check_sender			if($opt{check_sender})	;
				printf "%15s ", $ip							;
				printf "%s ", $revdns							;
				printf "%s ", $sender							;
			}
			printf "%s\n", $recipient;						;
		} else 
		{
			### multi-line
			
			## only output PTR - IP if its a new IP (grouping)
			printf "%-77s ", $revdns 			if($last_ip ne $ip)	;
			printf "%15s"  , $ip  				if($last_ip ne $ip)	;
			print  "\n"   					if($last_ip ne $ip)	;
			
			## always output the new pairs MX/A? (sender/recipient)
			
			# if sender was from MX or A of above IP			
			printf "%5s "  , $check_sender			if($opt{check_sender})	;
			printf "      ", $check_sender			if(! $opt{check_sender});
			# tries or blank space
			printf " %2s ", $triplets{$key}{counter}  	if($opt{show_tries})	;
			print  "    " 					if(! $opt{show_tries})	;
			
			# sender - recipient
			printf " %40s ", $sender						;
			printf " %40s ", $recipient						;
			print  "\n"								;
			
		}
		($last_ip, $last_subnet) = ($ip, $triplets{$key}{subnet}); # save for next iteration
	}
	
}

#####################################################################
# find_and_sort_fatal( \%db, \%triplets )
#  read STDIN (maillog) and remember any 4xx greylisted log entries
# return array of fatal triplets (ip/sender/recipient) sorted by ip
sub find_and_sort_fatal
{
	my ($db, $triplets) = @_;
	
	# while(<>): STDIN is maillog.0, looking at reject: 4xx greylist entries and remembering all triplets
	MAILLOG: while (<>)
	{
		next unless (/$RE_reject/o);				# only look at reject: lines
		next unless (/$opt{return_string}/o);			# only look at greylisted lines
		next unless (/$RE_triplet/o);				# extract the triplet
		my ($revdns,$ipaddr,$sender,$recipient) = ($1,$2,$3,$4);
		my @ip = split(/\./, $ipaddr);
		$sender      = do_sender_substitutions($sender);		
		my ($subnet) = do_client_substitutions($ipaddr,$revdns); # 1.2.3.0
		my $key    = lc "$ipaddr/$sender/$recipient";		# postgrey key
		my $subkey = lc "$subnet/$sender/$recipient";		# subnet key 1.2.3.0/sender/recipient

		# if we are wanting to dump first,last out of the db do it before we determine if its fatal
		if ( is_debug_host($revdns) )
		{
			foreach my $testkey ( @{[$key,$subkey]} )
			{
				my ($tfirst, $tlast) = split(/,/,$db->{$testkey});
				my $tdiff = $tlast - $tfirst;
				print "$testkey : $db->{$testkey} = " .$tdiff . "s \n";	
			}
		}

		# if --match_clients was specified on command line then move on to the next line unless a match is found
		if ( scalar(@{$opt{match_clients}}) > 0 ) {
			next unless (	find_in_array($ipaddr, $opt{MATCH_CLIENT_IPS}) or 
					find_in_array($revdns, $opt{MATCH_CLIENT_PTR})     );
		}

		# if --skip_clients was specified on command line, skip to next line if a match is found
		next if (	find_in_array($ipaddr, $opt{SKIP_CLIENT_IPS}) or
				find_in_array($revdns, $opt{SKIP_CLIENT_PTR})	  );			

		# if --skip_pool then if last 2 ips are in ptr skip to next line
		next if ( $opt{skip_pool} and defined $ip[3] and $revdns =~ /$ip[2]/ and $revdns =~ /$ip[3]/ );
		
		# check the db, proceed if the triplet was fatal
		next MAILLOG unless is_fatal_triplet($db, $key, $subkey);	

		# if --skip_dnsbl then do RBL lookups (slow!)
		if ( defined $rbl ) {
			$rbl->lookup($ipaddr);
			my @listed = $rbl->listed_by;
			next if ( scalar(@listed) > 0 );
			
		}
		
		# we made it past all the filtering checks, remember the triplet as fatal

		$triplets->{$key}{counter}++;				# increase counter for this triplet
		$triplets->{$key}{revdns}=$revdns;			# save its ptr for later use
		$triplets->{$key}{ipaddr}=$ipaddr;			# save IP in easy to access form
		$triplets->{$key}{subnet}=$subnet;			# save subnet in easy to access form
		$triplets->{$key}{subkey}=$subkey;			# save key in subnet form
		$triplets->{$key}{entrytime}=substr($_,0,15);
		
	}

	die "Debugging DB active, report shutdown" if ($opt{debug_db}); # don't do anything other than spit out key pairs and stop

	my @greyfatal = keys %{ $triplets }; 				# create an array containing all triplets in form: ip/sender/recipient
	# sort fatal triplets by IP address
	@greyfatal = sort {
		    pack('C4' => $a =~
		      /(\d+)\.(\d+)\.(\d+)\.(\d+)/)
		    cmp
		    pack('C4' => $b =~
		      /(\d+)\.(\d+)\.(\d+)\.(\d+)/)
		  } @greyfatal;
		  
	return @greyfatal;			
	
}

sub find_in_array($$)
{
	my ($var, $patterns) = @_;
	for my $w (@{$patterns}) {
		return 1 if $var =~ $w;	
	}
	return 0;
}


sub is_fatal_triplet($$$)
{
	my ($db, $key, $subkey) = @_;
	
	my ($lapsed_ip, $lapsed_subnet) = (undef,undef);
	
	# try lookup by key
	if ( $db->{$key} =~ /,/ )
	{
		my ($tfirst,$tlast) = split(/,/,$db->{$key});		# time_first_seen,time_last_seen
		$lapsed_ip = $tlast - $tfirst;				# difference is time lapsed
	}
	
	# try subnet lookup	
	if ( $db->{$subkey} =~ /,/ )
	{
		my ($tfirst,$tlast) = split(/,/,$db->{$subkey});		# time_first_seen,time_last_seen
		$lapsed_subnet = $tlast - $tfirst;
	}
	
	if (   
	      ( defined $lapsed_ip or defined $lapsed_subnet )  
		and
	    (!( ($lapsed_ip >= $opt{delay} ) or ($lapsed_subnet >= $opt{delay}) ) )   
	   )    
	{
		#push (@greyfatal, $key); 	# if lapsed time less than --delay, then it was a fatal triplet
		return 1;
	} elsif (( ! defined $lapsed_ip ) and ( ! defined $lapsed_subnet ))
	{
		#push (@greyfatal, $key); 	# if neither is found in the db it must have been removed.
		return 1;
	}
	return 0;
}	


###########################################################################
# check_sender_mx(sender, ip, subnet) # subnet='' or '/24'
# return true if ip is in MX list for sender domain (or /24 if specified)
# enable via --check_sender=mx or --check_sender=mx,mx/24
sub check_sender_mx($$$)
{
	my ($sender, $ip, $subnet) = @_;
	my ($user, $hostname) 		 = split(/\@/,$sender);
	my @iplist;

	if ( $dns_cache{$hostname}{mx} )
	{
		@iplist = @{$dns_cache{$hostname}{mx}};	# use the cache for MX records
	} else 
	{
		my @mxr = mx($dns, $hostname);		# no cache existed, call out to Net::DNS
		# mx records
		if ($#mxr >= 0) 
		{ 
			foreach my $mxrr (@mxr) 
			{
				# print "MX for $hostname: ". $mxrr->exchange . "\n";
				my $ipquery = $dns->search($mxrr->exchange);
				if ($ipquery) 
				{
					foreach my $iprr ($ipquery->answer) 
					{
						next unless ($iprr->type eq "A");
						# print " IP=" . $iprr->address . "\n";
						push (@iplist, $iprr->address);
		
					}
				}
			}
		}
		if ( $#iplist < 0 ) { push (@iplist, '0.0.0.0'); }  # cache ip of all zero's so we dont keep calling net::dns if nothing is returned
		$dns_cache{$hostname}{mx} = [ @iplist ]; # cache the array IPs of the MX records into an hash location.
	}
	$subnet =~ s/^mx//i;
	return check_sender_ip_vs_list($ip, $subnet, \@iplist);
}

###########################################################################
# check_sender_a(sender, ip, subnet) # subnet='' or '/24'
# return true if ip is in A record for sender domain (or /24 if specified)
# enable via --check_sender=a or --check_sender=a,24
sub check_sender_a($$$)
{
	my ($sender, $ip, $subnet) = @_;
	my ($user, $hostname) 		 = split(/\@/,$sender);
	my @iplist;

	if ( $dns_cache{$hostname}{a} )
	{
		@iplist = @{$dns_cache{$hostname}{a}};	# use the cache'd A records
	} else 
	{
		my $ipquery = $dns->search($hostname);	# no cache existed, call out to Net::DNS
		if ($ipquery) 
		{
			foreach my $iprr ($ipquery->answer) 
			{
				next unless ($iprr->type eq "A");
				# print " IP=" . $iprr->address . "\n";
				push (@iplist, $iprr->address);

			}
		}
		if ( $#iplist < 0 ) { push (@iplist, '0.0.0.0'); }  # cache ip of all zero's so we dont keep calling net::dns if nothing is returned
		$dns_cache{$hostname}{a} = [ @iplist ]; # cache the array IPs of the A records into an hash location.
	}
	$subnet =~ s/^a//i;
	return check_sender_ip_vs_list($ip, $subnet, \@iplist);
}
###################################################
# used by check_sender_mx and check_sender_a
# return true if IP is in list
# if /24 then return true if first 3 octets match
sub check_sender_ip_vs_list($$$)
{
	my ($client_ip, $match, $iplist) = @_;
	foreach my $ipaddr ( @{$iplist} )
	{
		return 1 if ($client_ip eq $ipaddr);
		return 0 if (! $match eq '/24');
		
		$client_ip =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.)/;
		my $client_classaddr = $1;
		$ipaddr =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.)/;
		my $ipaddr_classaddr = $1;

		return 1 if ( $client_classaddr eq $ipaddr_classaddr );
	}	
	return 0
}


#########################################
# drop_priv(username)
# code from Net::Server
sub drop_priv
{
	my ($user) = @_;
	### drop privileges
	eval{
		if( $user ne $> ){
			# print "Setting uid to \"$user\"\n";
		set_uid( $user );
		}
	};
	if( $@ ){
		if( $> == 0 ){
			die $@;
		} elsif( $< == 0){
			# print "NOTICE: Effective UID changed, but Real UID is 0: $@\n";
		}else{
			print $@."\n";
		}
	}
}

###########################################3
# setup_dbm(dbdir)
# connect to BerkeleyDB *READ_ONLY*, return reference to db hash
sub setup_dbm
{
	my ($dbdir) = @_;
	my %db;	
	

	    tie(%db, 'BerkeleyDB::Btree',
	        -Filename => "$dbdir/postgrey.db",
	        -Flags    => DB_RDONLY,
	    ) or die "ERROR: can't find database $dbdir/postgrey.db: $!\n";
	    
	return \%db;
}
	

# from postgrey 1.14 http://isg.ee.ethz.ch/tools/postgrey/    
sub do_sender_substitutions($)
{
    my ($addr) = @_;

    my ($user, $domain) = split(/@/, $addr, 2);
    defined $domain or return $addr;
    # strip extension, used sometimes for mailing-list VERP
    $user =~ s/\+.*//;
    # replace numbers in VERP addresses with '#' so that
    # we don't create a new key for each mail
    $user =~ s/\b\d+\b/#/g;
    return "$user\@$domain";
}

# from postgrey 1.14 http://isg.ee.ethz.ch/tools/postgrey/    
sub do_client_substitutions($$)
{
    	my ($ip, $revdns) = @_;

	# --lookup-by-subnet:

    return ($ip, undef) if $revdns eq 'unknown';
    my @ip=split(/\./, $ip);
    return ($ip, undef) unless defined $ip[3];
    # skip if it contains the last two IP numbers in the hostname
    # (we assume it is a pool of dialup addresses of a provider)
    return ($ip, undef) if $revdns =~ /$ip[2]/ and $revdns =~ /$ip[3]/;
    return (join('.', @ip[0..2], '0'), $ip[3]);

}


## used code from postgrey for read_client_whitelists() to import client files
sub read_client_files()
{
	my @skip_client_ips;
	my @skip_client_ptr;
	my @match_client_ips;
	my @match_client_ptr;
	
	for my $f (@{$opt{'skip_clients'}}) {
          if(open(CLIENTS, $f)) {
            while(<CLIENTS>) {
                s/^\s+//; s/\s+$//; next if $_ eq '' or /^#/;
                if(/^\/(\S+)\/$/) {
                    # regular expression
                    push @skip_client_ptr, qr{$1}i;
                }
                elsif(/^\d{1,3}(?:\.\d{1,3}){0,3}$/) {
                    # IP address or part of it
                    push @skip_client_ips, qr{^$_};
                }
                # note: we had ^[^\s\/]+$ but it triggers a bug in perl 5.8.0
                elsif(/^\S+$/) {
                    push @skip_client_ptr, qr{\Q$_\E$}i;
                }
                else {
                    warn "WARNING: $f line $.: doesn't look like a hostname\n";
                }
            }
          }  
	}
	$opt{SKIP_CLIENT_PTR} = \@skip_client_ptr;
	$opt{SKIP_CLIENT_IPS} = \@skip_client_ips;

	for my $f (@{$opt{'match_clients'}}) {
          if(open(CLIENTS, $f)) {
            while(<CLIENTS>) {
                s/^\s+//; s/\s+$//; next if $_ eq '' or /^#/;
                if(/^\/(\S+)\/$/) {
                    # regular expression
                    push @match_client_ptr, qr{$1}i;
                }
                elsif(/^\d{1,3}(?:\.\d{1,3}){0,3}$/) {
                    # IP address or part of it
                    push @match_client_ips, qr{^$_};
                }
                # note: we had ^[^\s\/]+$ but it triggers a bug in perl 5.8.0
                elsif(/^\S+$/) {
                    push @match_client_ptr, qr{\Q$_\E$}i;
                }
                else {
                    warn "WARNING: $f line $.: doesn't look like a hostname\n";
                }
            }
          }  
	}
	$opt{MATCH_CLIENT_PTR} = \@match_client_ptr;
	$opt{MATCH_CLIENT_IPS} = \@match_client_ips;
	
	
}


sub setup_debug()
{
	if ($opt{debug_db} or $opt{search_db})
	{
		die "\nDebugging_DB Activated, but no matching RE's defined. use --debug_re also! \n  " if (! $opt{debug_re} );
		print "\nDebugging_DB Active, Displaying hosting matching REs: ";
		foreach my $RE ( split(/,/,$opt{debug_re}) )
		{
        		print "$RE ; ";
        		push ( @{ $opt{debug_RE} }, qr/$RE/i );
		}
		print "\n\n";
	}	
	
}

sub is_debug_host($)
{
	my ($host) = @_;
	foreach my $RE ( @{$opt{debug_RE}} )
	{
		return 1 if ($host =~ /$RE/);
	}	
	return 0;
}


main();
exit 0;


__END__



=head1 NAME

postgreyreport - Fatal report for Postfix Greylisting Policy Server

=head1 SYNOPSIS

B<postgreyreport> [I<options>...]

 -h, --help                   display this help and exit
     --version		      display version and exit

     --user=USER              run as USER (default: postgrey)
     --dbdir=PATH             find db files in PATH (default: /var/lib/postgrey)
     --delay=N                report triplets that did not try again after N seconds (default: 300)
     --greylist-text=TXT      text to match on for greylist maillog lines

     --skip_pool	      Skip report for 'subscriber pools' ( last 2 octets of IP found in PTR name )
     --skip_dnsbl=RBL	      RBL server to query and skip reporting for any listed hosts (SLOW!!)
     --skip_clients=FILE      PTR or IP or REGEXP of clients to skip in report        
     --match_clients=FILE     *ONLY* report if fatal *AND* PTR/IP of client matches
     
     --show_tries	      display the number of attempts failed triplets made in first column
     --show_time	      show entry time in maillog (single line only)
     --tab		      use tabs as separators for easy cut(1)ting

     --nosingle_line	      display sender/recipients grouped by ptr - ip
     --separate_by_subnet=TXT display TXT for every new /24 (ex: "=================\n" )
     --separate_by_ip=TXT     display TXT for every new IP  (ex: "\n")
     --check_sender=LIST      one or more of: mx,mx/24,a,a/24
                              does DNS/A lookups for sender @domain and compares sending IP
                              if match displays "MX" "A" or "MX/24" or "A/24" depending on LIST
  
   Note that --(skip|match)_clients can be specified multiple times and there are no default files.
   Same rules apply as postgrey's --whitelist-clients, see postgrey doc for more info.

   --skip_dnsbl can also be specified multiple times to query multiple DNSBL servers.

=head1 DESCRIPTION

postgreyreport opens postgrey.db as read-only; reads a maillog via STDIN, 
extracts the triplets for any Greylisted lines and looks them up in postgrey.db. 
if the difference in first and last time seen is less than --delay=N then the 
triplet is considered fatal and displayed to STDOUT

The report sorts by client IP address 


=head2 Note:

unless you are using --lookup_by_subnet or excluding all known MTA pools you will likely have 
false fatal reports for "BigISPs". A message that was tried from every IP in SMTP pool before making it
through will show up in the report for all of the attempted source IPs


=head2 USAGE

It is best to run postgreyreport against a maillog that is at least several hours old (yesterdays?) 
( you be the judge on how old is acceptable ). if you run the report against a live maillog you are
not giving legit MTA's enough time to try again and you will have lots of inaccurate information.

=over

=item * Ex usage:

	zcat /var/log/maillog.0.gz | ./postgreyreport [options] > postgreyreport.log

	or
	
	zcat /var/log/maillog.0.gz | \
	./postgreyreport --nosingle_line --check_sender=mx,a \
	--separate_by_subnet=":==================\n"
	# 94 "=" total, some were omitted for clarity

=item * Ex Output: ( POD wrapping will mess this up, view source )

 :============================================================================================
 unknown                 4.29.43.31
                    marissa_mcclendonuu@abit.com.tw                      user1@recipient1.com 
                            jake_meyerdt@ali.com.tw                      user2@recipient1.com 
                        jenny_banks_sh@translate.ru                      user1@recipient2.com 
                              rvazquezpo@ali.com.tw                      user3@recipient1.com 
                                 aep@notimexico.com                      user2@recipient1.com  
                    brittneystanley_ei@cetra.org.tw                      user2@recipient1.com  
                            brendasheehan_cw@lib.ru                      user2@recipient1.com  
 :============================================================================================
 lsanca1-ar5-127-189.biz.dsl.gtei.net      4.33.127.189
    A      fokkensr@lsanca1-ar5-127-189.biz.dsl.gtei.net                 user2@recipient1.com 
                       
                       cyxlfrfwciercu@publicist.com                      user3@recipient4.com  
 :============================================================================================
 smtpout.mac.com       17.250.248.83
                             do_not_reply@apple.com                      user4@recipient5.com 

 smtpout.mac.com       17.250.248.88
   MX                             legituser@mac.com                      user6@recipient7.com 
 :============================================================================================

=back

=head1 HISTORY


B<1.14.3  20100321>

=over 4

  Some additions, Leonard den Ottolander <leonard.den.ottolander.nl>
  New option: --tab   Use tabs as separator in single line mode
  New option: --show_time   Show entry time in maillog in single line mode

=back

B<1.14.2  20040715>

=over 4

  BUGFIX: (automatic) lookup-by-subnet support was broken, fixed.
  BUGFIX: corrected a few spelling errors
  new Option: --skip_pool   Skip report for 'subscriber pools' 

=back

B<1.14.1  20040712>

=over 4

  Changed --return-string to --greylist-text to match postgrey
  new Option: --skip_clients=FILE
  new Option: --match_clients=FILE
  new Option: --skip_dnsbl=RBL.DNS.NAME
  All 3 of the new options can be specified multiple times.
  Updated do_*_subsititions again to match postgrey

=back

B<1.11.1 20040701>

=over 4

  missing keys from DB are considered fatal triplets and included in report
  Changed --delay testing from "greater than" to "greater than or equal to"
  Fixed --help and --man switches
  Removed setuid Notice

=back

B<1.6.4  20040618>

=over 4

  Initial Public Version (postgrey/contrib)

=back

=head1 AUTHOR

S<Tom Baker E<lt>tbaker@bakerfl.orgE<gt>>

=cut

Anon7 - 2022
SCDN GOK