From 832f6d34b4beb7548ef56a97b02f5f427da75f0c Mon Sep 17 00:00:00 2001 From: "John E. Rollinson" Date: Sun, 29 Jan 2017 09:39:12 +0900 Subject: [PATCH 01/44] Add ciphertext to ticket data structures --- scripts/base/init-bare.bro | 2 ++ src/analyzer/protocol/krb/krb-types.pac | 1 + 2 files changed, 3 insertions(+) diff --git a/scripts/base/init-bare.bro b/scripts/base/init-bare.bro index ffee527bb7..b5bf5d298d 100644 --- a/scripts/base/init-bare.bro +++ b/scripts/base/init-bare.bro @@ -3955,6 +3955,8 @@ export { service_name : string; ## Cipher the ticket was encrypted with cipher : count; + ## Cipher text of the ticket + ciphertext : string &optional; }; type KRB::Ticket_Vector: vector of KRB::Ticket; diff --git a/src/analyzer/protocol/krb/krb-types.pac b/src/analyzer/protocol/krb/krb-types.pac index a5b2eb1041..bb2bfba3e8 100644 --- a/src/analyzer/protocol/krb/krb-types.pac +++ b/src/analyzer/protocol/krb/krb-types.pac @@ -95,6 +95,7 @@ RecordVal* proc_ticket(const KRB_Ticket* ticket) rv->Assign(1, bytestring_to_val(ticket->realm()->data()->content())); rv->Assign(2, GetStringFromPrincipalName(ticket->sname())); rv->Assign(3, asn1_integer_to_val(ticket->enc_part()->data()->etype()->data(), TYPE_COUNT)); + rv->Assign(4, bytestring_to_val(ticket->enc_part()->data()->ciphertext())); return rv; } From 68e3f0d96ac404a2abdcc4f975b4d9ce088b0eca Mon Sep 17 00:00:00 2001 From: "John E. Rollinson" Date: Sun, 29 Jan 2017 09:39:40 +0900 Subject: [PATCH 02/44] Ensure TGS req does not stomp out AP data --- scripts/base/protocols/krb/main.bro | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/scripts/base/protocols/krb/main.bro b/scripts/base/protocols/krb/main.bro index 13200a559e..fc6abc5bff 100644 --- a/scripts/base/protocols/krb/main.bro +++ b/scripts/base/protocols/krb/main.bro @@ -164,9 +164,16 @@ event krb_tgs_request(c: connection, msg: KDC_Request) &priority=5 return; local info: Info; - info$ts = network_time(); - info$uid = c$uid; - info$id = c$id; + + if ( !c?$krb ) + { + info$ts = network_time(); + info$uid = c$uid; + info$id = c$id; + } + else + info = c$krb; + info$request_type = "TGS"; info$service = msg$service_name; if ( msg?$from ) info$from = msg$from; From 7caf5071631ce69620c462826d99e367f0140354 Mon Sep 17 00:00:00 2001 From: "John E. Rollinson" Date: Sun, 29 Jan 2017 09:40:11 +0900 Subject: [PATCH 03/44] Add script to log ticket hashes in krb log --- .../policy/protocols/krb/ticket-logging.bro | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 scripts/policy/protocols/krb/ticket-logging.bro diff --git a/scripts/policy/protocols/krb/ticket-logging.bro b/scripts/policy/protocols/krb/ticket-logging.bro new file mode 100644 index 0000000000..e254b6dc26 --- /dev/null +++ b/scripts/policy/protocols/krb/ticket-logging.bro @@ -0,0 +1,43 @@ +module KRB; + +redef record Info += { + ## Hash of ticket used to authorize request/transaction + auth_ticket: string &log &optional; + ## Hash of ticket returned by the KDC + new_ticket: string &log &optional; +}; + +event krb_ap_request(c: connection, ticket: KRB::Ticket, opts: KRB::AP_Options) + { + if ( c?$krb && c$krb$logged ) + return; + + local info: Info; + + if ( !c?$krb ) + { + info$ts = network_time(); + info$uid = c$uid; + info$id = c$id; + } + else + info = c$krb; + + info$request_type = "AP"; # Will be overwritten when request is a TGS + if ( ticket?$ciphertext ) + info$auth_ticket = md5_hash(ticket$ciphertext); + + c$krb = info; + } + +event krb_as_response(c: connection, msg: KDC_Response) + { + if ( msg$ticket?$ciphertext ) + c$krb$new_ticket = md5_hash(msg$ticket$ciphertext); + } + +event krb_tgs_response(c: connection, msg: KDC_Response) + { + if ( msg$ticket?$ciphertext ) + c$krb$new_ticket = md5_hash(msg$ticket$ciphertext); + } \ No newline at end of file From 59f0477d29b168673b0e706fd3f458bef0116f04 Mon Sep 17 00:00:00 2001 From: Seth Hall Date: Fri, 3 Feb 2017 12:29:22 -0800 Subject: [PATCH 04/44] Implement ERSPAN support. This is a small caveat to this implementation. The ethernet header that is carried over the tunnel is ignored. If a user tries to do MAC address logging, it will only show the MAC addresses for the outer tunnel and the inner MAC addresses will be stripped and not available anywhere. --- src/Sessions.cc | 27 ++++++++++++------ testing/btest/Baseline/core.erspan/tunnel.log | 10 +++++++ testing/btest/Traces/erspan.trace | Bin 0 -> 117979 bytes testing/btest/core/erspan.bro | 4 +++ 4 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 testing/btest/Baseline/core.erspan/tunnel.log create mode 100644 testing/btest/Traces/erspan.trace create mode 100644 testing/btest/core/erspan.bro diff --git a/src/Sessions.cc b/src/Sessions.cc index 9361b7cde2..ad82f1c736 100644 --- a/src/Sessions.cc +++ b/src/Sessions.cc @@ -431,7 +431,6 @@ void NetSessions::DoNextPacket(double t, const Packet* pkt, const IP_Hdr* ip_hdr return; } #endif - int proto = ip_hdr->NextProto(); if ( CheckHeaderTrunc(proto, len, caplen, pkt, encapsulation) ) @@ -509,6 +508,8 @@ void NetSessions::DoNextPacket(double t, const Packet* pkt, const IP_Hdr* ip_hdr uint16 flags_ver = ntohs(*((uint16*)(data + 0))); uint16 proto_typ = ntohs(*((uint16*)(data + 2))); int gre_version = flags_ver & 0x0007; + // If a carried packet has ethernet, this will help skip it. + unsigned int eth_len = 0; if ( gre_version != 0 && gre_version != 1 ) { @@ -519,7 +520,18 @@ void NetSessions::DoNextPacket(double t, const Packet* pkt, const IP_Hdr* ip_hdr if ( gre_version == 0 ) { - if ( proto_typ != 0x0800 && proto_typ != 0x86dd ) + if ( proto_typ == 0x6558 ) + { + // transparent ethernet bridging + eth_len = 14; + proto_typ = ntohs(*((uint16*)(data + gre_header_len(flags_ver) + 12))); + } + + if ( proto_typ == 0x0800 ) + proto = IPPROTO_IPV4; + else if ( proto_typ == 0x86dd ) + proto = IPPROTO_IPV6; + else { // Not IPv4/IPv6 payload. Weird(fmt("unknown_gre_protocol_%" PRIu16, proto_typ), ip_hdr, @@ -527,7 +539,6 @@ void NetSessions::DoNextPacket(double t, const Packet* pkt, const IP_Hdr* ip_hdr return; } - proto = (proto_typ == 0x0800) ? IPPROTO_IPV4 : IPPROTO_IPV6; } else // gre_version == 1 @@ -559,7 +570,7 @@ void NetSessions::DoNextPacket(double t, const Packet* pkt, const IP_Hdr* ip_hdr unsigned int gre_len = gre_header_len(flags_ver); unsigned int ppp_len = gre_version == 1 ? 1 : 0; - if ( len < gre_len + ppp_len || caplen < gre_len + ppp_len ) + if ( len < gre_len + ppp_len + eth_len || caplen < gre_len + ppp_len + eth_len ) { Weird("truncated_GRE", ip_hdr, encapsulation); return; @@ -578,9 +589,9 @@ void NetSessions::DoNextPacket(double t, const Packet* pkt, const IP_Hdr* ip_hdr proto = (ppp_proto == 0x0021) ? IPPROTO_IPV4 : IPPROTO_IPV6; } - data += gre_len + ppp_len; - len -= gre_len + ppp_len; - caplen -= gre_len + ppp_len; + data += gre_len + ppp_len + eth_len; + len -= gre_len + ppp_len + eth_len; + caplen -= gre_len + ppp_len + eth_len; // Treat GRE tunnel like IP tunnels, fallthrough to logic below now // that GRE header is stripped and only payload packet remains. @@ -607,7 +618,6 @@ void NetSessions::DoNextPacket(double t, const Packet* pkt, const IP_Hdr* ip_hdr // Check for a valid inner packet first. IP_Hdr* inner = 0; int result = ParseIPPacket(caplen, data, proto, inner); - if ( result < 0 ) Weird("truncated_inner_IP", ip_hdr, encapsulation); @@ -794,6 +804,7 @@ void NetSessions::DoNextInnerPacket(double t, const Packet* pkt, // Construct fake packet for DoNextPacket Packet p; p.Init(DLT_RAW, &ts, caplen, len, data, false, ""); + DoNextPacket(t, &p, inner, outer); delete inner; diff --git a/testing/btest/Baseline/core.erspan/tunnel.log b/testing/btest/Baseline/core.erspan/tunnel.log new file mode 100644 index 0000000000..76d2784a7a --- /dev/null +++ b/testing/btest/Baseline/core.erspan/tunnel.log @@ -0,0 +1,10 @@ +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path tunnel +#open 2017-02-03-20-27-11 +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p tunnel_type action +#types time string addr port addr port enum enum +1442309933.472798 CHhAvVGS1DHFjwGM9 10.200.0.3 0 10.200.0.224 0 Tunnel::GRE Tunnel::DISCOVER +#close 2017-02-03-20-27-11 diff --git a/testing/btest/Traces/erspan.trace b/testing/btest/Traces/erspan.trace new file mode 100644 index 0000000000000000000000000000000000000000..64382645ec07aabfa8a1dd94f2dcd1c2d3a399cf GIT binary patch literal 117979 zcmeF430zF=-|(kGM0ToaWq?9CmuR9;7<6o=$UBq}IKE1t z@H?OvqRg!hO)Y^wOKpW;sU>s|g*qT0mHy&y0}*ftPDO1tM+pc`w|8)K!aKXTy1DQ3 z@bn^h@AvWb^A8C8^=*h@mMUKcEtnw#$sqWHL2B!FlJ5-C`yd9vVJJStfkJJa5Q8jW zSntJ`0RuCnBN@iT|HK~=5&8=hb;~WjWxffHdLJ1TejE5)zMtEV+)ste{0VfX zP+XAvW}wVP$18J)A&ftRG0boe$uR27p(|BO+7m8w1{5FS0J%S5h9fr9QE$PiLC2^v ze_pZ$X4vsNgQ6lR;UBh-*6xLV*JX|X_W<+-3Z=yG*1kJg?-LJM;^6gys|5c-ximWn zoIdJ_nhO4&1>R*QI0Tl@5jpuIkAzS&W`ZxjzZ3W@iJF6&yv;@ zZ18I+0aesgQ%l2b63Vi2s43tL7oo(!izVfNTH|WI#cK+m)@|fz!2w4X?*q7aA0vv_ z6cq2w@roB>ILV*E3}*Nn$uLIodcnmzh$!9(F~|dkyj=qPE@^X^;W1x^pBEQ=Nlz_- zD863+-&5^ZLH+mwipt<0)dG&1hKxGO{?Dk$SbQoHD5}7msr;HVOE~IvzER=JN$4S5 z=4psBHvwfnYrHat81(rwY=arDAsNOfa{^rE1Bf!85Q73>*k{Do18BrSHJ*fI7-j#v zhexo7M+gsFfQQ-Rd4L#-`7_wS42ejFF+6y~9_R=U6Jk&V4ChIFJ?w@V;*ktvcu0di zJVbcd3_Q#k&jZBJ%%5Qo%y0?GFouWyu!nwxhY2w#0fy!2d_53hh9D%v7#<$O9v&b( z7y}P;$MXO&*z;!~!3=>&hA}+&z#jS#9wx+~3>bXd`FezK)1gK;bB4ys(|5w6<-gaM+6wqNQN;yq{ANWAv_oY z4-3Zg05N>y&+rarkVP_#;lUsFKtp(#FoOpFF5m}`#L2Nt^5X0)Xd>P7NhE+(0F+2pq9=Z@7Cd8lt z7))4v87g50aU{bS9x`ALcMu-*fQLond4L$6^Jn-BGps-|jNu^&_Rxv&Fd+s_z(7(G z;aB~Cff<${8OHFC346GW@SqDkEFRAT#L&y1p%!LXjAR(YLon>21L0vp3|fF8!Hutn zZ!p6GB*PdUvS1H?BRpV%hb7~AfEcL!8R}t%`ACK_JcPg=+7TWm#GnlrF5Tekp$TS~ zgJc-PLpJOo8R20g@UV0|4-i8Se}-n5VK$Os3=g5Ohc<+V2{GsZ2IUIA9@=4s8AygP zJmkP0ZXrBifQMz{d4L#N_%n3C4AYSeV|WOIJ+vY`Oo#yk7!syW=T|>4Achzol3@%F z6xhQ}ga;kqVflC-Acna!_%aN^3|u6`7#_l54=o4}6Jpp17|!YPWnjY$OeDh?9$vs6 zZXi5p0}m_4^8hgr_4qPyV1{8N!x$dGO&;hbHzPbuhye>297Fgql!D0wkPib$hA}+k z!XB<8JZJ$A;^TRM7}EJOfRhFcbR@$V9>9$z@X&)cG?^ff;&`3}bjW0DEXec$g4_9$?66;_E>aW@tk)jN#!G?BOcH zg9h-hay$Pn$uNe82-w3ngog<+7y^d;<9t1=h8aqc3}bkB1A9n9cu)l% zR*&ZaVrV(RmthUeP=aI_!^2_N!&iid2{9M}25IJ0K2r{*vZM216vNNK{|QbxKvB>0 zkBT0h0i*2yj5@(72Po>ZUHoRxO6B0FzQ6naH$x}2fG6urMGIk;*C{mDh}y$`C^?e+C7Z;Wd(BR2qGs96bW3G8K`^6Jpo| z7;J|5dVpt?VkR=91jg}zhsy{L%D}_g@jO5bk^C9p8Kszs%qSg&J=7sQOo+i4Fj$~P z`89Rca9NO%WiduRAas zU!4@E|ju2Z&)-5MKs();ngztoIlmPQe~35gsPQU=A48-Q&vu zZZ?325#7TvJb;NJ;Ncv?gADK>JDvxKfzF=+bo2p(53(%A@NgRT@Co5zLJSsw!Q&fW z51>T`3Ps53vXj(!c|HJP#1VW&RAHH3SS^NQN;yM8h5`5FRGPuoW=GFPg=# zo`h%rV@AyWkKv&d_7H>cAO$?gjpqSkXyDHPvK@GEn}CNiu!nMlhY2xk0}MxJd|z}7 zChtctI&?0Lg&7=?3}bizlb7JSIE(PG0eFxf&jZA85DZj7@yB2|2QxSz8OF$mv#^IU zgog<+YzGV|L%t06U{9iJxvLBq*wz?^N zM#erbfKKZq4XGANa~}NhW6k-`F)HNxdgw(cNHhG!h)+<|0sc`J!cn`CQAZW%&!|6E ze8aK$tgnZniczPaJOB%Hj9OQ2`&kT*+JcMGM$Rm;YSK<}XB! zS^L}s@lPL^J0kYfpRwgYY-hj*9iz^B$LGax><(mX=!HMlzQa!8eFf`;KEvnzYlz^- zd7lRHZ<){DxPCgQGQWR7XZ&!IFvi5-4iar17E(&KIhJqPG27$~GeE zqK)fC!ELpX!8QrB#HMZA%+d0)@}eSGM@PK3pN53Jx3`CzqrIOS!3!NE0I_a9yjy>F0%y|=W)2K0uXv1Lr`y;@_&d&8MM+a)n~$3lcnjz^H6<)f^z;-oC1lOO>Fb$nl~9nC zlU0^eRZvBIPf=D`RuOzpeZ=?Vetyps@8o7Lp$Fa)?;~NZ2V%*s|8))qUXBDOH!l|r z36~HzaDHItl!v_^UQ}cg!55r`yt;y{yo#Evin^?VsE8iH%M0%asn(F#g2#KyU_IOd zz-zJoey#)`w-C4#G{E_om`ZH(*lN1X%52+qELIl_K6I6gjR`K$SED|Runq=J(0BC% zwY5b>L`9(7QkN1&?E`rQ9iwt<&u4g^>qH|mx1bk(udlx6HI&hUiqaRL+SRtRas^5 zBlE5J{r-4gKO=uPCk+WFc}FJ|Wd~&$WxS%2jFO{@BDi71tIDXUt2wLT<($=2)W9!U zTU*OOxmHIZUA^5`u{Jx?Tw2O{_okbB91(cdU^C)&UEm07%C`-9W4! z;K1qzV2w<%HWuLhz^nDJdOJK-?cLS=(H0;a^Z`nChm1KP5buL`lF<#;kic4+LuoB4 z^2@EItGy52oZ#l=w+(Oa38{B;#QTCQ1eXH%PgPDH7~COaYAIvv6+qaB$9j8%A8JZK z<>2On--@?)!g+ZFi;C#OX1Dp{r6uH*B`opY5(?mV!6ywxbqz%|2_sVw7G$#@CQ!&(zCR5=QNwgo2OXQ*V;lXLx$^SRXR=pv?QdGM%(qs2WPWmS5xV->bE{ zkYm<<56u^0Zp{K6p8u`Z{>N5p`cPIIVX-z?V;ks88|bR>g0EQ%aBb+q-!yW;+G7pD zg#}%8;DT}bRR!uPKo^Ft%@$BC+UWXjm4jYmZmDY{@8Ge+Vw0<)s<)Y|iZa?^v*Hfu z0)#G53#@~Mt~VB3`1Tgi1#E$JbOGN7e}W4$7`ouWg>Hy71Sl3*Kd2(GfI~xXJ?6&_ zD?>F$tg@3O&euc5KuzCFO2x_)Yhm!?7Z4Y+jk{BbfsM0?|BfF&G=|Redxc|x-SS(- z0qU5sDhN|h_vmiN8UW58zYi*+KUPSfy0RFf0s_^S3)Vx~Ux9#j2#~`XfT93GVZSa| zP)#Dv{5vl6dApyfwYgNVldtKPU@2o)CxVCic4d15Pc_{ge%=nITm0Qkwk!KNZ+1~P z-U9Le`C}i}*hSYEI$z-10;{KnHMPJhWA%+eg^CTr>f2y(P=yKoX#xFdJnC(J)-~~} zBK$gTl5oeZ7FpN)shLBqla7PxnwS5~C%~OXzlR;{`T+CUDE2=cHy_Zr&eoZZx(wb3 zI)1O=1W$k721Pv)GG0_cC+UlD;|eVr_`U@c8FdkgE&3SIxGKsifSTF~RMd`s|J1(Q zg1tc_=!XyTL%aHUdT2^Gf=0j>@29(BBP|J<|Lz}qO2;T?kuM)W3S>UrzEH1q$+Q( z4z)P{G)$ZD_8xw&mW~7;Kn+?9bG(nQn=jmsK+QTh6CZ!jBl>UB(y9NsrIS|yojFkv zEmzQB>S+17`FY@V^a+mso}f7d9oGQ7lNS2>ceT*)Yecjh2u{H|TCVasI0tu7em`FY zzOl{K%~!(d=Wl!32TM5MC7cQVUQQBP_7a=68W?J?nqbkb(s2d711+?@jusjY2<7TC zuo!U;$W!PTm8(LZ;aT7)L1eB%jq&fTu|Snj4V0^G&|*Y*%k=lwxIW;RbwC$rjm1RQ z|7eZ>kW>GA-|^q_zC%<5N{}Fj8K|S6#G&K&1VIIThNs6*9z!Mwlu7@LZ%i$L?|sdr zFQHHe<_JKWuA$0|hdK`a)0mNTy-8p;7@UDWBn&qhVhwZ+3_u6>5C37YD3k)xV^Q093C|P3A|H15=IKm-~kj{2Qi%_Bj1Zs>6Uvww3QSI0#W4A2n0+2P$9{ z^q-&|MhihS1{FyQB)oOX$eA9A6|)~z{V%|7}YP||D_S8 z5=K&i7mvDeLm3HmLsU|`02O@WR-}YdMk!9eh7u3}Ph*0A2PL2&sG=?eW-5fh@PM$J zmyErWlg}i3A8*jlo`DiL4RC~M{2IV7a03`Qkp}QJEa~{amlO@CD*1LoYo|k!BCC*+ z#z^x#nCjU79V*RqehpylI+!YJ0#t8cs>o4Po&RY?n)#)9ExcYM^40{X-ojKz z|2@@NK(%Edze>FpZk8h_(k#D&sgC@6s#rj^e60{F6kHq7G3w&;to;m^pcv8>d?Ecl z5w0ghgQ%oz0oC`5PZ?Z%=;=u)!GA73p}@evNkRJ@orJ(VC*pBw;0bFG%6>^9)HCqz z&@l?-PA%LYJ~}GJN8Je@kj{rt1dV(=LP64@ba z&VcK+`K46lhf<;Om4eMT5K0a};iI$$LOD9>*(rZe`W{wlh)}8mD-{}F zDM0zxO3$wsLiqqk&@n1CoNB+oN|%kBr5ZxLuu}XlrK&*b+Ue&}lg3aAH5>xxfk_ud zegt)5u*q)|An1b$oPH6g{UDe;CIJw55fE%L=J%|;I=BOUG!#hywE!c^^5;UC32t12 zqQ>%%dICQ05w#W+bVM@$j5@)MYfx0ty^~Nx@HWsfs=U4H&cRW4{C=+R3M--Sv41@F zamBcgeH40kV15T`#hj#@N+1>}1n6T@ptrpfT=o#nFrWd|GklDq*w~V2y=qvbHc?=Y}`P0~ug_p$&hTmpH~ zXf%mW$F(jUrc(NyYQz-@E#xwiF4_pFUWWXjf*LHP?;Y2Jhkj<{I2~6ACuSAD4o2N` z7)R=NoIgl{wl&NVApUzv@qo(Sg5Oe(rOpn^6T%n=CxzeDx1mS4xU4j$7z zGLbP|XvzN^fv{0j-T!GqB>-1yBK+k0_~jK9Zu*WC zPJjwp4nIfWXPWf4G7!KGpACf`2Z) z5xqsYS-1iSIQ$9XU;_K!y^Om2HKZ$Gf}fQVM4$!}OdgW}bP19HLHvOksCJMQ(D8f2 zH2KT7#S$Q2Y+c49n0`k36Cjw9uJp^n?{i0>E&R3iO+ijh0=ETB%-cJ<;$@)4Q9c9@ z4GC`_w*au3N?O9xK1c@4v}=P&@1HBD#+@p>I zNlPfoOJMz7My$~KF>h@GdgL;uV11&q8y>8%8*4q+k5^mS`#`J8;B}!gKSTZ}s{!Ox z)YR0JH8=qwU&jN4#XakB7GXrmb?0`h)DsmcNc#%_kI4RF517ndYc<~LsNHbkeQ`iAkI=Q$jr(j*i@e&2aXWv<(d-s@5QCP z&D9@TPbY(O^pp9yo=yoYsZ)Sf8E8s?6|Sz@XuAMEH#-M6FZ8by0IM=Q!K#e^21_D- z!Tyt)KNtTgswv2U!W9(}kD3ZDI?z=_b!7|hU1oe?&04hQ`%u#6fz6d){jN?uuy@|- z&u2Ey?#w+_*c{Wj+|B5(^(h+;zQ zcnlV8uFg+ddEm}FaSSm`9J7kp(AZ03(hFi$Da8e4es!|%=}p$SBy!>naZhSCt2R_J zkfsyfuf)lwaGIl9+PZ@z!)rg~J#{R4me|!UwI0Z;RZZ>A z5xTf1E1BY@$}SE1RHUPEjoV|-^sd2OoE0qot}S!BMT`D&7I&tqN7T~~$IUT@yigmlsJl7DJ&%Jk z5{a2R8!7vBS7%9iu87O)^W53e)C6-;* z^19{!(48}rq>=fd(b9V;dXKX|lSSq=a;;t3T-UZR+06ip*uCdLq@zn<2~@f{kN9aKolexKmJ2iL!U&}%UCgU-GcUy6zKVil?6 z@Q}Xt;UWzd$HgwJI*BS}oYI-Y$xga#O(2m|W$lu5WW>33RX7r}$6uSu;{-a35zuzT zjCX11=>Gcp`dTr&ZFV}L-n~t@iYDAAYc#Q*kx$d+g(vO#@W_KXFDjDKT0?OisL1!? zhNhCp$;Cnbn7mlaHsLn@+ z^n`Mp(pb~e^4cG8I=^VmauH?}r{O5_hdwX9bjCewpL2tx4zG`)&NZ{S7e%wx3f*2r z;lwtHQR3_e33obLcZabuYlJbiNn9O_c5n#u8hbD>DJ)ro7fy8Nvceb)z{=LKE++EC zk}(5p28q*|UoXkXVQn|u!P!g5Nhi??2&4*POM713EMh8In%Kh10gL@!HTTz{ElQsK+dzE z{#q(2he!9|UC|)*MFghOD*I_*(maIQS1?d!=QsRyNE0)h*xA;UM(wET9n88z=Ganr z+{U)tqEM6B`Na$hF8<}Q%k49wH#vROnY%pZyr(TX?ZW)$7K`INds(;>O|{MyA5J*$ zJeB4=Ff(KEwSth{C-aTBU#PlqC*NUvL1l3GrOG6gzzb`_V_l-oeb^#m;DwszrL(o} z!_~>3VvSpozY3J=?t%h4OQ|-vIE_TZ0cwA=B-pq(t;-iW?(zj0b z)MmKp*lRpcUU_)h^ZB39*R%_+xfS<^y}2=@om6H=ZVH_hxS}rM3lChUX5w|^(r6B= zN{HFp!(KsLX5!3X3ArrMs*r+0C1waQ$xkla-cn=d@z|wLA+F6RAA>CDVR_b(3@|<=u%U z8t;aCje9SPc7vjJbNx+Re#-h3X30Ev#-KSSV}o-SyQI54ari)JFg;^KRFyq*%QAXO z#Vnoq^Gh~$E|6Danb31@#P3N~DtjG74u8B_cU!@u_s;q!`u(Om-YZHc@|*__$A>=; zGoyy3-&$PUq1i@b+Aw=V)C-EE@H(s^vnBhU__v%HO8*ewoE9z1$j|$$Ss=fZ-W$9f zeQa^=z-xhmtZ+l|uyZkTAEere+m&y4K3YH2o)-NzS?)TD;;d~mfA!4M&&4Z(w=5AC z6j(>$#T8V3Ihu){x+L2@N@ah0yMNu&yuL#>s)QF2uOA@e@p?|r77w-9*fbA3S>D|5ur)TqJ#!J; zRx^aGleFNYZR+}EHnwz)sg$r;ttN*h_stDpZaX-Iy#6l!N!F!R6pWU&cez-_$(=&G zTmCu{pm>AIqf6rKvVwit?fqq{BJ0_$+AB+T*@a^gKpG$jdPtdR21hEXE3*T52t6h~8Ev z%wmuelXQZaoo!_ip~(z!o#3IbLsiMR0Nh|wl2({ck5kJKeW;CF$K|$_%{%n4zq6{0 z#V}hz451CRaT1pwVn8+jJy7#&fqn;c{NCqBZTPlWzz2gql>D>i_u!Q!;sO~nPm?=q zLwg#7Xim`~%etUnh2HB0crR!0HFS)6uN~i(KzA?^?LXgZ%zt5Nolrk?|Ik4K_Yd&k z|Mw-6jgrT#4{aa08w`s2j(^*=6uxW0BcqNQ$OOYC3x}kupr|SNBkx)cZU4364cxWV z;p|#c{0Nc6s+!=(lzvy5eMYuk@hQDEhHAF4Izn444p(l&b6I-RN@#Kt3iPE**RS`g zsL7|1G^DDN_n}x`+bP;;sks4VrtF%UmzY5rTdAEby--`t_CQigjUUWo}0@ z8Kj*`Qhk~d8R);y`jJwOR(Hi_WMm|+?K-FxZoP3mrZA!2VcLsV*+%j1yq5IsjUsan zuZi85w88XBfEa6YE_F(u?_dp)G`wFSS!?#P%|~R&0hBOOtIHNr&&r;j%$k>L;|dqa z2rLTo4qb=wMo*hhi>T4P`jFntF7Wx}sNv^b^R${FEVpjj-r9DTk0!4V3WiKQl`A$s zZkOV|SqevVn8#l(H(XUp&(7d+#O5!5Oeg*Q)!6_mbfm126g%Dc>y`Z<`wQFN{dR1Z6JRnTho1s(in76Qn-t}CTJVIncY#eoFG)r84=gFg09(O5#?u z6yGP2Y28EyW4JrBmdwniJmVU?Eo*2XtLf+vgX5ASB~e^kKW6SiseW#*=?Pfv;b!c zBk+A3HQTO;NV6_)+3q{oncq!I3rc5(2ds1E=3B>A)!|A*%K-H)X(Bm0wJyB3*=D#Y zf&7fymuaWvcsZ#%=Uon~KE#KX*MyTHIdCfK!gThq8*wIs@0Gvxh4RV+DgE;(#WYYQ zEGdh5^XL}2yx2>f#gvtA11-hGGvTa?%S_qD-MzRZo=)hjg#2Ey zvefSLm@9)_{pn~~Nis!>L=H{0ip^$Wg18r$)d@7V4wYlpZ;9D&NLxl~89Kq$<{nQ5 zX41*o&oJ4HJD0O^cz%x<_lIxOay|PM zHsh*FY^V#Bo1cfX#WCI&kNb1Uee@Mq@7tnlEqi1ci^KdGzu%Wqbi*;ba`?2v#iTRZfplNl#qT|n!?=uwfjD? zy=vY|US3+hpdTkc9Vfei$sS@b-D#4+cQFIC*3@8sHcKnH_%x2fa*t+S->;LI6waE~ z>y`gF$R{_EXPdeZuc!2k*dP47=suAwMz88kD`UlBdTUu7`Pt;%ppYKNq5U zO}PJXqXT*8GG1dsDDHj!uCD{J#iVwIESaqnihkdgEhg1d(bj8SemLlGlBAe0oz`3a zHDGY~RYzK}-FZtEBhA^T&&fdTaajsA(rBonJLQDa?HjcFr1pW>fySK_3h|L1g(tyc zf6G>U$>^*}z0=9y);m01S5P$&TR`5B)agVf`R}*N`TKd*q(hvb6v4oTmnr?j4nCnV9WtXfIm=KiSE98>nDN5%@lSaRBz*3o9R|B-|ookw_Mw1!^*?4bxct82gx@`r=zLM!nmD>h)Iv_ z=5YJTOtz#fQG7`yNQ|mr!iwTyXeR!s^mE1yK@ z`BW6&cx`14%Pl;RbD~x6ffc2AfMZ!|@xe}?$D$OM(M)nO_u+dBJ8P&mrVGoC_OJxn zE_dX&;IYh-vILiFiX`SUbtihDpw)&LF*gRZ!X=ZcgLq3hT>w4AX1ydqq-dL6G%GBBRj?eVd44L)JZwf4RWV3V47 z{>Dfn1Esa2c`~VPr;|)cH1g`@+qxU=&UUl1kI$s5-}7TH zE2``^@mtX*`tF2QGMg1xlr*EmU2$#ntUzL4ZD(fjl9+p}*C}^s6yM%F+|zl}lfxeB zDcSJK2P*O!8N}YQ*ifq5BU)0)GR`*jJvkDeS+gkXf;^Poq(qUQF=Bx{Ofbii5O6!1 zd?S#otRF?bc-C}vh^{bExoTfQK8_YcmQSDzb&F-9iA*|I3=_1S*h{b1$<`HGxuD03 z7e!q<*vLrIK0;ix`##CNx4jFuoSYq!!I(!4?^9=Klf~^W*C#VEnEgH2uJ|8NobJy9W z#mc4m(lk@r(n)+nX{s*_%=h9nR>Zu? zP~5|H8V=2NyI_}7!wV1L(7s51Zi;EV#m=X)Gz()@Q;8+C{!rfhst&QrDjLVBWlOwj z{X?3`1&l!$Z~H(f`9o5S^gR-&r%1HU%&rsc^G&$$C!xjx0nFv`^Xkd9BF@H>Chq0r&Pj`S9s4)1usWGLn= zMmsTQvSQZFI0MgOU z)SCew1&WLN3tYsu6ZQmvUUPGN1HV0l))})@<{f$#26&H zW1j}LWMX==bm~F}28ZgWUzqmSQZ5yZi*Jy`U~lWZ;eA^g6JB5IV(mmhA4lgiG(rQI zv1sBUzvh{}RbiWT&h>W4rQ$Z-PtvO5u_d<=l6IqsXdnHf{+LeTLiNHH<+d7eEy-QF z4Pkm&dOhUhp3?VSk372ZJZLB)zYbTTx!Udqg~DWibAPnjsG?GtKs-dB(UHI?y@@7n zRB6bh4!4m*A{Mo#u+T|KwY>2Ar;Bkpntz4JCvJ)hJ3=-|wqy=>r95KZmc&@Zx+SYv za?R);0vF<_B`t5!bvnWRlz7kqfoguKLwv`pmaYVMExY@F*8CN5dAUuU{p1kM>_B!~ z94^J5a)(i&9tQ|b@6 zP1dMS{&@23s-Qssuhtb!U<}wRiNsi0_)RR%PKFr6%%JqghT;=BtNROPojV$s&*SEH zwsl8X6Nln5J;*IacRl3PG%%g)?;|}!exU6Wi<0Tjz>#W^4m~5 z9T{8^Mal=uDZ5-uUBP>N_INrow%%Qj#NCx6_T_kz2}Lnp=;SxaK1o6gez4X?Ikfc5 zlb8LCT-=$(w;KXsg4tcuCwzE*r4m@J7K zw9C5sg!G6~>{CjK%}eP^)*ZbV;eR?ZmD^myU4iY4(GNc#WbML$^ zkyw&RJX;EGz$xN#OtERsl)YOo8~d2$ly{3foLRM~@5#HOGvTXP&sOSCG9D1W;AmN) zD}BuRxH`nVBJFn!nx1^U*y;|RLupm@#*1p;VLd zOIp!dMo!v$vCc}RbJMF?MQPS?WtJ%^FWxTe=QiSaueZcxy5P)O;^r$-!h3pzaY@8s z1ztTnB~%P&m0I_h+}*{~v8KwlHxw&%1X?ewXj8R`t(+v`6B$^$S^EN&Ru|6BB+?q& zdJ`IGoHzM+tK=@Nw}-5mHX13n_T61`s<@5T94ema&x)pyhL%d^1!oLox`V+cH1|~O zxfPrmQcboYniE4+Q@2AWzq@pc!lPkQ)*YJ;b%!gT)d+)VdU=7MEYx4ReN zT9$_#soe1@ea54+j2WAm3oH(B0~y_57_yucX6<~4m`8Gc8XvOxw)Ojclo3PCnY`v_ z!vW!ai46nYWd+VwN$kd=-$e0cCq{ueQ=-a6G{Tv1Z#+e}SHcQ83ewwi64a|M4+1@mg3 zIEC2Hd>iNyS>702@)#RiTN~LoKqGNuS$2uxZ`Sot3q>41ve{LAS=T;MhBlD1iaH9i zi09`Wt5@LNkrlrw&dKRWOw8_&He4UhyZq_hCwlW=hJj z8>sm|gZXLb_!d&$NlTYiiGFLBc#86jXe&)sNj;H&W9L!Q zv;naccjNMmBq6|IS{=y5x?k$6XD(Spwx4nFN>zd7zc%cO63 zuGU^V37PA5^==*ecsQ-7j=3lj&%x&Llo~~5c!%TH@|6W|51$O>#061sX#pCzbTjH4 z6}lvbvMA&*@fJnD;(ZGH(9p_MJL|N2(VWwm6*tAH9FE()GoQ>7GUYY6etO~4=bwz1 z(0b0z?(0~wXo*g>#oL^@Gz-tl>(yz&Q7jd9y;%H|NubTi zID8{)uX%&4@#~~*X&vb2Rf=8WBG(tEeku&>XwZ4Ehq%kUO($RyPO5(SPTg0R58#e< z9Ahk`)a21HwY9a0TkrO>Z~>U`Ar_5FA@#5ZnoC-r$!2RqcdfRf_bt9q zwZ!3=y;qLR>*$rY7dE!0RGfSqeMPN$vA~8I&2D?F2c|MxW-3lU`Ii#GKS$YQol2DW zy|^@X){fE>%HO02Gbx8uCFH*NJ-cL*Yt$&CQ&`<883k_3Gj^utI6c?zh?bkD6iuf@ zQOWVWm8t#Pt;$-74vDG#&Haz!oz@sHGBmiyQt6vAFIJ1YCq3Op8^hyJ-oa+<-#mL1D(qPy#A>(-u0?_NGNaK;xJxCJXJ>bLH=d3C|M zv&H(8mMm#gplpur(8yfN_!^eosN&X=c(1?doRR`XvA&4LcBr@8A9-l80HHBWlfJx7 zI2;Uik&o{?Fo#e#gL|f~A-Sl=Kf4h_n#LiY-z&RH)HEp8FZ9ghwdN|cNY^(Z8y1TU zyvm5D{Do7heHl18Jj-_zW<}Xl&A(#mCdWGOZZ3!lFf+vrukM`t&V$GZ&11av9LUOe z-!ZR#btz-@427f~-zg{G3ceKzwUj=7zAca#~?9=ndtd1PVx>^Tze~6oS#z5Tx2Jt=`f{KQaqX5lS+@EujfQp8&|qt z+BCVu(Z01te3y3r!_bCvT67JuFY{C8P&PIjlu$GJqe%IQuj~YsLiHSN*M9!E$!u@V z%xJ}@61}t~wAIVDW{J-3opiA}U*e#~TFmv_%Ia2d+tp&JFF?;`^@WNrJs?gZFPR%( zOFOcS?zx#-Fg3%p?qkq1f%v`NUnEXHdJqwQskPJM?B!=}bxL18-eDYVj(FjDZGnRv z>vh84i*7|5d+3!0R1I6L>V9=&?Vgf19RVjNt>x+*R}G93Cd?jaQ+4;U8%UOP-7s&C zin8JYrPqERQ?YuN&^31>b{@8VzJ8vh&TPSA!QL%``}W2M4GuS4RbP8`%F}bnEKV_c zsX14SL|4KTf5K1SRToBY4W){QAGXWC+-}#{_EB71o7u|9W|g4TJD2Wb>JeM8Up(ZN z|D}>5R)yb>J)N5#l~vT<#tzwMQ`ncBw`TqHKD4c`d6?m5AyW2->fv>JDx22H34!W=o-$pn39}G1opCN32s@ z>V102aDPq3p=)gCfz!*^48}57hhq*gSMPBes3}vho4q8}?QucWy~|vU1a<4>eWK59 z^tcJ;?8=g{eWGrwo^X~rWOJo>Wly%DtB}g-Gn0zUm*Z}@{XOsIdeKYVu5@nF6_LsL zgE7Y&X5G)1?MJ&F7@k2m^D?99%DZ@01oH^x%trquS53prm7Y5dKij?&b-XBnIb{V+ zMk}@M^42iLM9NjDp0@+_JfFFQW$;|Wh=212nsoTSJofvVs2@+3qr-Cv(CWkA<`OQg z89kTa#XstDIBLt!)`iau#sC}@T3z^C)HiwNvsGmO**y=Pt3Us!E8wUj_Fj*1u7hw? zX!YR8s5@rqn9p84Yh=`(1ipsF;iyd$I9CQ76j^EpF z0q!>mDC!}d@uCWp3k^Y0+lu}e6?%5x>K~8Qu=B7FCiwgLqUGfj716$a!5-kLE!$l2 zp7!9ecxYd|y^o`-tfQ|l#s?puy={$xuB(rI&^|v$d90d&m%Wj)i;?Lji#3Y+;Qv27 zjst!!-&VXcczy_YTH6GlH}dN#cq5+Vx83sp-zWKria@zx+CPKOQ#8M=gfnu)Ly7)) z9ViJ6!x`!MD>tU2%(dsyRiRh^=_#5}Y*+rVSHZErAoKE1vxwj+ns9yd(g(zbmV|@n zcfOdu&}g9_h_Cjir)Wa4gF$RQb=_ZZ?D02)tP^6w=Ni={-f)JHosQZ3Joq;kJuGXgVv#mtuTheRb*yHcbQj&)2*_Znfwr9-soV^Cb|2md! zZ4jG}?X__1x^d5Yoe?||RV`zwgQcC_uC{LdJE;y}cRty5#z=D<h*5i*N3ASYl7`!tm);25q7O)Kc7XWL$Fl9k-}ZXg_W1v9+&UpH zeBOS)o%eGP-)6M!LBK68pa`%>S%s#X|MA*w74A49GC( z7?ov*ze&Mac5l3zL~!RM6r5!nf93Y~8uWWj0%h5qA6aINci4*eCHVU|f@jVJIboc% zmA7GiH~Hvo#`??a2lzU9=vm3B1X##8xI3sD`PwM?I=k*PCwN*Z8#wISs*J}wZr0to zKiJd2T5h`oeut%Vhzo9?iP{!(e?JAAO>zcm*2bQjbmh&x74-BS<=6UYt0*cdD66Te zs;MX`sVS&|W$FLh2l}7#G-D`3UxR;6Z70Z1=opouCf}st3?1<_<3F`Jf~zJ8z!@6& zD?`7xI^SzwC_}+cTKJK&{CnHb;|l|n{;3I76hVL6KV%!;aE{!jRiGz2 zBQ|yXwqMB7AM7qtke4tc1VFos;=8@)zxVzYWd#ML|L5CV ziYg1-c5N%5gB>0=+tEZ`-1ah^{96Rd?r#mwQj`yIT?uz{-ql#VCoQWK>zK@0b~Db# z=7YlHeiAS1{-+d+c=<1>?LOz8Dhq^JnN}9AOS~{Sn-u~UEO#Z+#dvRwH{&$X@$y1( zNu|X%q=q{l)V$lfH~tcZb9AP-dlUDVs{Of$0QPa}=b4f5RaDLtuQTN@Ip0)!-(+?JYPiB(bq{1v zTCSzGseSEX9r%E=Jg(%)THNLawi&a^rS0t$N_2#3uiBNTtf}Xw&NOM&Jh67gDk083 zB1)=q$6n#Y>j!gdHm3LcN^3stLhZ>B~qyONCwIUB7h z9!zRaWeN!?>~7uHvUe&pWtg_J>gixmzag%nxbk#VQCrDH8-ipT|#7w!i0gW{ho{?^0daqpbpxPaR^&#eV>BiZaU8AI|3 z>kCcP=5$`C&x`78^VwPGQ4hhOrDIX5 zm|!-$6U?8cq3y!x;hJVz0hh_MEiEe(KP6~T%Of9TOq2*>x_p&+c(iLV!~c z+XO+ncNGj}OMr9>hrq&-$$R(4Cz8h*4ZJhoDulG1i!(;9MpuIkeH3v3Q|)qb6<#gV0`&S+pMO}dCR zm>gX+Kj|BzRatQccNeuX@L*t^V%P-{@4;uccYZ1#%y7$3Ni0fhn*8CyRXL^+NwUIW zd-J_-PFhpWKa4OyXJE2!b-&tg;8)FTZHh~!hUu4_`+LEl{p?_@)rU#ndh0q)dvsi% z*Yo93Em6-oVQ=`kdscXC7Vb=UPEAfmMzxOQ=bDcAtm*60s_ZwvN}2Z2rDHIRC2@v% zCArC?pmj6FG%>xS`tY93bhSCF<~FDGrfKYIbiv(T*=Fg8N;=EM91&5u$6o)Y=DskI zT=!JxtGbSBj!Ru5tvG3k*pidInE`bx_D!d#A84XfN(g{Mu!nfhtedFiP`{-h zN>aq}v9~r+rUn36?k8*y42P9t4DGjgPpe)-haFL)M_Ki{%e>MqED|7E2zjYox=+oEXlf9*wm>f zLu+GW*1LjB8(iBR&jjo-<<>V@6GBe9tPpQq6ox$`>hh&|f!wU@YeW6y^x#v&??a<` zD}wfWUr{o6zdA{zFY~zQPXDvP8l*!-hLB_|tZeaBKSnXRIoF9lv+6m!|S~}uRmewv0Gb1fE3Y239 ztdPTYtFPj$KD?h0QgnQAy#DP;*LYs77dD3b*{&j~vtQ3T)K*{?N~@87__>p*tT1zj zV-?MXQ&LM?l4snfpDoto@!_&}$fYG!k*%h@YRT%w*_FoSUj?cPKL@=Ob{t;Sck3@9 z$`-=TfR!mJ%LW^nA9}^pHA6+#u3R)Ia7yFFHK(@6i&G!PhXi5Q&xwyT@V_)YJuu!s zwBt;HcPp4l-@Nj{%J@8+<}bq6P_4q8)VFP_FXS5p1v;{9O5!K2Ye{eSxu_Jq|NAzUcB1v6z*~bAnv2HqBnlHtFtq_my%`%*$vBPbM;; z|C>_}bF!tf`f-C#R`Uy^avp?R+#pj}ePJEf$B(LGe#ARt27SCaM4_RN}Neo)On1ZsXhdxvD;y+g4-YyOYPvPn~Q z4jWH8_#m*3JjXT9%%M?^?jW z_5RE_^;g8-)4y#<5O6#5AJX|MP%9;?KS%9FgKDOld3{U zj$^C#=HEZn7K&X#?_d}hcAM_fPQCA=u;iWe&MwY=uGe-ee_PUrTc_zt zxnf`6vo^iA&yPGnnq<)y-lh;xK52{A+eLyby0!*cX8PNO7ZgzWRl0!qR}>bR46 zfq9DflK%E>-7-08<`ccJ@Uzwankz%3)U*zKau&O?x_lBtZEL#i0|*a z=v1JGnk6K;cwl82d4+@)>dmzeS6+09-^BDQGFOFP+bK8XabCG_`}X1Ny)S!B<~l4~ z`}oNMEsH3fhrMU!f26R3D39g|^$q)$%)Zk0!s%V0v~)p54ILB~r zPLs4cvC;Kb)rROC1KulXE|$>-)i)7=v^8dr$TL#skE!)DlySr;}cXxLW8r)Wb zOJL#d5Fofi5`s&DOCY!t+zIY1Y+)bdoW1Wp`<%Pa{obp0U)8Hi)%-`Tt_3~E>>k}c zTBrXKp{|JW=Zd$iZ};1GxvZxdZPWa>A*T{*Nv0Q1!LR@yl3DHU-K?OtrQ(H*aMYEz z>_)!xF7~S8BUbp-2ML`;3R~=(k>Up(mjs+uR%Da>kQzC6@AyC2U^=QoNWZS@*t>+ea~Q^<1_pBY81Y;+z- zh_PNJ=^I_bAJ!w%4G6zU1?Su(&>ttOQB;cd-~#R?n63a(GA5rHkzAOsw8a_p*S|h> zu9b@HqIL_4XNXerW9A$}vIj@ICP)}8ob$sOFf3BOxRjrl-gq~# zF3|5cd`>kI=nBRZFwj@8HMVfM$vj=S1BZZMtR1g%?#a;Mrl!oO?G)OXqPyNFj>43$ zhrkB__3R_*j6lhTRqjZB0w=LHpMsoE^BgRA_)se0g?D~hLl)9QNK#irN$BsJ=`SN- zg1@l`dq;GED*Y@p!)&Us<+H^vtx(k9IeW1sDZMr4kVVPW2C49LmP6jPd>=?}L(0jb zcgbKW^}ewbq0}v;^vc>2>(H9#bE@|-(EAiaMQ+g{gj90=rcMgCKK;bka@_PHysgl7 zJX~btQ5ayVrrXE8C3e(f^!&bD1P8dAA2)ZFy#x8nI#%!@|@AE5B#~<81-Xov(Yq6Y6 zI~ebC-9y(7bLCf5AWw=tJYmxeeP!F*hr6d(d=$gtL6m&Ifb&CmCt|lNSJ{dX+MaHK zP2QE6VMG{nD(&-o!~2B%N>4q6&*#s~D6GgD8^lxA;?MUR@+(j?C<{wL_~)8Ke49iP zPlOVQ1x$@ZTfcnAmG5oJgDvJUpNHtH(uuM|6Ry%snkn1uQ#FcWdO2$z0n=EpHa6H; zU>frpu6?+O>+{%-?E3rO2GatZdxuQaX}HwLOYlnRY?4i~ocy8%tCYV#uxxTQX!a?RP@KWIg| z&(KdduQ0Vgcr;|a^Jb}tK16-Bs^)wW*b`i+x&!b(S+}O^psq~hmnT0fg%4FP0p9GY zl9AZ~OzHIuRWwLoUe{oERiX_+0mCc7Mc<+MA=BT~7vawTgr#NODTXlF))CJ3JSe(} zN7;EiN88S9(r-z{#Y;>8;lo#+g>ML&zMJD-X}Z0=ZyTl;lX`qyIx>lwh!<r^0o9XvJk7V$jcAO1e#0!#LeI@Tv!Kg z3A^-Y{5hzAGY3%Wp;0nDXqPqJ3ERGOI<;8s^U*%?U8QIS-rr$kfd6nc)E%YC= z+}_X3H*^+iijGY|ADrX^BlC*(f<+k=sxaC89z~FcH!_S_f*+rnTZfil9$pc>@a={T5{){*gn!s0A z4#FnbfFRP7JPxv+Hjhsw=uJ9Hsez?TTW}R} z@ixW#EAb%F4Q>g^j$H>#Fa4+wXB;G#Cb#b}h(K&&^Ul+3yqY%|U$@P%u!|5yPv|Nn zO*(r}rhJiwX5m$L=w9g*K|&Uu7lpOP2n=32l}NWbYC+3TbUha&naAtDh?gJg>h%K0 zCRR!;fOuoK4+~n{Hw@l*ZvlksM^cO1)jpEK1Zo4UX}=2(ehVQbBce|2TwfAVsp~bR3yGQRAIb(L zgU4Wup#i+8%e}&yib5BTx%jEFr|y6Hx=>#6;Y7?A$G?PE>y*l3yj@zjB)=}Na%Y1K z)zrwBsdkltfy??0hyaOt+SH23iCVam&w2SGO>H7x_t@<21MNxCqfG3*`s@I+SPs70OTj2Ce^j6CDinuTUQF&!Ie>#6O4fsvM{g`7jW_&YuAt zCV7qX*(xyYk5>OxC=X-D{Hv{_jpq-c{Br}3y9o9r>+|AH^e5rNe=Z`;QXEpGyyaDk=ObK>SBZf%Cbf!2VQ&;Qd=k;qT&Onoo;ipXJRgyxpvx zK3hiH{As@`6+1tdH8(G>0QW1tSC$-t>{O^o|0^Yk|LZF>|4?xGP2lY}6)j%=UyH^5 zTQ#(f{F(0W2kkg+4+@vEeXm3nfscbN&QbOXz~gw&UUUx+(fb`fRsYBBX>O)moI!)? z7WsDfD?ZiUXzSaLUPha>1)qO7nLPIPKaaXR^Kc7tzE`@(Xuyrl7Q==GmDAw6@y?Gt zz+9W?f);=2rJ))t-hn+TCOMS`$A-)n){zf$AMK-3)xAYDC zcDG|)uK2#I9B|Bd;-Yi__2+LX+eN`UcDn6LOAkTpFWE$!&iy2Xvb8UmE6EQlmWyGU z(u>6&c3Z=*a*nVWZtQ(E>z(So>dI4h8irTu7YqzuGLx&E)5a!=PKL!`CYt>iAwT5c zOm+lc7F7HgoGWxUcK(~9l|zAwYoop;zPGEV%V~xF%NV`E==h@|_qKsrk6YuW`_1!} z+k>Y`{6{`h|C_sWz3*zkM|#tT^Y|JB8KZX>95uHV@2ruW z#lF}5RQXhdgXi}Y5f-nVBZ@UjoG#?eun3VI#cw6##NMyLU!5yGX|6p&5>B7Yhd+VT za|9YD@Ry7S*DshH|07^MQxDJF1H$;<7>K`hIK#mFf=TD!Ve;3`@Sl?r`hq^{7$Q6W z*!4@G`V9$`{dj<5s22kIdG)@yAQeKtPRL>k`!L!{zzY0NR#{ zLxCAn zXILf62199>sp)Vu?@CE~sQ~3$9UCTaRd`K!dU(~`f1K&CBCM z4_Yh0Rb~PX)PcD`2T@S5OW?lhF0pM;EQYp(`$4)gRes{bbqYBYukj3OYvac#+WJy- zw=hexrkGkgRcb(ybnJEcI&n}b7cvwi%U#hU@{mUuUnk=Wi}S2a82zfUnw_Le$WN#6 zB6Caf^c>??4efn4JwEwvnw-an2cu|YSTJF}v@h8|3=sNByJP~GULE)ke0d?~oOa`V zyCZR;F?_hmMT7;Xjfc7xn3yt0!)C`%dw9`KS#71?OSKap zUG?h5pztlt9&F)3l|2fex>3&=`12xBiEtmpihCVNqG>6>)JAhZczGnKaN4^5Bt(F| z=aE*Ed}+>4&CbL+{)2pl;f!!HXqk^R@rN?{m)dl!2nhLFi7LkChYk-RB?Ym?9RQb8 z3WMTQcD672k?&hn+s@fj^U7_bvw)s(q5TE;ad;d@<}(kI=W)?6TM2EV2AtgGm1D!G zrC>mpCWcuKZl`B}e-uD_oS4<`i%|B`f!_-ncUgAFYpSaw;sg%{IOh6X_Vaz=$c5$s z`DCfmm4aH|C|<&KXZm>mtbi3_DD=Z-B|nDNe)v2OVxi4AXUab4>_ZM;cd^!Obqa-& zMnHVmZGOUf@F1f5RqZr%SS`E*33=Il*kTi2?kcpTjV^b{V0~#w5vEB|Wubr29%*%Q zvn2oddxC+H@E}wWj^EBgK5wR8G&8sOo!^Y}heH$1Wg4d4?pST^7Q|^wT1AD~`maG5 zF=_M){>Ax#1E@Stspve?1GUrK{SH0<>&!mmx^>y%J$s%{6?qo#G%-Tl;vdlOCBy)* za3%rpP?brAUH%03ZWJwgWnZ_eVM)C|$Fv>t;7*(bgyWo)hJhPrx;OPbFHZROtA&aU ztv}HLE&cN=m4Te1GsHxbc@*BD8;HmzY)r6ovRkG5^i|+i~#_(6KEl5}W z$ehZvky@9T=JhrByq8R#G))1(jgWw<>l*&!t?0loJE8A3`0gCOwSxf2;KOoe*3|es zs~wTGuPW;V18Znvpn{qyDJsJI;w94~cm(lIOz*D_YaVCPy74rK1>_Lu_un9@+Yqf# z-92U=ycr67sE%37@7W*zGE;V1N>!iCqx`T_;wlR*-cUBN$)sii~8Ul^q zp7Jw;4Ae>& ztd@__f$&~n0# z(U891rM}PZVjWWh({bX8rg85hz6=mq$Iigmwe4n}*OQUh2I1>q;e+q^JvVZmWk$z3Z(_*$ zG?+iL$ynTis)fI|Wy-E*h^$0R z(p_47_kRt`Jn?v#y1rbhl%X?FrG*sIR_M>-sa2YCppT&zFW*i;k3g<*OeV*^g2Im7 zda_BZEnwZ{Rv`Uqj17dV79}{9B%6e&1V3$ zA$p-cw_k8Jd`Uk)8Id;@@%qGO<~2U01p-rn;Mj-_^f_TGlb7qGl;G|p`jkSYT5fJm zi+~?^{hI!JZm!5H*(7K9zT%}9t8UgaZ591NQ7iqGVGfz zzx#1AKRcTw*{OD=NDxzdT;i6OhN>5wc?#TB7LOtr^#5UW^b1cbjZ5d1yrxWuYm);E8KA%n{MY^nt?AAJyCuPdOLcnLe}eAa&Jf z`sMcIzTJDZsN$Wi*pv5peodEjiy1)LozEi3&>FUCT*WD+jHe`9S8y;Ragv-#Xty$& zaqkGftbm3oV)YGxB86^ZLkbMl9v*0D1#nWhD{#urqqt!d4Qj$LEerH21q^oJI6>Lm za^u`ukK&vZ3t>L#YTH`3INX;dnA;gvk%5o88B5MR7!jVcH*3q?=O~+aPD4m!kKyOl zO6_BOqM{Z!-%79*qlE92{DY^ycn^L*;^lhbSW+6giAlD;IjHW}`w;~q8`mra=BiMU>K6eAD{1NKXI&Kw7RFum1 z>S!f%aiSoTmGsfTS@wIzjxMDQstl=jE(4)Fcvq^2ZIz|#1kZMH;ejd_cRy63fUhLj zH#wVRzq({&AzxB?8gH^ZOULOPGTm{80t>vqR*0%kBE|bSAKb`g28uk4f%T)nbmND9fcjMv(^*7E=TJ47KrG7z^kT z7H|U0dhW*cm7Vdkxi{)Tu?+9+;OvdFjzsQ2lZZG%qp79!lGbNnY-4ZN${m)EEQt1v zTp6_$QFS5~z0OC&F&a;3N+0(A^P3|7d4sV@?`)Q{L{=wv(H=P_i;}^F9Ye{{ zo;?Nl@13yQix+Nlk&^worCyyyZ3g;)5K(yrS&2Gc3e#Nde4sSE2!9ccJ+-`_Cdbe2 zn3QLo^ZD#lt z|72l^mwXsD<-9!f=@SllQ(RNkf(MDZ8g zj4(KpX~@9EQ7|Vl=r;z5S@XHJGM7xpK%HCY3J>5XFP^85753z~O(q#j%1y`KjT9!N zvF(Bx{Yqbb*;ngOzFk$Vul~HV4E7K zvkH*!Y-KxBBq?pw?sOuH&OcLWvhnrIt$`b`6VsbL-w%11IU?_l_o&n?Mo7vi9`#{> zBxj0ROuB%3*vsFsy>{(qAs1aXd(B;8lve|+wJMp;i+RP>qcx^Z`#G262%IR9CWZ~r zsn$VaY#1xVS&#yK=}7T{c<92V-xkMyf-zn zP{2DX;G3QJ7Ax1gF4!y;aZTB2Z%*(fZ3^{Dh99Fd$tHdH1^ zDJB;L+c-s1I^5xbY56_;6%^a1rpkFe2OqH@Jw{Jq1pd zE^7qY6sh>#`?~V5oLq&FnwT^2l%|9mg9@RXe^fEfpcW_C7>ESJajsx1lfu}3ql@ni z%q50YbA}K;g+O-y#>7e9FRU>olWWHY8M%9C^m?5FJ_%eBZU%s%B6=$}^pbHD3o-^O z_ds~@5ho)eY(O!x`bXl9>7@G-MxTyM^n$kv`8T+C=2&S(6l~|2TB?txgywR*w)!HL zPkFj#K{cKBPAjS(L!Xtu%qfcmk^T`LLYFSp9(bL~v_b$weOuYKTRv-T1Hs8djELp8 zbWc|H3&D#y4U~{g>)8~`5pRkuaBCH+Sy%>%L#2{ zw^p5>8-vjYqseq+DpdART0d!jcva{XB}sMsUeu+IqLJ*I`rCb8z^9K{mOR(*2xrg& z;V{IzY=eteV!N@6ClDvy)OjVfm>ts+uuHNOOR%m!rq{a?Ab^df!xHn+Es?IHV`mq| z9pzreuSdNcLLT^0RNzp6qTp+!XVona_|`EN%+U{DOKBD-MP7nbWD0vQ)ILrk^c&FO8{Y>-%Z38i$xd{}8+A3gWj2!Hr2h>VnUPxD9~7qR6Hzn149VWo5;0^Llk%>cY5Aw-@M~ko~_1I_7YAQ zT!N1Z+~Ijet}qfC?gfT<#B`kess_K8TdKVMu6Lytp{8XB8p--pRvS=N&EXc|3J6TB znh(DmJxS5=?T>``P?<@~;e*oWCeXuL`P~=CBnG79Gs7AII3USJi>$fy7O?4jqLqO) zN)6d;x`*}tU2TmIakh9&`rz%>6SZ@e?i11E>lweM8m`6S``)#xFFiJHHQ|p45R)%f zH6Fed=8O;}{?IEaQs0qM!s&gNa8S4RaS>U8_oc0{p9EzbWv^xPaEzMBoC%-zaC_xu zEbiw{LNNh^j2k?ES|d+_*qMA9Z>jNsgOvl z$R?T!Nc!De)3nhdq4?ReMk_J3cj7q>I_Sr7or;T+I%8^4NJM@@Y;25H0>iSXustvh ztiq5-8YuFR8u#b>XwcUt!~NpU$!r|Mk)f4)%&U6Na)5ocD^@?ezzDa5bm243>);z+ zlBRcW2ah4R?$2dg^nyA*rY9wRI=42)YDDysT>2V~X}Z|BV_DszHWiaNs7RiX2#%=D zXa9UEYK1MCX0%cu{Y^WJux*6=BFQ#DGMg%q4Eh=QmGu6yQo;L6<8k&kkFrB3Q|dc=p+@KK&2rlQ{aOZ?OS;Jp6(&0XW!SAKWs zA(r&uo#hypF;$X&MIBNMvIKt|l%zf-a&G{#vw2wmCH>;iVx{^jWig2wC&fZig#WG~ zp$USaz6tFY#knJ!oD__zfE`K_@DI11Zg9#%dOVH5i*q|sEfYG>ZZ}3cg9a2MDc1Il ziuR|CG`TbxDZviKwPAZQN3#}fVoAh);9aM{LtzEnL1B0xKUIa%O{U;+nG(|Goj!8lFbRpL4fOt~}y}h#*w?%w-xy3>bHaeDq) zUZt_@6nQCJ4)NMY{30lesp38MpK_YqbP4pkj%uR|a$v z@Ij$&mntcRZX|NGry)~2h761?dj>HZuMYTc6wO_WQu`nu#ZTy+>du6C$pd}b8n%P4 z?yYpAiN`nA7(JpUN^8|d$;BlS-vv}U7SL0%`q^qwNkN5n8)I3o<;LXsyI~`(w3Sp5 zqDpal{Ehb~Gx+Ia{qfaW0D=+sAyN7@kXZYgtE(6}jxM>7M+#Mp_>WSl+qN85QRxI2_QG`R5jtszl$G^1s;Vf6FhO zy-T33#E7aVlEl$myD)%>39CBwB!`;dG8n?kcqO2HPS2O+EET0LoL(zsfHRUhS~jZd%0NYqqa1DkZ}{U)F16J7 zZ9pbw6Cai>TLv^Zg}f`2-powWdBlC*e8&yhe_7mX0`RSRJA^3}Ug(-_XW(^mB|#+r zozZ49t0-}X1xojd2jr568S#=I5gv4IkyR&J!%f~orrb;Awg)?`U~uV8>I#Kf*hM36Y4<(4NQS6da!ljGp*yBDp z9u&4clLw(MW}MeF&A0KsC%)}(mUA`C8b|}Ze{cBSnJ;$&6{vV0!FRbE_xAG=0>E}b z;RlXZi6}DkaMdZpt~(!IcC85(-$t4>R|cW|C{an~*Hv_#!!hwZkD<~Wmw94Aa?cBm%`J8mGCVG2$HW%maUO?mh>De(AM3l-{7kwwS7c)``jHRE zbEx3zGS&3dC9rVg!(m_zTYk*)jQr+L$p04_kG#J$9yxwP{-z`5{OEE~O+t|`d~^M! zA9gr`*ajkf7^5S{_uSok26#cRKKnA9a%Uf95>0Vvymzut(Gn~r_#D?PH^!S-8%3Ym z6}6Z(KQC;tR;F@$B9`{|c-6OD@2J}U=+dl`P;^!HlGlR1(X|H%kG@O51{Nplm`a7-A-bLqC6`Skh;MGW= zHN*%5B~S2-t8YN|xP?6mH%e-_&^Klp_pUdOZ0m%H`AcY%i^SGM_F`)k@(iziOU857 z-LzNHJxgm{X%JSRH9e#3#^4q~lXnkaGc*kmP`Hsl_hMb|%k?BpSACq2Yu-#MEqA$9 zfaHk5ip7$yu^Z%ox-$6Db(^_)eo&L=&!Hk#VZZr(e3g;si>s}V8(r@yf?Y3uS zVaYcSt=kGe51lxd^WZun@>Sm5uJ0YjzEtj4xD~(Mu?;$Vx^jtibRp|Q6&k}S1uMo_ zYi1e&b5ZJ%-M3VGlBHR`xQ?E)`e8eodolb9oZ&~*!zHKs=~X${7)d>?-OI8d)C|)V zr-YfDgqh*bq(M0xdYqWjbXvOj1_BxAvaiuB2Wl4}nfFu8g$Vih7W=*if9-h$O@%x07U>>V2DVBK!j2Aa?}rxY$8K)|*7#|kJc2())hY2kU~#I2~#1}|{DYfs$G zJQgf7Kfc5CtZS?dj&a!TY0WfUY$f+SU1l*^jBfn`jK(i4TW-JU3C>;-Ic&Y!9X|H+ zX`du7k5+8FqkQ!L$}DroTT?h&?rVNmiKk?6X3l$`!qebizL)AA?>|*HW5wFW;#c;i zb!hLBr&vb8JdqQ|D8gtjtY+|zCXvM9t8paJEkv8~bPxJYDg=s?KI11}=IAN3YHABC3HHqJS1|O|-w~6j zt<9snjcmO5G_%*Vm)k&_U-yPH7?M8kDcz;$ZpN*YyUHJKbCVbd_M4|5 z^XulsBZYsa3009Vfnx^IojqL46WfQB^@Z)SS(v2m0~Rh2r4jYC7593Cse7pkscnfs zMue4!X4N@o;F_qTRqa@0N$#!*GXMcp3jQcx!`Ah*R>Sis5Z)R3r**222l!x>kvphq zQ?*UTuv#73uqUF=6hp#CURHr2xiERXgV0QvdYReTS-z=gSdWVVIK|t-wg>ky_kwVP&(Suj~E0ix}n|A70 z!O%ir>GKueSBR1+l=7yPWQl?3qFRGBT+7?>^(**<#WIz=7p#N6CDadv@!=k42;Rn_ zi@}Vnb&y4~7ZM7UzRiNxp?73b7KPU|0NB7VU);Qu@2`h5E`nk_CNWs3;y6V}aMJc1zQQMUpZ@yc! zhxsheCdcIh9VZg?F}WNpvi%udipR8*ue*_$ z>pxU50`$K9R>45ifq-ZqG4z08PUdu+-Zj_wocUJQI5dl?ZluKN&3kxL27%iZY0-JQ zV7yy5woArjbW%F4gBz5EzNFcPvr92Yq!PWsFzVxj~V zxp_;7i@Ix%Pn?Ei+F0XC z(Ha?8<^bMEZtcHE{81_RMamku@eNwM4b4{@1hhXy^-G(=^pl*ub}I(#bo&G6ZY+U8 zU-c^OO&P*F7hfc4=D*Z|CE*5B1bHetvi14eF8lV8h7Na&%N)S9@{V0iXF0NZ+sxqz zlzbBG#=xbV#sURidcQ)j@o}DSLeTF%3w(<$r(y2G#t8|I1W}P|ustk57f!#Q#m!w9 z;#>1>hNzJXP_3+>hGmgQc05ls^UD=0yh3cMCxGcHMdRM+t;|QkW$p`*Sbc=K4hu{- z;CtFEmR=tpz zx^rr_%y{X(17=vSs06rGB+1E)q+w2*ZmV04u&=j_ghy(K^rUehHh%Xor{NQb#uX^z zn}|bE_qjl|6dDHT4phIgM9uJL;OO}4D=hfy3J?78GW{pO@v9uTZ~srg5n!0FjzL}g zk5?G&Q@|cwgXY%Z7m#>GXDCPk0#7}4pB3fYWvY0B5OzU$ zzTR+w0`4^_m0QA(qjK_4H9>$|e;C~z*4mC|rOB%ZYn63X@-}?c)P&>kcYd)af~9s# zZCJ(J9~2Qz(Q#pK=ZlOdwg)dRnE@r61?zrYYK6`ByZ63V)Qo&BZvX(pEyC-XL4-R2 z)KA%ynlOy@5fmT#^w;l?LO2(@^Af%FJG80S!7W z>24?jGuxzmjD?exn4O&Z@QzS{+s3CrfW&JYA&n2Z*UkavZB!WRwJ;4LXF)5wDo(?C zQz>N#%aq)Hkyy+Z$(8Y5dZVaXQ!TU*wcx1PG!qKl-Ap@~eOYO#bkU?b>v)`ZSeIKK zl7&XxmgK=FWCUfep^BvLL&--y&z4@kzqLMzVGA@JGt-$kiXQKv55)b7D@>?|@LFvr zTn%Qm^sMtUM8@gHA*B~M5FWr=Ta>N3<8)pUV;PW|-w;no>u)56`6D4dD5tS{Z{NM$ zm|1WC2W2(Wks@%*`pPn8%U!leH78(pAKT)fY4j}lQIOa@ArvM9mV( zLeyJHOO8;IQ<43x<4G3i9{n(5;m{qV`1_?@12Bsg=_>N!6-H(Z7_b!HlIm5UO!Ov3 zb5VEr}7$* zBRx3qk{GG*oZU?ilZSwVgN~c_&`L5evBWScHVKZ?eLNepm7qHR9HXy~LtqqKkIq|M znO-a!gN$1Ifld9r=&nL6Qtia?4;&|Wns;yvQk<(+{P|O`@_D$@X`<+>$=?-*+RCg| zYhRH}DO0E^5+U7BLvQVBf#-t-vRi7YRLj8F&XdvR=P7UUwxH%X@@C2ns@IP#^4)0n zzIn+<`Z~C1(Jve}dz_&eBeImmniE!Yfx_Mvi-+jLK~C0Q!|kL$sGCfsq_Cetp9-Sx zV_p~2`EN=$n zYcy_;d_#g7xi~W|{^HVF)1WH$UPo_8OxQJ(4mVhH8A)K#%tC$4d@yT|+vQS=J3wwB z4y+lB3C00QI1=Dlxj&7WUYMfDA{RNI<}jq1VId@i2g%iAJTWH5cV4`i2MKz1{yr)q z7AqzDW*E=!jqJZe{^ug(?+rAK2QLMyvKMmC1bEPe@Jv#UpyLupdp0iG6G|XU7N^$j zFucFqo;*mUr>TYy&0sQcFQZL5+X_e%k~+8R`Pj5#=}VA<1kC#RG$M|%6~^?c{Ts(+ z%8eeV=J(-yaQxNk?s$y5FbH=K_b@+Ee((fm_N^{3C6R!~g1sgB<6GM30;0HsoT?e` zKJ7t1v_8&zMAH^8@Rfp)zs4M1I!KPOu18|cVbc~U%0G57Cs~7%p!gtK8~MJrh0|I? zel+la{RnL38@mtgzjz3Q^)W@shMI0)Cf+H%g&c$DG$uy?L83n97w$q6RzWQ--DF7n z$#Y$FU`}^vRL%F#1nsTO2iiZp1ynWravOo-q2H=S*g@>)9;08cp`HGS3%*rfBntIa zdjR{GUBP!A4Vs#wAKXh+UHz=Aguk4WsLCBMZG>D<@ayPTw<65wh7_R}loQe2FO#{Q z9Vx?y?ro_=IR4cor#IA?&vBhG=>v{p>vzC0Erf(E6{Y3%R-l~EYqUjlVC{w@NQ--A z+R{MgES8h4c1cg@$1|n!UE5I0Eyo632#(zg;HyfCZMBu68s8$4>F=}97|u=s z>Sb4(m6g!kRfJHofzN*mcv~|Lm>8&5HlQDAUSKN*I}%9i#(_)SB~cHW2*X(ajZU&S zYsDM#73e7WGWR`zy>s#CzP4lT3_Vkg4u>Cdaq2l$VKv&fg1#7a-{NSBa?9~{-T;fG z6@!=aV{dg|YWf~Vl|$E~HET{UJP{HP5h~%g>L$>=XC?kae!}3>$=1pH$&m~+;7@nc zjw04t2-H&|T;v-m2;Uz}b>+l@5q=}Ow7Gr?kMohB_V2zy2_Pm@QShnF(`MphKO#8u zc}aHqyB+Etk|~sh2j)D&3AMyE*^r}JElDqoP^jv4y4*sF@8Pvj^izpHf#5p*rcuPO zIOFsmC?*d?L9zhE3X1otjB5+kyCB8VPY+71JZ3)qt!K2Kd7eraz+;0=k@nh7K6ru3 z{@uy)&wDHU`By$6JRz9PTT0D0z_gN8VzP(7WC zR;{hYX0>dP5l<3baYDBR4wE<#ExA{9J_m`ge@vw9MK(AxR823Au*EcL?a<=w;Hk$@dE3 zM3gdvrTa~b;GLsmH4c~$OIyy>eXM6&&aGd{YUhqhH>S_l3x({mE6>W8+Ef$WRQeef zXSJ3%zqjkzfK(gB;)0#WDP5IH8;MQxAC_-FA6gsg(Xs@y1C$4a3*V)E%${^m_o}*F zn6bX^^_Se07=@l?|7vG|;xBjq4wiO(F5RhdhRK1l@Qwm#JiKc>P-Uf6*Y=UCcr&bA zG?8{GV7&wuvc=vXa6+fR&%&^LiQAIzw{(fiY_iTnHG=*^z7S>ft%{vYlDP4Vo36On zQb~Wm;SVoKNfT}M+EQ{3UF@C+oT4YJ3l93cq!2(tC~&Q$--^YD6W0T691o zfm47~tF#$1jQ@fqvYLPe|=Ho?~?l!i!SYy>Ba`pkjj+31!0^dFQl-S4d^enK`izpZ?BiWyY9sX&4{W$WfYtI-fhX;%AOi_iH`2{S)$UJf-c= z=bzH{={2F})=B^0MbbTF$cYT*mge>&3aC`CN4(}bGGMW&j;PynSH!+6E$caUlJyK{ zK;30I8c95)rZ=3pGYwm;T@&BPGE;mYyqksFQdDKYiL}IMC=&B$?*Sh925kcG*}nsi z9I~JER$`+bTv?=7yiv&(zXPYt5*ma==%%Xyrs}R#*0#xEYkgOK{JU?{@**Uy<&6$>huWn zk9W`yHA0kol?X{`?5LMKAC^TcU%c6&rpD=*a=VZ(DngKbytBWDfgnG`0)B~yda+}} z3FCbZr`#~tT_}1(pO_^gk^>umOm!@gk~=*IWwP$Afn`Phd2g>EUBn0Mn=2vKB0=Nv zGzxTz5BoJm#eF%>bd<}`ZVBnyd8!WQvoS{VBKdHXVGDIl{M9juvsNzBZ6Sc@T8^{4 zdF5#=)5UN|^fn@>&X>HnT`baB%4z5L{&eKJb7Z8H{AnlI@ncH?Y$$z^fkNLTU

B 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 uyTPRlZbvN{EMk`#7O9&#GbzR9iJjHvfnMR%|T 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 literal 0 HcmV?d00001 diff --git a/testing/btest/core/erspan.bro b/testing/btest/core/erspan.bro new file mode 100644 index 0000000000..eb05cdcf5a --- /dev/null +++ b/testing/btest/core/erspan.bro @@ -0,0 +1,4 @@ +# @TEST-EXEC: bro -C -b -r $TRACES/erspan.trace %INPUT +# @TEST-EXEC: btest-diff tunnel.log + +@load base/frameworks/tunnels From d32e4b25f1d0cea8504dba55c7d321bc29cf18ef Mon Sep 17 00:00:00 2001 From: Seth Hall Date: Fri, 3 Feb 2017 12:34:39 -0800 Subject: [PATCH 05/44] Small change to avoid potentially over reading memory. --- src/Sessions.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Sessions.cc b/src/Sessions.cc index ad82f1c736..4ca5235a2c 100644 --- a/src/Sessions.cc +++ b/src/Sessions.cc @@ -508,8 +508,11 @@ void NetSessions::DoNextPacket(double t, const Packet* pkt, const IP_Hdr* ip_hdr uint16 flags_ver = ntohs(*((uint16*)(data + 0))); uint16 proto_typ = ntohs(*((uint16*)(data + 2))); int gre_version = flags_ver & 0x0007; + // If a carried packet has ethernet, this will help skip it. unsigned int eth_len = 0; + unsigned int gre_len = gre_header_len(flags_ver); + unsigned int ppp_len = gre_version == 1 ? 1 : 0; if ( gre_version != 0 && gre_version != 1 ) { @@ -520,11 +523,11 @@ void NetSessions::DoNextPacket(double t, const Packet* pkt, const IP_Hdr* ip_hdr if ( gre_version == 0 ) { - if ( proto_typ == 0x6558 ) + if ( proto_typ == 0x6558 && len > gre_len + 14 ) { // transparent ethernet bridging eth_len = 14; - proto_typ = ntohs(*((uint16*)(data + gre_header_len(flags_ver) + 12))); + proto_typ = ntohs(*((uint16*)(data + gre_len + 12))); } if ( proto_typ == 0x0800 ) @@ -567,9 +570,6 @@ void NetSessions::DoNextPacket(double t, const Packet* pkt, const IP_Hdr* ip_hdr return; } - unsigned int gre_len = gre_header_len(flags_ver); - unsigned int ppp_len = gre_version == 1 ? 1 : 0; - if ( len < gre_len + ppp_len + eth_len || caplen < gre_len + ppp_len + eth_len ) { Weird("truncated_GRE", ip_hdr, encapsulation); From c857f5c4dd2195c80b8bb2f6f20db184e499da2e Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Mon, 6 Feb 2017 23:30:54 -0600 Subject: [PATCH 06/44] BIT-1785: fix scripts able to access uninitialized variables. --- src/Frame.cc | 9 +++++++ src/Frame.h | 1 + src/Func.cc | 1 + .../language.uninitialized-local2/out | 2 ++ .../btest/language/uninitialized-local2.bro | 25 +++++++++++++++++++ 5 files changed, 38 insertions(+) create mode 100644 testing/btest/Baseline/language.uninitialized-local2/out create mode 100644 testing/btest/language/uninitialized-local2.bro diff --git a/src/Frame.cc b/src/Frame.cc index e97b948dbe..f30312aaec 100644 --- a/src/Frame.cc +++ b/src/Frame.cc @@ -33,6 +33,15 @@ Frame::~Frame() Release(); } +void Frame::Reset(int startIdx) + { + for ( int i = startIdx; i < size; ++i ) + { + Unref(frame[i]); + frame[i] = 0; + } + } + void Frame::Release() { for ( int i = 0; i < size; ++i ) diff --git a/src/Frame.h b/src/Frame.h index 85e1dbec2e..0c22fa0e4e 100644 --- a/src/Frame.h +++ b/src/Frame.h @@ -24,6 +24,7 @@ public: frame[n] = v; } + void Reset(int startIdx); void Release(); void Describe(ODesc* d) const; diff --git a/src/Func.cc b/src/Func.cc index ccb2570f70..88da9a7a04 100644 --- a/src/Func.cc +++ b/src/Func.cc @@ -397,6 +397,7 @@ Val* BroFunc::Call(val_list* args, Frame* parent) const bodies[i].stmts->GetLocationInfo()); Unref(result); + f->Reset(args->length()); try { diff --git a/testing/btest/Baseline/language.uninitialized-local2/out b/testing/btest/Baseline/language.uninitialized-local2/out new file mode 100644 index 0000000000..75d09294e6 --- /dev/null +++ b/testing/btest/Baseline/language.uninitialized-local2/out @@ -0,0 +1,2 @@ +error in /home/jon/projects/bro/bro/testing/btest/.tmp/language.uninitialized-local2/uninitialized-local2.bro, line 19: value used but not set (var_b) +var_a is, baz diff --git a/testing/btest/language/uninitialized-local2.bro b/testing/btest/language/uninitialized-local2.bro new file mode 100644 index 0000000000..f11a5fda10 --- /dev/null +++ b/testing/btest/language/uninitialized-local2.bro @@ -0,0 +1,25 @@ +# @TEST-EXEC: bro -b %INPUT >out 2>&1 +# @TEST-EXEC: TEST_DIFF_CANONIFIER=$SCRIPTS/diff-remove-abspath btest-diff out + +event test() + { + local var_a: string = "foo"; + } + +event test() + { + if ( F ) + { + local var_b: string = "bar"; + } + + local var_a: string = "baz"; + + print "var_a is", var_a; + print "var_b is", var_b; + } + +event bro_init() + { + event test(); + } From 209a560cc68cc197546c2449651de99a21eddd1e Mon Sep 17 00:00:00 2001 From: Jan Grashoefer Date: Thu, 9 Feb 2017 19:36:05 +0100 Subject: [PATCH 07/44] Fixed intel expiration reset. Reinserting the same indicator did not reset the expiration timer for the indicator in the underlying data store. Addresses BIT-1790 --- scripts/base/frameworks/intel/main.bro | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/scripts/base/frameworks/intel/main.bro b/scripts/base/frameworks/intel/main.bro index aa51af5ee0..30f9a6bf75 100644 --- a/scripts/base/frameworks/intel/main.bro +++ b/scripts/base/frameworks/intel/main.bro @@ -407,7 +407,11 @@ function insert(item: Item) if ( host !in data_store$host_data ) data_store$host_data[host] = table(); else + { is_new = F; + # Reset expiration timer. + data_store$host_data[host] = data_store$host_data[host]; + } meta_tbl = data_store$host_data[host]; } @@ -422,7 +426,11 @@ function insert(item: Item) if ( !check_subnet(net, data_store$subnet_data) ) data_store$subnet_data[net] = table(); else + { is_new = F; + # Reset expiration timer. + data_store$subnet_data[net] = data_store$subnet_data[net]; + } meta_tbl = data_store$subnet_data[net]; } @@ -436,7 +444,12 @@ function insert(item: Item) if ( [lower_indicator, item$indicator_type] !in data_store$string_data ) data_store$string_data[lower_indicator, item$indicator_type] = table(); else + { is_new = F; + # Reset expiration timer. + data_store$string_data[lower_indicator, item$indicator_type] = + data_store$string_data[lower_indicator, item$indicator_type]; + } meta_tbl = data_store$string_data[lower_indicator, item$indicator_type]; } From c6b16ad2ca9b3c1b7f561a711d8a62156f708884 Mon Sep 17 00:00:00 2001 From: Jan Grashoefer Date: Thu, 9 Feb 2017 19:40:25 +0100 Subject: [PATCH 08/44] Updated expiration test case to cover reinsertion. Addresses BIT-1790 --- .../output | 25 +++++++++++++------ .../base/frameworks/intel/expire-item.bro | 19 +++++++++++++- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/testing/btest/Baseline/scripts.base.frameworks.intel.expire-item/output b/testing/btest/Baseline/scripts.base.frameworks.intel.expire-item/output index dfa922f88f..a01d119b81 100644 --- a/testing/btest/Baseline/scripts.base.frameworks.intel.expire-item/output +++ b/testing/btest/Baseline/scripts.base.frameworks.intel.expire-item/output @@ -3,20 +3,31 @@ #empty_field (empty) #unset_field - #path intel -#open 2016-06-15-19-11-06 +#open 2017-02-09-18-29-44 #fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p seen.indicator seen.indicator_type seen.where seen.node matched sources fuid file_mime_type file_desc #types time string addr port addr port string enum enum string set[enum] set[string] string string string -1466017866.348490 - - - - - 1.2.3.4 Intel::ADDR SOMEWHERE bro Intel::ADDR source1 - - - -1466017867.349583 - - - - - 1.2.3.4 Intel::ADDR SOMEWHERE bro Intel::ADDR source1 - - - -1466017868.349656 - - - - - 1.2.3.4 Intel::ADDR SOMEWHERE bro Intel::ADDR source1 - - - -#close 2016-06-15-19-11-12 +1486664984.510411 - - - - - 1.2.3.4 Intel::ADDR SOMEWHERE bro Intel::ADDR source1 - - - +1486664987.510937 - - - - - 1.2.3.4 Intel::ADDR SOMEWHERE bro Intel::ADDR source1 - - - +1486664990.511265 - - - - - 1.2.3.4 Intel::ADDR SOMEWHERE bro Intel::ADDR source1,source2 - - - +1486664993.512024 - - - - - 1.2.3.4 Intel::ADDR SOMEWHERE bro Intel::ADDR source1,source2 - - - +1486664996.512265 - - - - - 1.2.3.4 Intel::ADDR SOMEWHERE bro Intel::ADDR source1,source2 - - - +#close 2017-02-09-18-30-03 +-- Run 1 -- Trigger: 1.2.3.4 Seen: 1.2.3.4 +-- Run 2 -- +Trigger: 1.2.3.4 +Reinsert: 1.2.3.4 +Seen: 1.2.3.4 +-- Run 3 -- Trigger: 1.2.3.4 Seen: 1.2.3.4 +-- Run 4 -- +Trigger: 1.2.3.4 +Seen: 1.2.3.4 +-- Run 5 -- Trigger: 1.2.3.4 Seen: 1.2.3.4 Expired: 1.2.3.4 -Trigger: 1.2.3.4 -Trigger: 1.2.3.4 +-- Run 6 -- Trigger: 1.2.3.4 diff --git a/testing/btest/scripts/base/frameworks/intel/expire-item.bro b/testing/btest/scripts/base/frameworks/intel/expire-item.bro index df9170b669..dd915b3a03 100644 --- a/testing/btest/scripts/base/frameworks/intel/expire-item.bro +++ b/testing/btest/scripts/base/frameworks/intel/expire-item.bro @@ -20,11 +20,28 @@ redef table_expire_interval = 3sec; global runs = 0; event do_it() { + ++runs; + print fmt("-- Run %s --", runs); + print "Trigger: 1.2.3.4"; Intel::seen([$host=1.2.3.4, $where=SOMEWHERE]); - ++runs; + if ( runs == 2 ) + { + # Reinserting the indicator should reset the expiration + print "Reinsert: 1.2.3.4"; + local item = [ + $indicator="1.2.3.4", + $indicator_type=Intel::ADDR, + $meta=[ + $source="source2", + $desc="this host is still bad", + $url="http://some-data-distributor.com/2"] + ]; + Intel::insert(item); + } + if ( runs < 6 ) schedule 3sec { do_it() }; } From a5e9a535a5d2095e3409b238a8dc2299da5cd508 Mon Sep 17 00:00:00 2001 From: Robin Sommer Date: Tue, 31 Jan 2017 21:51:31 -0800 Subject: [PATCH 09/44] Changing semantics of Broker's remote logging to match old communication framework. Broker had changed the semantics of remote logging: it sent over the original Bro record containing the values to be logged, which on the receiving side would then pass through the logging framework normally, including triggering filters and events. The old communication system however special-cases logs: it sends already processed log entries, just as they go into the log files, and without any receiver-side filtering etc. This more efficient as it short-cuts the processing path, and also avoids the more expensive Val serialization. It also lets the sender determine the specifics of what gets logged (and how). This commit changes Broker over to now use the same semantics as the old communication system. TODOs: - The new Broker code doesn't have consistent #ifdefs yet. - Right now, when a new log receiver connects, all existing logs are broadcasted out again to all current clients. That doesn't so any harm, but is unncessary. Need to add a way to send the existing logs to just the new client. --- aux/binpac | 2 +- aux/bro-aux | 2 +- aux/broccoli | 2 +- aux/broctl | 2 +- aux/broker | 2 +- aux/btest | 2 +- aux/plugins | 2 +- src/RemoteSerializer.cc | 5 +- src/broker/Data.cc | 430 ++++++++++++++++++ src/broker/Data.h | 30 ++ src/broker/Manager.cc | 357 +++++++++++---- src/broker/Manager.h | 34 +- src/logging/Manager.cc | 65 ++- src/logging/Manager.h | 50 +- src/logging/WriterBackend.cc | 57 +++ src/logging/WriterBackend.h | 11 +- src/logging/WriterFrontend.cc | 27 +- src/logging/WriterFrontend.h | 8 +- .../Baseline/broker.remote_log/recv.recv.out | 6 - .../broker.remote_log_types/recv.recv.out | 0 .../broker.remote_log_types/recv.test.log | 10 + .../broker.remote_log_types/send.send.out | 1 + .../broker.remote_log_types/send.test.log | 10 + testing/btest/broker/remote_log.test | 89 ++-- testing/btest/broker/remote_log_types.test | 133 ++++++ 25 files changed, 1178 insertions(+), 159 deletions(-) create mode 100644 testing/btest/Baseline/broker.remote_log_types/recv.recv.out create mode 100644 testing/btest/Baseline/broker.remote_log_types/recv.test.log create mode 100644 testing/btest/Baseline/broker.remote_log_types/send.send.out create mode 100644 testing/btest/Baseline/broker.remote_log_types/send.test.log create mode 100644 testing/btest/broker/remote_log_types.test diff --git a/aux/binpac b/aux/binpac index 0f1ecfa972..a0990e61ad 160000 --- a/aux/binpac +++ b/aux/binpac @@ -1 +1 @@ -Subproject commit 0f1ecfa97236635fb93e013404e6b30d6c506ddd +Subproject commit a0990e61ad4a3705bda4cc5a20059af2d1bda4c3 diff --git a/aux/bro-aux b/aux/bro-aux index b1e75f6a21..7660b5f4c5 160000 --- a/aux/bro-aux +++ b/aux/bro-aux @@ -1 +1 @@ -Subproject commit b1e75f6a212250b1730a438f27fc778618b67ec3 +Subproject commit 7660b5f4c5be40aa5f3a7c8746fdcf68331f9b93 diff --git a/aux/broccoli b/aux/broccoli index ed52e3414b..765eab50f7 160000 --- a/aux/broccoli +++ b/aux/broccoli @@ -1 +1 @@ -Subproject commit ed52e3414b31b05ec9abed627b4153c8e2243441 +Subproject commit 765eab50f7796fdb3c308fe9232cd7891f098c67 diff --git a/aux/broctl b/aux/broctl index 73dbc79ac2..f6d451520e 160000 --- a/aux/broctl +++ b/aux/broctl @@ -1 +1 @@ -Subproject commit 73dbc79ac24cdfef07d8574a4da5d43056ba5fa5 +Subproject commit f6d451520eaaaae97aab6df2bb4e0aecb6b63e66 diff --git a/aux/broker b/aux/broker index 23def70c44..68a36ed814 160000 --- a/aux/broker +++ b/aux/broker @@ -1 +1 @@ -Subproject commit 23def70c44128d19138029615dd154359286e111 +Subproject commit 68a36ed81480ba935268bcaf7b6f2249d23436da diff --git a/aux/btest b/aux/btest index 9d5c7bcac9..32e582514a 160000 --- a/aux/btest +++ b/aux/btest @@ -1 +1 @@ -Subproject commit 9d5c7bcac9b04710931bc8a42b545f0691561b2f +Subproject commit 32e582514ae044befa8e0511083bf11a51408a1d diff --git a/aux/plugins b/aux/plugins index 2322840bcd..0a2f021527 160000 --- a/aux/plugins +++ b/aux/plugins @@ -1 +1 @@ -Subproject commit 2322840bcdbd618ae7bd24e22d874fb30ab89bbb +Subproject commit 0a2f0215270e6ceaf9c1312f705b95d2cce1b530 diff --git a/src/RemoteSerializer.cc b/src/RemoteSerializer.cc index 4842f819b6..2adecfc89a 100644 --- a/src/RemoteSerializer.cc +++ b/src/RemoteSerializer.cc @@ -2730,8 +2730,7 @@ bool RemoteSerializer::ProcessLogCreateWriter() id_val = new EnumVal(id, internal_type("Log::ID")->AsEnumType()); writer_val = new EnumVal(writer, internal_type("Log::Writer")->AsEnumType()); - if ( ! log_mgr->CreateWriter(id_val, writer_val, info, num_fields, fields, - true, false, true) ) + if ( ! log_mgr->CreateWriterForRemoteLog(id_val, writer_val, info, num_fields, fields) ) { delete_fields_up_to = num_fields; goto error; @@ -2803,7 +2802,7 @@ bool RemoteSerializer::ProcessLogWrite() id_val = new EnumVal(id, internal_type("Log::ID")->AsEnumType()); writer_val = new EnumVal(writer, internal_type("Log::Writer")->AsEnumType()); - success = log_mgr->Write(id_val, writer_val, path, num_fields, vals); + success = log_mgr->WriteFromRemote(id_val, writer_val, path, num_fields, vals); Unref(id_val); Unref(writer_val); diff --git a/src/broker/Data.cc b/src/broker/Data.cc index bc4197a974..35d815b9e8 100644 --- a/src/broker/Data.cc +++ b/src/broker/Data.cc @@ -709,3 +709,433 @@ bool bro_broker::DataVal::DoUnserialize(UnserialInfo* info) delete [] serial; return true; } + +static broker::util::optional threading_val_to_data_internal(TypeTag type, const threading::Value::_val& val) + { + switch ( type ) { + case TYPE_BOOL: + return {val.int_val != 0}; + + case TYPE_INT: + return {val.int_val}; + + case TYPE_COUNT: + case TYPE_COUNTER: + return {val.uint_val}; + + case TYPE_PORT: + return {broker::port(val.port_val.port, to_broker_port_proto(val.port_val.proto))}; + + case TYPE_ADDR: + { + IPAddr a; + + switch ( val.addr_val.family ) { + case IPv4: + a = IPAddr(val.addr_val.in.in4); + break; + + case IPv6: + a = IPAddr(val.addr_val.in.in6); + break; + + default: + reporter->InternalError("unsupported protocol family in threading_val_to_data"); + } + + in6_addr tmp; + a.CopyIPv6(&tmp); + return {broker::address(reinterpret_cast(&tmp), + broker::address::family::ipv6, + broker::address::byte_order::network)}; + } + + case TYPE_SUBNET: + { + IPAddr a; + int length; + + switch ( val.subnet_val.prefix.family ) { + case IPv4: + a = IPAddr(val.subnet_val.prefix.in.in4); + length = (val.subnet_val.length - 96); + break; + + case IPv6: + a = IPAddr(val.subnet_val.prefix.in.in6); + length = val.subnet_val.length; + break; + + default: + reporter->InternalError("unsupported protocol family in threading_val_to_data"); + } + + in6_addr tmp; + a.CopyIPv6(&tmp); + + auto s = broker::address(reinterpret_cast(&tmp), + broker::address::family::ipv6, + broker::address::byte_order::network); + fprintf(stderr, "%d\n", val.subnet_val.length); + return {broker::subnet(s, length)}; + } + + case TYPE_DOUBLE: + return {val.double_val}; + + case TYPE_TIME: + return {broker::time_point(val.double_val)}; + + case TYPE_INTERVAL: + return {broker::time_duration(val.double_val)}; + + case TYPE_ENUM: + return {broker::enum_value(std::string(val.string_val.data, val.string_val.length))}; + + case TYPE_STRING: + case TYPE_FILE: + case TYPE_FUNC: + return {std::string(val.string_val.data, val.string_val.length)}; + + case TYPE_TABLE: + { + auto s = broker::set(); + + for ( int i = 0; i < val.set_val.size; ++i ) + { + auto c = bro_broker::threading_val_to_data(val.set_val.vals[i]); + + if ( ! c ) + return {}; + + s.emplace(*c); + } + + return {move(s)}; + } + + case TYPE_VECTOR: + { + auto s = broker::vector(); + + for ( int i = 0; i < val.vector_val.size; ++i ) + { + auto c = bro_broker::threading_val_to_data(val.vector_val.vals[i]); + + if ( ! c ) + return {}; + + s.emplace_back(*c); + } + + return {move(s)}; + } + + default: + reporter->InternalError("unsupported type %s in threading_val_to_data", + type_name(type)); + } + + return {}; + } + + +broker::util::optional bro_broker::threading_val_to_data(const threading::Value* v) + { + broker::util::optional d; + + if ( v->present ) + { + d = threading_val_to_data_internal(v->type, v->val); + + if ( ! d ) + return {}; + } + + auto type = broker::record::field(static_cast(v->type)); + auto present = broker::record::field(v->present); + auto data = (v->present) ? broker::record::field(*d) : broker::util::optional(); + + return {broker::record({move(type), move(present), move(data)})}; + }; + +struct threading_val_converter { + using result_type = bool; + + TypeTag type; + threading::Value::_val& val; + + result_type operator()(bool a) + { + if ( type == TYPE_BOOL ) + { + val.int_val = (a ? 1 : 0); + return true; + } + + return false; + } + + result_type operator()(uint64_t a) + { + if ( type == TYPE_COUNT || type == TYPE_COUNTER ) + { + val.uint_val = a; + return true; + } + + return false; + } + + result_type operator()(int64_t a) + { + if ( type == TYPE_INT ) + { + val.int_val = a; + return true; + } + + return false; + } + + result_type operator()(double a) + { + if ( type == TYPE_DOUBLE ) + { + val.double_val = a; + return true; + } + + return false; + } + + + result_type operator()(const std::string& a) + { + if ( type == TYPE_STRING || type == TYPE_FILE || type == TYPE_FUNC ) + { + auto n = a.size(); + val.string_val.length = n; + val.string_val.data = new char[n]; + memcpy(val.string_val.data, a.data(), n); + return true; + } + + return false; + } + + result_type operator()(const broker::address& a) + { + if ( type == TYPE_ADDR ) + { + auto bits = reinterpret_cast(&a.bytes()); + auto b = IPAddr(*bits); + + if ( a.is_v4() ) + { + val.addr_val.family = IPv4; + b.CopyIPv4(&val.addr_val.in.in4); + return true; + } + + if ( a.is_v6() ) + { + val.addr_val.family = IPv6; + b.CopyIPv6(&val.addr_val.in.in6); + return true; + } + } + + return false; + } + + result_type operator()(const broker::subnet& s) + { + if ( type == TYPE_SUBNET ) + { + auto bits = reinterpret_cast(&s.network().bytes()); + auto a = IPAddr(*bits); + + val.subnet_val.length = s.length(); + + if ( s.network().is_v4() ) + { + val.subnet_val.prefix.family = IPv4; + a.CopyIPv4(&val.subnet_val.prefix.in.in4); + val.subnet_val.length += 96; + return true; + } + + if ( s.network().is_v6() ) + { + val.subnet_val.prefix.family = IPv6; + a.CopyIPv6(&val.subnet_val.prefix.in.in6); + return true; + } + } + + return false; + } + + result_type operator()(const broker::port& a) + { + if ( type == TYPE_PORT ) + { + val.port_val.port = a.number(); + val.port_val.proto = bro_broker::to_bro_port_proto(a.type()); + return true; + } + + return false; + } + + result_type operator()(const broker::time_point& a) + { + if ( type == TYPE_TIME ) + { + val.double_val = a.value; + return true; + } + + return false; + } + + result_type operator()(const broker::time_duration& a) + { + if ( type == TYPE_INTERVAL ) + { + val.double_val = a.value; + return true; + } + + return false; + } + + result_type operator()(const broker::enum_value& a) + { + if ( type == TYPE_ENUM ) + { + auto n = a.name.size(); + val.string_val.length = n; + val.string_val.data = new char[n]; + memcpy(val.string_val.data, a.name.data(), n); + return true; + } + + return false; + } + + result_type operator()(const broker::set& a) + { + if ( type == TYPE_TABLE ) + { + val.set_val.size = a.size(); + val.set_val.vals = new threading::Value* [val.set_val.size]; + + auto p = val.set_val.vals; + + for ( auto& i : a ) + *p++ = bro_broker::data_to_threading_val(move(i)); + + return true; + } + + return false; + } + + result_type operator()(const broker::table& a) + { + return false; + } + + result_type operator()(const broker::vector& a) + { + if ( type == TYPE_VECTOR ) + { + val.vector_val.size = a.size(); + val.vector_val.vals = new threading::Value* [val.set_val.size]; + + auto p = val.set_val.vals; + + for ( auto& i : a ) + *p++ = bro_broker::data_to_threading_val(move(i)); + + return true; + } + + return false; + } + + result_type operator()(const broker::record& a) + { + return false; + } +}; + +threading::Value* bro_broker::data_to_threading_val(broker::data d) + { + auto r = broker::get(d); + + if ( ! r ) + return nullptr; + + auto type = broker::get(*r->get(0));; + auto present = broker::get(*r->get(1));; + auto data = *r->get(2); + + if ( ! (type && present) ) + return nullptr; + + auto tv = new threading::Value; + tv->type = static_cast(*type); + tv->present = *present; + + if ( present && ! broker::visit(threading_val_converter{tv->type, tv->val}, data) ) + { + delete tv; + return nullptr; + } + + return tv; + } + +broker::data bro_broker::threading_field_to_data(const threading::Field* f) + { + auto name = broker::record::field(f->name); + auto type = broker::record::field(static_cast(f->type)); + auto subtype = broker::record::field(static_cast(f->subtype)); + auto optional = broker::record::field(f->optional); + + broker::util::optional secondary; + + if ( f->secondary_name ) + secondary = {f->secondary_name}; + + return move(broker::record({name, secondary, type, subtype, optional})); + } + +threading::Field* bro_broker::data_to_threading_field(broker::data d) + { + auto r = broker::get(d); + + if ( ! r ) + return nullptr; + + auto name = broker::get(*r->get(0)); + auto secondary = r->get(1); + auto type = broker::get(*r->get(2)); + auto subtype = broker::get(*r->get(3)); + auto optional = broker::get(*r->get(4)); + + if ( ! (name && type && subtype && optional) ) + return nullptr; + + if ( secondary && ! broker::is(*secondary) ) + return nullptr; + + return new threading::Field(name->c_str(), + secondary ? broker::get(*secondary)->c_str() : nullptr, + static_cast(*type), + static_cast(*subtype), + *optional); + } diff --git a/src/broker/Data.h b/src/broker/Data.h index 0045ad58ad..9e0c8120de 100644 --- a/src/broker/Data.h +++ b/src/broker/Data.h @@ -61,6 +61,36 @@ broker::util::optional val_to_data(Val* v); */ Val* data_to_val(broker::data d, BroType* type, bool require_log_attr = false); +/** + * Convert a Bro threading::Value to a Broker data value. + * @param v a Bro threading::Value. + * @return a Broker data value if the Bro threading::Value could be converted to one. + */ +broker::util::optional threading_val_to_data(const threading::Value* v); + +/** + * Convert a Bro threading::Field to a Broker data value. + * @param v a Bro threading::Field. + * @return a Broker data value if the Bro threading::Field could be converted to one. + */ +broker::data threading_field_to_data(const threading::Field* f); + +/** + * Convert a Broker data value to a Bro threading::Value. + * @param d a Broker data value. + * @return a pointer to a new Bro threading::Value or a nullptr if the conversion was not + * possible. + */ +threading::Value* data_to_threading_val(broker::data d); + +/** + * Convert a Broker data value to a Bro threading::Value. + * @param d a Broker data value. + * @return a pointer to a new Bro threading::Value or a nullptr if the conversion was not + * possible. + */ +threading::Field* data_to_threading_field(broker::data d); + /** * A Bro value which wraps a Broker data value. */ diff --git a/src/broker/Manager.cc b/src/broker/Manager.cc index 334b7f84f5..76040b129c 100644 --- a/src/broker/Manager.cc +++ b/src/broker/Manager.cc @@ -20,10 +20,17 @@ using namespace std; VectorType* bro_broker::Manager::vector_of_data_type; EnumType* bro_broker::Manager::log_id_type; +EnumType* bro_broker::Manager::writer_id_type; int bro_broker::Manager::send_flags_self_idx; int bro_broker::Manager::send_flags_peers_idx; int bro_broker::Manager::send_flags_unsolicited_idx; +struct unref_guard { + unref_guard(Val* v) : val(v) {} + ~unref_guard() { Unref(val); } + Val* val; +}; + bro_broker::Manager::Manager() : iosource::IOSource(), next_timestamp(-1) { @@ -83,6 +90,7 @@ bool bro_broker::Manager::Enable(Val* broker_endpoint_flags) send_flags_unsolicited_idx = require_field(send_flags_type, "unsolicited"); log_id_type = internal_type("Log::ID")->AsEnumType(); + writer_id_type = internal_type("Log::Writer")->AsEnumType(); bro_broker::opaque_of_data_type = new OpaqueType("Broker::Data"); bro_broker::opaque_of_set_iterator = new OpaqueType("Broker::SetIterator"); @@ -206,8 +214,9 @@ bool bro_broker::Manager::Event(std::string topic, broker::message msg, int flag return true; } -bool bro_broker::Manager::Log(EnumVal* stream, RecordVal* columns, RecordType* info, - int flags) +bool bro_broker::Manager::CreateLog(EnumVal* stream, EnumVal* writer, const logging::WriterBackend::WriterInfo& info, + int num_fields, const threading::Field* const * fields, int flags, + const string& peer) { if ( ! Enabled() ) return false; @@ -221,40 +230,82 @@ bool bro_broker::Manager::Log(EnumVal* stream, RecordVal* columns, RecordType* i return false; } - broker::record column_data; + auto writer_name = writer->Type()->AsEnumType()->Lookup(writer->AsEnum()); - for ( auto i = 0u; i < static_cast(info->NumFields()); ++i ) + if ( ! writer_name ) { - if ( ! info->FieldDecl(i)->FindAttr(ATTR_LOG) ) - continue; + reporter->Error("Failed to remotely log: writer %d doesn't have name", + writer->AsEnum()); + return false; + } - auto field_val = columns->LookupWithDefault(i); + auto writer_info = info.ToBroker(); - if ( ! field_val ) - { - column_data.fields.emplace_back(broker::record::field{}); - continue; - } + broker::vector fields_data; - auto opt_field_data = val_to_data(field_val); - Unref(field_val); + for ( auto i = 0; i < num_fields; ++i ) + { + auto field_data = threading_field_to_data(fields[i]); + fields_data.push_back(move(field_data)); + } - if ( ! opt_field_data ) + // TODO: If peer is given, send message to just that one destination. + + std::string topic = std::string("bro/log/") + stream_name; + auto bstream_name = broker::enum_value(move(stream_name)); + auto bwriter_name = broker::enum_value(move(writer_name)); + broker::message msg{move("create"), move(bstream_name), move(bwriter_name), move(writer_info), move(fields_data)}; + endpoint->send(move(topic), move(msg), flags); + + return true; + } + +bool bro_broker::Manager::Log(EnumVal* stream, EnumVal* writer, string path, int num_vals, const threading::Value* const * vals, int flags) + { + if ( ! Enabled() ) + return false; + + auto stream_name = stream->Type()->AsEnumType()->Lookup(stream->AsEnum()); + + if ( ! stream_name ) + { + reporter->Error("Failed to remotely log: stream %d doesn't have name", + stream->AsEnum()); + return false; + } + + auto writer_name = writer->Type()->AsEnumType()->Lookup(writer->AsEnum()); + + if ( ! writer_name ) + { + reporter->Error("Failed to remotely log: writer %d doesn't have name", + writer->AsEnum()); + return false; + } + + broker::vector vals_data; + + for ( auto i = 0; i < num_vals; ++i ) + { + auto field_data = threading_val_to_data(vals[i]); + + if ( ! field_data ) { reporter->Error("Failed to remotely log stream %s: " - "unsupported type '%s'", - stream_name, - type_name(info->FieldDecl(i)->type->Tag())); + "unsupported type for field #%d", + stream_name, i); return false; } - column_data.fields.emplace_back( - broker::record::field{move(*opt_field_data)}); + vals_data.push_back(move(*field_data)); } - broker::message msg{broker::enum_value{stream_name}, move(column_data)}; std::string topic = std::string("bro/log/") + stream_name; + auto bstream_name = broker::enum_value(move(stream_name)); + auto bwriter_name = broker::enum_value(move(writer_name)); + broker::message msg{move("write"), move(bstream_name), move(bwriter_name), move(path), move(vals_data)}; endpoint->send(move(topic), move(msg), flags); + return true; } @@ -639,6 +690,8 @@ void bro_broker::Manager::Process() { switch ( u.status ) { case broker::outgoing_connection_status::tag::established: + log_mgr->SendAllWritersTo(u.peer_name); + if ( Broker::outgoing_connection_established ) { val_list* vl = new val_list; @@ -684,6 +737,8 @@ void bro_broker::Manager::Process() { switch ( u.status ) { case broker::incoming_connection_status::tag::established: + log_mgr->SendAllWritersTo(u.peer_name); + if ( Broker::incoming_connection_established ) { val_list* vl = new val_list; @@ -809,12 +864,6 @@ void bro_broker::Manager::Process() } } - struct unref_guard { - unref_guard(Val* v) : val(v) {} - ~unref_guard() { Unref(val); } - Val* val; - }; - for ( auto& ls : log_subscriptions ) { auto log_messages = ls.second.q.want_pop(); @@ -826,59 +875,19 @@ void bro_broker::Manager::Process() for ( auto& lm : log_messages ) { - if ( lm.size() != 2 ) + if ( lm.size() < 1 ) { - reporter->Warning("got bad remote log size: %zd (expect 2)", - lm.size()); + reporter->Warning("got bad remote log message, no type field"); continue; } - if ( ! broker::get(lm[0]) ) - { - reporter->Warning("got remote log w/o stream id: %d", - static_cast(broker::which(lm[0]))); - continue; - } - - if ( ! broker::get(lm[1]) ) - { - reporter->Warning("got remote log w/o columns: %d", - static_cast(broker::which(lm[1]))); - continue; - } - - auto stream_id = data_to_val(move(lm[0]), log_id_type); - - if ( ! stream_id ) - { - reporter->Warning("failed to unpack remote log stream id"); - continue; - } - - unref_guard stream_id_unreffer{stream_id}; - auto columns_type = log_mgr->StreamColumns(stream_id->AsEnumVal()); - - if ( ! columns_type ) - { - reporter->Warning("got remote log for unknown stream: %s", - stream_id->Type()->AsEnumType()->Lookup( - stream_id->AsEnum())); - continue; - } - - auto columns = data_to_val(move(lm[1]), columns_type, true); - - if ( ! columns ) - { - reporter->Warning("failed to unpack remote log stream columns" - " for stream: %s", - stream_id->Type()->AsEnumType()->Lookup( - stream_id->AsEnum())); - continue; - } - - log_mgr->Write(stream_id->AsEnumVal(), columns->AsRecordVal()); - Unref(columns); + if ( lm[0] == "create" ) + ProcessCreateLog(std::move(lm)); + else if ( lm[0] == "write" ) + ProcessWriteLog(std::move(lm)); + else + reporter->Warning("got remote log w/o known type: %d", + static_cast(broker::which(lm[0]))); } } @@ -979,6 +988,202 @@ void bro_broker::Manager::Process() next_timestamp = -1; } +bool bro_broker::Manager::ProcessCreateLog(broker::message msg) + { + if ( msg.size() != 5 ) + { + reporter->Warning("got bad remote log create size: %zd (expected 5)", + msg.size()); + return false; + } + + unsigned int idx = 1; // Skip type at index 0. + + // Get stream ID. + + if ( ! broker::get(msg[idx]) ) + { + reporter->Warning("got remote log create w/o stream id: %d", + static_cast(broker::which(msg[idx]))); + return false; + } + + auto stream_id = data_to_val(move(msg[idx]), log_id_type); + + if ( ! stream_id ) + { + reporter->Warning("failed to unpack remote log stream id"); + return false; + } + + unref_guard stream_id_unreffer{stream_id}; + ++idx; + + // Get writer ID. + + if ( ! broker::get(msg[idx]) ) + { + reporter->Warning("got remote log w/o writer id: %d", + static_cast(broker::which(msg[idx]))); + return false; + } + + auto writer_id = data_to_val(move(msg[idx]), writer_id_type); + + if ( ! writer_id ) + { + reporter->Warning("failed to unpack remote log writer id"); + return false; + } + + unref_guard writer_id_unreffer{writer_id}; + ++idx; + + // Get writer info. + + if ( ! broker::get(msg[idx]) ) + { + reporter->Warning("got remote log create w/o writer info id: %d", + static_cast(broker::which(msg[idx]))); + return false; + } + + auto writer_info = std::unique_ptr(new logging::WriterBackend::WriterInfo); + + if ( ! writer_info->FromBroker(std::move(msg[idx])) ) + { + reporter->Warning("failed to unpack remote log writer info"); + return false; + } + + ++idx; + + // Get log fields. + + auto fields_data = broker::get(msg[idx]); + + if ( ! fields_data ) + { + reporter->Warning("failed to unpack remote log fields"); + return false; + } + + auto num_fields = fields_data->size(); + auto fields = new threading::Field* [num_fields]; + + for ( auto i = 0u; i < num_fields; ++i ) + { + if ( auto field = data_to_threading_field((*fields_data)[i]) ) + fields[i] = field; + else + { + reporter->Warning("failed to convert remote log field # %d", i); + return false; + } + } + + if ( ! log_mgr->CreateWriterForRemoteLog(stream_id->AsEnumVal(), writer_id->AsEnumVal(), writer_info.get(), num_fields, fields) ) + { + ODesc d; + stream_id->Describe(&d); + reporter->Warning("failed to create remote log stream for %s locally", d.Description()); + } + + writer_info.release(); // log_mgr took ownership. + return true; + } + +bool bro_broker::Manager::ProcessWriteLog(broker::message msg) + { + if ( msg.size() != 5 ) + { + reporter->Warning("got bad remote log size: %zd (expected 5)", + msg.size()); + return false; + } + + unsigned int idx = 1; // Skip type at index 0. + + // Get stream ID. + + if ( ! broker::get(msg[idx]) ) + { + reporter->Warning("got remote log w/o stream id: %d", + static_cast(broker::which(msg[idx]))); + return false; + } + + auto stream_id = data_to_val(move(msg[idx]), log_id_type); + + if ( ! stream_id ) + { + reporter->Warning("failed to unpack remote log stream id"); + return false; + } + + unref_guard stream_id_unreffer{stream_id}; + ++idx; + + // Get writer ID. + + if ( ! broker::get(msg[idx]) ) + { + reporter->Warning("got remote log w/o writer id: %d", + static_cast(broker::which(msg[idx]))); + return false; + } + + auto writer_id = data_to_val(move(msg[idx]), writer_id_type); + + if ( ! writer_id ) + { + reporter->Warning("failed to unpack remote log writer id"); + return false; + } + + unref_guard writer_id_unreffer{writer_id}; + ++idx; + + // Get path. + + auto path = broker::get(msg[idx]); + + if ( ! path ) + { + reporter->Warning("failed to unpack remote log path"); + return false; + } + + ++idx; + + // Get log values. + + auto vals_data = broker::get(msg[idx]); + + if ( ! vals_data ) + { + reporter->Warning("failed to unpack remote log values"); + return false; + } + + auto num_vals = vals_data->size(); + auto vals = new threading::Value* [num_vals]; + + for ( auto i = 0u; i < num_vals; ++i ) + { + if ( auto val = data_to_threading_val((*vals_data)[i]) ) + vals[i] = val; + else + { + reporter->Warning("failed to convert remote log arg # %d", i); + return false; + } + } + + log_mgr->WriteFromRemote(stream_id->AsEnumVal(), writer_id->AsEnumVal(), *path, num_vals, vals); + return true; + } + bool bro_broker::Manager::AddStore(StoreHandleVal* handle) { if ( ! Enabled() ) diff --git a/src/broker/Manager.h b/src/broker/Manager.h index 9fb7b9e328..10d5019b74 100644 --- a/src/broker/Manager.h +++ b/src/broker/Manager.h @@ -156,15 +156,34 @@ public: /** * Send a log entry to any interested peers. The topic name used is * implicitly "bro/log/". - * @param stream_id the stream to which the log entry belongs. - * @param columns the data which comprises the log entry. - * @param info the record type corresponding to the log's columns. + * @param stream the stream to which the log entry belongs. + * @param writer the writer to use for outputting this log entry. + * @param path the log path to output the log entry to. + * @param num_vals the number of fields to log. + * @param vals the log values to log, of size num_vals. * @param flags tune the behavior of how the message is send. * See the Broker::SendFlags record type. * @return true if the message is sent successfully. */ - bool Log(EnumVal* stream_id, RecordVal* columns, RecordType* info, - int flags); + bool Log(EnumVal* stream, EnumVal* writer, string path, int num_vals, + const threading::Value* const * vals, int flags); + + /** + * Send a message to create a log stream to any interested peers. + * The log stream may or may not already exist on the receiving side. + * The topic name used is implicitly "bro/log/". + * @param stream the stream to which the log entry belongs. + * @param writer the writer to use for outputting this log entry. + * @param info backend initialization information for the writer. + * @param num_fields the number of fields the log has. + * @param fields the log's fields of size num_fields. + * @param flags tune the behavior of how the message is send. + * See the Broker::SendFlags record type. + * @param peer If given, send the message only to this peer. + * @return true if the message is sent successfully. + */ + bool CreateLog(EnumVal* id, EnumVal* writer, const logging::WriterBackend::WriterInfo& info, + int num_fields, const threading::Field* const * fields, int flags, const string& peer = ""); /** * Automatically send an event to any interested peers whenever it is @@ -325,7 +344,6 @@ public: static int send_flags_to_int(Val* flags); private: - // IOSource interface overrides: void GetFds(iosource::FD_Set* read, iosource::FD_Set* write, iosource::FD_Set* except) override; @@ -340,6 +358,9 @@ private: broker::endpoint& Endpoint() { return *endpoint; } + bool ProcessCreateLog(broker::message msg); + bool ProcessWriteLog(broker::message msg); + struct QueueWithStats { broker::message_queue q; size_t received = 0; @@ -360,6 +381,7 @@ private: static VectorType* vector_of_data_type; static EnumType* log_id_type; + static EnumType* writer_id_type; static int send_flags_self_idx; static int send_flags_peers_idx; static int send_flags_unsolicited_idx; diff --git a/src/logging/Manager.cc b/src/logging/Manager.cc index e1a314d4d6..5bebdebdfb 100644 --- a/src/logging/Manager.cc +++ b/src/logging/Manager.cc @@ -919,12 +919,6 @@ bool Manager::Write(EnumVal* id, RecordVal* columns) #endif } -#ifdef ENABLE_BROKER - if ( stream->enable_remote && - ! broker_mgr->Log(id, columns, stream->columns, stream->remote_flags) ) - stream->enable_remote = false; -#endif - Unref(columns); return true; @@ -1121,23 +1115,46 @@ threading::Value** Manager::RecordToFilterVals(Stream* stream, Filter* filter, return vals; } +bool Manager::CreateWriterForRemoteLog(EnumVal* id, EnumVal* writer, WriterBackend::WriterInfo* info, + int num_fields, const threading::Field* const* fields) + { + return CreateWriter(id, writer, info, num_fields, fields, true, false, true); + } + +static void delete_info_and_fields(WriterBackend::WriterInfo* info, int num_fields, const threading::Field* const* fields) + { + for ( int i = 0; i < num_fields; i++ ) + delete fields[i]; + + delete [] fields; + delete info; + } + WriterFrontend* Manager::CreateWriter(EnumVal* id, EnumVal* writer, WriterBackend::WriterInfo* info, - int num_fields, const threading::Field* const* fields, bool local, bool remote, bool from_remote, + int num_fields, const threading::Field* const* fields, bool local, bool remote, bool from_remote, const string& instantiating_filter) { + WriterFrontend* result = 0; + Stream* stream = FindStream(id); if ( ! stream ) + { // Don't know this stream. + delete_info_and_fields(info, num_fields, fields); return 0; + } Stream::WriterMap::iterator w = stream->writers.find(Stream::WriterPathPair(writer->AsEnum(), info->path)); if ( w != stream->writers.end() ) + { // If we already have a writer for this. That's fine, we just // return it. + delete_info_and_fields(info, num_fields, fields); return w->second->writer; + } WriterInfo* winfo = new WriterInfo; winfo->type = writer->Ref()->AsEnumVal(); @@ -1190,7 +1207,7 @@ WriterFrontend* Manager::CreateWriter(EnumVal* id, EnumVal* writer, WriterBacken winfo->info->rotation_interval = winfo->interval; winfo->info->rotation_base = parse_rotate_base_time(base_time); - winfo->writer = new WriterFrontend(*winfo->info, id, writer, local, remote); + winfo->writer = new WriterFrontend(*winfo->info, id, writer, local, remote, stream->remote_flags); winfo->writer->Init(num_fields, fields); InstallRotationTimer(winfo); @@ -1207,8 +1224,8 @@ void Manager::DeleteVals(int num_fields, threading::Value** vals) delete [] vals; } -bool Manager::Write(EnumVal* id, EnumVal* writer, string path, int num_fields, - threading::Value** vals) +bool Manager::WriteFromRemote(EnumVal* id, EnumVal* writer, string path, int num_fields, + threading::Value** vals) { Stream* stream = FindStream(id); @@ -1280,6 +1297,34 @@ void Manager::SendAllWritersTo(RemoteSerializer::PeerID peer) } } +void Manager::SendAllWritersTo(const string& peer) + { +#ifdef ENABLE_BROKER + for ( vector::iterator s = streams.begin(); s != streams.end(); ++s ) + { + Stream* stream = (*s); + + if ( ! stream ) + continue; + + for ( Stream::WriterMap::iterator i = stream->writers.begin(); + i != stream->writers.end(); i++ ) + { + WriterFrontend* writer = i->second->writer; + + EnumVal writer_val(i->first.first, internal_type("Log::Writer")->AsEnumType()); + broker_mgr->CreateLog((*s)->id, + &writer_val, + *i->second->info, + writer->NumFields(), + writer->Fields(), + stream->remote_flags, + peer); + } + } +#endif + } + bool Manager::SetBuf(EnumVal* id, bool enabled) { Stream* stream = FindStream(id); diff --git a/src/logging/Manager.h b/src/logging/Manager.h index 5d3372fb9b..6852160169 100644 --- a/src/logging/Manager.h +++ b/src/logging/Manager.h @@ -129,6 +129,52 @@ public: */ bool Write(EnumVal* id, RecordVal* columns); + /** + * Create a new log writer frontend. This is exposed so that the + * communication system can recreated remote log streams locally. + * + * @param stream The enum value corresponding the log stream. + * + * @param writer The enum value corresponding the desired log writer. + * + * @param info A fully initialized object defining the + * characteristics of the backend writer instance. The method takes + * ownership of this. + * + * @param num_fields The number of log fields to write. + * + * @param vals An arry of log fields to write, of size num_fields. + * The method takes ownership of the arry. + * + * @return Returns true if the writer was successfully created. + */ + bool CreateWriterForRemoteLog(EnumVal* id, EnumVal* writer, WriterBackend::WriterInfo* info, + int num_fields, const threading::Field* const* fields); + + /** + * Writes out log entries that have already passed through all + * filters, and have raised any events. This is meant called for logs + * received alrready processed from remote. + * + * @param stream The enum value corresponding the log stream. + * + * @param writer The enum value corresponding the desired log writer. + * + * @param path The path of the target log stream to write to. + * + * @param num_fields The number of log values to write. + * + * @param vals An arry of log values to write, of size num_fields. + * The method takes ownership of the arry. + */ + bool WriteFromRemote(EnumVal* stream, EnumVal* writer, string path, + int num_fields, threading::Value** vals); + + /** + * Announces all instantiated writers to a given Broker peer. + */ + void SendAllWritersTo(const string& peer); + /** * Sets log streams buffering state. This adjusts all associated * writers to the new state. @@ -203,10 +249,6 @@ protected: int num_fields, const threading::Field* const* fields, bool local, bool remote, bool from_remote, const string& instantiating_filter=""); - // Takes ownership of values.. - bool Write(EnumVal* id, EnumVal* writer, string path, - int num_fields, threading::Value** vals); - // Announces all instantiated writers to peer. void SendAllWritersTo(RemoteSerializer::PeerID peer); diff --git a/src/logging/WriterBackend.cc b/src/logging/WriterBackend.cc index 3e868f067a..624b66b8e8 100644 --- a/src/logging/WriterBackend.cc +++ b/src/logging/WriterBackend.cc @@ -119,6 +119,63 @@ bool WriterBackend::WriterInfo::Write(SerializationFormat* fmt) const return true; } +broker::data WriterBackend::WriterInfo::ToBroker() const + { + auto bpath = broker::record::field(path); + auto brotation_base = broker::record::field(rotation_base); + auto brotation_interval = broker::record::field(rotation_interval); + auto bnetwork_time = broker::record::field(network_time); + + auto t = broker::table(); + + for ( config_map::const_iterator i = config.begin(); i != config.end(); ++i ) + { + auto key = std::string(i->first); + auto value = std::string(i->second); + t.insert(std::make_pair(key, value)); + } + + auto bconfig = broker::record::field(move(t)); + + return move(broker::record({bpath, brotation_base, brotation_interval, bnetwork_time, bconfig})); + } + +bool WriterBackend::WriterInfo::FromBroker(broker::data d) + { + auto r = broker::get(d); + + if ( ! r ) + return false; + + auto bpath = broker::get(*r->get(0)); + auto brotation_base = broker::get(*r->get(1)); + auto brotation_interval = broker::get(*r->get(2)); + auto bnetwork_time = broker::get(*r->get(3)); + auto bconfig = broker::get(*r->get(4)); + + if ( ! (bpath && brotation_base && brotation_interval && bnetwork_time && bconfig) ) + return false; + + path = copy_string(bpath->c_str()); + rotation_base = *brotation_base; + rotation_interval = *brotation_interval; + network_time = *bnetwork_time; + + for ( auto i : *bconfig ) + { + auto k = broker::get(i.first); + auto v = broker::get(i.second); + + if ( ! (k && v) ) + return false; + + auto p = std::make_pair(copy_string(k->c_str()), copy_string(v->c_str())); + config.insert(p); + } + + return true; + } + WriterBackend::WriterBackend(WriterFrontend* arg_frontend) : MsgThread() { num_fields = 0; diff --git a/src/logging/WriterBackend.h b/src/logging/WriterBackend.h index 2a93e8fefc..e17b7070c1 100644 --- a/src/logging/WriterBackend.h +++ b/src/logging/WriterBackend.h @@ -6,6 +6,7 @@ #define LOGGING_WRITERBACKEND_H #include "threading/MsgThread.h" +#include "broker/Data.h" #include "Component.h" @@ -110,15 +111,15 @@ public: } } - private: - const WriterInfo& operator=(const WriterInfo& other); // Disable. - - friend class ::RemoteSerializer; - // Note, these need to be adapted when changing the struct's // fields. They serialize/deserialize the struct. bool Read(SerializationFormat* fmt); bool Write(SerializationFormat* fmt) const; + broker::data ToBroker() const; + bool FromBroker(broker::data d); + + private: + const WriterInfo& operator=(const WriterInfo& other); // Disable. }; /** diff --git a/src/logging/WriterFrontend.cc b/src/logging/WriterFrontend.cc index 14e131c755..05f4f6593a 100644 --- a/src/logging/WriterFrontend.cc +++ b/src/logging/WriterFrontend.cc @@ -2,6 +2,10 @@ #include "Net.h" #include "threading/SerialTypes.h" +#ifdef ENABLE_BROKER +#include "broker/Manager.h" +#endif + #include "Manager.h" #include "WriterFrontend.h" #include "WriterBackend.h" @@ -97,7 +101,7 @@ private: using namespace logging; -WriterFrontend::WriterFrontend(const WriterBackend::WriterInfo& arg_info, EnumVal* arg_stream, EnumVal* arg_writer, bool arg_local, bool arg_remote) +WriterFrontend::WriterFrontend(const WriterBackend::WriterInfo& arg_info, EnumVal* arg_stream, EnumVal* arg_writer, bool arg_local, bool arg_remote, int arg_remote_flags) { stream = arg_stream; writer = arg_writer; @@ -108,6 +112,7 @@ WriterFrontend::WriterFrontend(const WriterBackend::WriterInfo& arg_info, EnumVa buf = true; local = arg_local; remote = arg_remote; + remote_flags = arg_remote_flags; write_buffer = 0; write_buffer_pos = 0; info = new WriterBackend::WriterInfo(arg_info); @@ -167,12 +172,23 @@ void WriterFrontend::Init(int arg_num_fields, const Field* const * arg_fields) backend->SendIn(new InitMessage(backend, arg_num_fields, arg_fields)); if ( remote ) + { remote_serializer->SendLogCreateWriter(stream, writer, *info, arg_num_fields, arg_fields); +#ifdef ENABLE_BROKER + broker_mgr->CreateLog(stream, + writer, + *info, + arg_num_fields, + arg_fields, + remote_flags); +#endif + } + } void WriterFrontend::Write(int arg_num_fields, Value** vals) @@ -191,12 +207,21 @@ void WriterFrontend::Write(int arg_num_fields, Value** vals) } if ( remote ) + { remote_serializer->SendLogWrite(stream, writer, info->path, num_fields, vals); + broker_mgr->Log(stream, + writer, + info->path, + num_fields, + vals, + remote_flags); + } + if ( ! backend ) { DeleteVals(arg_num_fields, vals); diff --git a/src/logging/WriterFrontend.h b/src/logging/WriterFrontend.h index e343f326bf..c39d2bdf3c 100644 --- a/src/logging/WriterFrontend.h +++ b/src/logging/WriterFrontend.h @@ -38,12 +38,15 @@ public: * * local: If true, the writer will instantiate a local backend. * - * remote: If true, the writer will forward all data to remote + * remote: If true, the writer will forward logs to remote * clients. * + * remote_flags: Broker flags controlling where remote logs are + * propagated to. + * * Frontends must only be instantiated by the main thread. */ - WriterFrontend(const WriterBackend::WriterInfo& info, EnumVal* stream, EnumVal* writer, bool local, bool remote); + WriterFrontend(const WriterBackend::WriterInfo& info, EnumVal* stream, EnumVal* writer, bool local, bool remote, int arg_remote_flags); /** * Destructor. @@ -214,6 +217,7 @@ protected: bool buf; // True if buffering is enabled (default). bool local; // True if logging locally. bool remote; // True if loggin remotely. + int remote_flags; // Broker propagation flags. const char* name; // Descriptive name of the WriterBackend::WriterInfo* info; // The writer information. diff --git a/testing/btest/Baseline/broker.remote_log/recv.recv.out b/testing/btest/Baseline/broker.remote_log/recv.recv.out index 2f4a31df51..e69de29bb2 100644 --- a/testing/btest/Baseline/broker.remote_log/recv.recv.out +++ b/testing/btest/Baseline/broker.remote_log/recv.recv.out @@ -1,6 +0,0 @@ -wrote log, [msg=ping, nolog=no, num=0] -wrote log, [msg=ping, nolog=no, num=1] -wrote log, [msg=ping, nolog=no, num=2] -wrote log, [msg=ping, nolog=no, num=3] -wrote log, [msg=ping, nolog=no, num=4] -wrote log, [msg=ping, nolog=no, num=5] diff --git a/testing/btest/Baseline/broker.remote_log_types/recv.recv.out b/testing/btest/Baseline/broker.remote_log_types/recv.recv.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/testing/btest/Baseline/broker.remote_log_types/recv.test.log b/testing/btest/Baseline/broker.remote_log_types/recv.test.log new file mode 100644 index 0000000000..eb2b066cd4 --- /dev/null +++ b/testing/btest/Baseline/broker.remote_log_types/recv.test.log @@ -0,0 +1,10 @@ +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path test +#open 2017-02-11-02-17-35 +#fields b i e c p sn a d t iv s sc ss se vc ve f +#types bool int enum count port subnet addr double time interval string set[count] set[string] set[string] vector[count] vector[string] func +T -42 Test::LOG 21 123 10.0.0.0/24 1.2.3.4 3.14 1486779455.703438 100.000000 hurz 1 AA (empty) 10,20,30 (empty) foo\x0a{ \x0aif (0 < i) \x0a\x09return (Foo);\x0aelse\x0a\x09return (Bar);\x0a\x0a} +#close 2017-02-11-02-17-35 diff --git a/testing/btest/Baseline/broker.remote_log_types/send.send.out b/testing/btest/Baseline/broker.remote_log_types/send.send.out new file mode 100644 index 0000000000..632279e697 --- /dev/null +++ b/testing/btest/Baseline/broker.remote_log_types/send.send.out @@ -0,0 +1 @@ +Broker::outgoing_connection_established, 127.0.0.1, 9999/tcp diff --git a/testing/btest/Baseline/broker.remote_log_types/send.test.log b/testing/btest/Baseline/broker.remote_log_types/send.test.log new file mode 100644 index 0000000000..59987c5998 --- /dev/null +++ b/testing/btest/Baseline/broker.remote_log_types/send.test.log @@ -0,0 +1,10 @@ +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path test +#open 2017-02-11-02-17-35 +#fields b i e c p sn a d t iv s sc ss se vc ve f +#types bool int enum count port subnet addr double time interval string set[count] set[string] set[string] vector[count] vector[string] func +T -42 Test::LOG 21 123 10.0.0.0/24 1.2.3.4 3.14 1486779455.703438 100.000000 hurz 1 AA (empty) 10,20,30 (empty) foo\x0a{ \x0aif (0 < i) \x0a\x09return (Foo);\x0aelse\x0a\x09return (Bar);\x0a\x0a} +#close 2017-02-11-02-17-36 diff --git a/testing/btest/broker/remote_log.test b/testing/btest/broker/remote_log.test index 5881ad6d92..598f3f6e46 100644 --- a/testing/btest/broker/remote_log.test +++ b/testing/btest/broker/remote_log.test @@ -12,25 +12,28 @@ @TEST-START-FILE common.bro + +global quit_receiver: event(); +global quit_sender: event(); + + module Test; export { - redef enum Log::ID += { LOG }; + redef enum Log::ID += { LOG }; - type Info: record { - msg: string &log; - nolog: string &default="no"; - num: count &log; - }; - - global log_test: event(rec: Test::Info); + type Info: record { + msg: string &log; + nolog: string &default="no"; + num: count &log; + }; } event bro_init() &priority=5 - { - Broker::enable(); - Log::create_stream(Test::LOG, [$columns=Test::Info, $ev=log_test]); - } + { + Broker::enable(); + Log::create_stream(Test::LOG, [$columns=Test::Info]); + } @TEST-END-FILE @@ -40,58 +43,66 @@ const broker_port: port &redef; redef exit_only_after_terminate = T; event bro_init() - { - Broker::subscribe_to_logs("bro/log/"); - Broker::listen(broker_port, "127.0.0.1"); - } + { + Broker::subscribe_to_logs("bro/log/"); + Broker::subscribe_to_events("bro/event/"); + Broker::listen(broker_port, "127.0.0.1"); + } -event Test::log_test(rec: Test::Info) +event quit_receiver() { - print "wrote log", rec; - - if ( rec$num == 5 ) - terminate(); + terminate(); } @TEST-END-FILE + @TEST-START-FILE send.bro const broker_port: port &redef; redef exit_only_after_terminate = T; event bro_init() - { - Broker::enable_remote_logs(Test::LOG); - Broker::connect("127.0.0.1", broker_port, 1secs); - } + { + Broker::enable_remote_logs(Test::LOG); + Broker::connect("127.0.0.1", broker_port, 1secs); + } global n = 0; event do_write() - { - if ( n == 6 ) - return; - else + { + if ( n == 6 ) { - Log::write(Test::LOG, [$msg = "ping", $num = n]); - ++n; - event do_write(); + local args = Broker::event_args(quit_receiver); + Broker::send_event("bro/event/", args); + schedule 1sec { quit_sender() }; } + else + { + Log::write(Test::LOG, [$msg = "ping", $num = n]); + ++n; + event do_write(); + } + } + +event quit_sender() + { + terminate(); } event Broker::outgoing_connection_established(peer_address: string, peer_port: port, peer_name: string) - { - print "Broker::outgoing_connection_established", peer_address, peer_port; - event do_write(); - } + { + print "Broker::outgoing_connection_established", peer_address, peer_port; + event do_write(); + } event Broker::outgoing_connection_broken(peer_address: string, peer_port: port) - { - terminate(); - } + { + terminate(); + } @TEST-END-FILE diff --git a/testing/btest/broker/remote_log_types.test b/testing/btest/broker/remote_log_types.test new file mode 100644 index 0000000000..9089e087cb --- /dev/null +++ b/testing/btest/broker/remote_log_types.test @@ -0,0 +1,133 @@ +# @TEST-SERIALIZE: brokercomm +# @TEST-REQUIRES: grep -q ENABLE_BROKER:BOOL=true $BUILD/CMakeCache.txt + +# @TEST-EXEC: btest-bg-run recv "bro -b ../common.bro ../recv.bro broker_port=$BROKER_PORT >recv.out" +# @TEST-EXEC: btest-bg-run send "bro -b ../common.bro ../send.bro broker_port=$BROKER_PORT >send.out" + +# @TEST-EXEC: btest-bg-wait 20 +# @TEST-EXEC: btest-diff recv/recv.out +# @TEST-EXEC: btest-diff recv/test.log +# @TEST-EXEC: btest-diff send/send.out +# @TEST-EXEC: btest-diff send/test.log + +@TEST-START-FILE common.bro + + +global quit_receiver: event(); +global quit_sender: event(); + + +module Test; + +export { + redef enum Log::ID += { LOG }; + + type Info: record { + b: bool; + i: int; + e: Log::ID; + c: count; + p: port; + sn: subnet; + a: addr; + d: double; + t: time; + iv: interval; + s: string; + sc: set[count]; + ss: set[string]; + se: set[string]; + vc: vector of count; + ve: vector of string; + f: function(i: count) : string; + } &log; + +} + +event bro_init() &priority=5 + { + Broker::enable(); + Log::create_stream(Test::LOG, [$columns=Test::Info]); + } + +@TEST-END-FILE + +@TEST-START-FILE recv.bro + +const broker_port: port &redef; +redef exit_only_after_terminate = T; + +event bro_init() + { + Broker::subscribe_to_logs("bro/log/"); + Broker::subscribe_to_events("bro/event/"); + Broker::listen(broker_port, "127.0.0.1"); + } + +event quit_receiver() + { + terminate(); + } + +@TEST-END-FILE + + +@TEST-START-FILE send.bro + +const broker_port: port &redef; +redef exit_only_after_terminate = T; + +event bro_init() + { + Broker::enable_remote_logs(Test::LOG); + Broker::connect("127.0.0.1", broker_port, 1secs); + } + +event quit_sender() + { + terminate(); + } + +function foo(i : count) : string + { + if ( i > 0 ) + return "Foo"; + else + return "Bar"; + } + +event Broker::outgoing_connection_established(peer_address: string, + peer_port: port, + peer_name: string) + { + print "Broker::outgoing_connection_established", peer_address, peer_port; + + local empty_set: set[string]; + local empty_vector: vector of string; + + Log::write(Test::LOG, [ + $b=T, + $i=-42, + $e=Test::LOG, + $c=21, + $p=123/tcp, + $sn=10.0.0.1/24, + $a=1.2.3.4, + $d=3.14, + $t=network_time(), + $iv=100secs, + $s="hurz", + $sc=set(1), # set(1,2,3,4), # Output not stable for multi-element sets. + $ss=set("AA"), # set("AA", "BB", "CC") # Output not stable for multi-element sets. + $se=empty_set, + $vc=vector(10, 20, 30), + $ve=empty_vector, + $f=foo + ]); + + local args = Broker::event_args(quit_receiver); + Broker::send_event("bro/event/", args); + schedule 1sec { quit_sender() }; + } + +@TEST-END-FILE From 2b694a1881f97b0042fd4e05f77bd88fff6389dd Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Sat, 11 Feb 2017 08:36:47 -0800 Subject: [PATCH 10/44] Update failing intel framework test. --- .../output | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testing/btest/Baseline/scripts.base.frameworks.intel.remove-non-existing/output b/testing/btest/Baseline/scripts.base.frameworks.intel.remove-non-existing/output index 9cb4a7c9ff..69606c1407 100644 --- a/testing/btest/Baseline/scripts.base.frameworks.intel.remove-non-existing/output +++ b/testing/btest/Baseline/scripts.base.frameworks.intel.remove-non-existing/output @@ -3,9 +3,9 @@ #empty_field (empty) #unset_field - #path reporter -#open 2016-09-20-22-35-58 +#open 2017-02-11-16-36-40 #fields ts level message location #types time enum string string -0.000000 Reporter::INFO Tried to remove non-existing item '192.168.1.1' (Intel::ADDR). /home/jgras/devel/bro/scripts/base/frameworks/intel/./main.bro, lines 507-508 +0.000000 Reporter::INFO Tried to remove non-existing item '192.168.1.1' (Intel::ADDR). /home/johanna/bro/master/scripts/base/frameworks/intel/./main.bro, lines 520-521 0.000000 Reporter::INFO received termination signal (empty) -#close 2016-09-20-22-35-59 +#close 2017-02-11-16-36-40 From 809660d48a171533eb51db30ad25bcef121d9c04 Mon Sep 17 00:00:00 2001 From: Seth Hall Date: Tue, 14 Feb 2017 07:21:00 -0800 Subject: [PATCH 11/44] Tiny mime-type fix from Dan Caselden. --- scripts/base/frameworks/files/magic/general.sig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/base/frameworks/files/magic/general.sig b/scripts/base/frameworks/files/magic/general.sig index d3bed97efa..23b1c1d074 100644 --- a/scripts/base/frameworks/files/magic/general.sig +++ b/scripts/base/frameworks/files/magic/general.sig @@ -116,7 +116,7 @@ signature file-reg-utf16 { # Microsoft Registry format (typically DESKTOP.DAT) signature file-regf { - file-mime "application vnd.ms-regf", 49 + file-mime "application/vnd.ms-regf", 49 file-magic /^\x72\x65\x67\x66/ } From b3a18f3c80ced6168487c09fa950e0b9b2c9ea9a Mon Sep 17 00:00:00 2001 From: Vlad Grigorescu Date: Wed, 15 Feb 2017 16:24:21 -0600 Subject: [PATCH 12/44] Kerberos ciphertext had some additional ASN.1 content being lumped in. --- src/analyzer/protocol/krb/krb-types.pac | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/analyzer/protocol/krb/krb-types.pac b/src/analyzer/protocol/krb/krb-types.pac index bb2bfba3e8..3b3b9d1f09 100644 --- a/src/analyzer/protocol/krb/krb-types.pac +++ b/src/analyzer/protocol/krb/krb-types.pac @@ -95,7 +95,7 @@ RecordVal* proc_ticket(const KRB_Ticket* ticket) rv->Assign(1, bytestring_to_val(ticket->realm()->data()->content())); rv->Assign(2, GetStringFromPrincipalName(ticket->sname())); rv->Assign(3, asn1_integer_to_val(ticket->enc_part()->data()->etype()->data(), TYPE_COUNT)); - rv->Assign(4, bytestring_to_val(ticket->enc_part()->data()->ciphertext())); + rv->Assign(4, bytestring_to_val(ticket->enc_part()->data()->ciphertext()->encoding()->content())); return rv; } @@ -162,7 +162,7 @@ type KRB_Encrypted_Data = record { true -> next_meta: ASN1EncodingMeta; false -> none_meta: empty; }; - ciphertext : bytestring &length=have_kvno ? next_meta.length : kvno_meta.length; + ciphertext : ASN1OctetString &length=have_kvno ? next_meta.length : kvno_meta.length; } &let { have_kvno : bool = kvno_meta.index == 1; }; From 511ca9e043c06729314db2f2c55cbb330b8d2d0f Mon Sep 17 00:00:00 2001 From: Robin Sommer Date: Fri, 17 Feb 2017 16:28:20 -0800 Subject: [PATCH 13/44] Adding Broker ifdefs for new remote logging code. --- src/logging/Manager.cc | 4 ++++ src/logging/Manager.h | 4 ++-- src/logging/WriterBackend.cc | 2 ++ src/logging/WriterBackend.h | 6 ++++++ src/logging/WriterFrontend.cc | 2 ++ 5 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/logging/Manager.cc b/src/logging/Manager.cc index 5bebdebdfb..8c720137e8 100644 --- a/src/logging/Manager.cc +++ b/src/logging/Manager.cc @@ -1207,7 +1207,11 @@ WriterFrontend* Manager::CreateWriter(EnumVal* id, EnumVal* writer, WriterBacken winfo->info->rotation_interval = winfo->interval; winfo->info->rotation_base = parse_rotate_base_time(base_time); +#ifdef ENABLE_BROKER winfo->writer = new WriterFrontend(*winfo->info, id, writer, local, remote, stream->remote_flags); +#else + winfo->writer = new WriterFrontend(*winfo->info, id, writer, local, remote, 0); +#endif winfo->writer->Init(num_fields, fields); InstallRotationTimer(winfo); diff --git a/src/logging/Manager.h b/src/logging/Manager.h index 6852160169..3334942a3a 100644 --- a/src/logging/Manager.h +++ b/src/logging/Manager.h @@ -131,7 +131,7 @@ public: /** * Create a new log writer frontend. This is exposed so that the - * communication system can recreated remote log streams locally. + * communication system can recreate remote log streams locally. * * @param stream The enum value corresponding the log stream. * @@ -153,7 +153,7 @@ public: /** * Writes out log entries that have already passed through all - * filters, and have raised any events. This is meant called for logs + * filters (and have raised any events). This is meant called for logs * received alrready processed from remote. * * @param stream The enum value corresponding the log stream. diff --git a/src/logging/WriterBackend.cc b/src/logging/WriterBackend.cc index 624b66b8e8..e2afd360e7 100644 --- a/src/logging/WriterBackend.cc +++ b/src/logging/WriterBackend.cc @@ -119,6 +119,7 @@ bool WriterBackend::WriterInfo::Write(SerializationFormat* fmt) const return true; } +#ifdef ENABLE_BROKER broker::data WriterBackend::WriterInfo::ToBroker() const { auto bpath = broker::record::field(path); @@ -175,6 +176,7 @@ bool WriterBackend::WriterInfo::FromBroker(broker::data d) return true; } +#endif WriterBackend::WriterBackend(WriterFrontend* arg_frontend) : MsgThread() { diff --git a/src/logging/WriterBackend.h b/src/logging/WriterBackend.h index e17b7070c1..f6602cc8ca 100644 --- a/src/logging/WriterBackend.h +++ b/src/logging/WriterBackend.h @@ -6,7 +6,10 @@ #define LOGGING_WRITERBACKEND_H #include "threading/MsgThread.h" + +#ifdef ENABLE_BROKER #include "broker/Data.h" +#endif #include "Component.h" @@ -115,8 +118,11 @@ public: // fields. They serialize/deserialize the struct. bool Read(SerializationFormat* fmt); bool Write(SerializationFormat* fmt) const; + +#ifdef ENABLE_BROKER broker::data ToBroker() const; bool FromBroker(broker::data d); +#endif private: const WriterInfo& operator=(const WriterInfo& other); // Disable. diff --git a/src/logging/WriterFrontend.cc b/src/logging/WriterFrontend.cc index 05f4f6593a..0c2378890f 100644 --- a/src/logging/WriterFrontend.cc +++ b/src/logging/WriterFrontend.cc @@ -214,12 +214,14 @@ void WriterFrontend::Write(int arg_num_fields, Value** vals) num_fields, vals); +#ifdef ENABLE_BROKER broker_mgr->Log(stream, writer, info->path, num_fields, vals, remote_flags); +#endif } if ( ! backend ) From 0b8b76cfabb5dd15547c437e1a1c12f81a10d591 Mon Sep 17 00:00:00 2001 From: Seth Hall Date: Sat, 18 Feb 2017 13:55:39 -0500 Subject: [PATCH 14/44] Refactor base krb scripts and update tests. --- scripts/base/protocols/krb/files.bro | 23 +- scripts/base/protocols/krb/main.bro | 227 ++++++++---------- .../policy/protocols/krb/ticket-logging.bro | 26 +- scripts/test-all-policy.bro | 1 + testing/btest/Baseline/plugins.hooks/output | 15 +- .../scripts.base.protocols.krb.kinit/output | 2 +- .../kerberos.log | 10 + .../policy/protocols/krb/ticket-logging.bro | 6 + 8 files changed, 135 insertions(+), 175 deletions(-) create mode 100644 testing/btest/Baseline/scripts.policy.protocols.krb.ticket-logging/kerberos.log create mode 100644 testing/btest/scripts/policy/protocols/krb/ticket-logging.bro diff --git a/scripts/base/protocols/krb/files.bro b/scripts/base/protocols/krb/files.bro index cd2127c605..a486a56290 100644 --- a/scripts/base/protocols/krb/files.bro +++ b/scripts/base/protocols/krb/files.bro @@ -78,30 +78,19 @@ event file_over_new_connection(f: fa_file, c: connection, is_orig: bool) &priori if ( f$source != "KRB_TCP" && f$source != "KRB" ) return; - local info: Info; - - if ( ! c?$krb ) - { - info$ts = network_time(); - info$uid = c$uid; - info$id = c$id; - } - else - info = c$krb; + set_session(c); if ( is_orig ) { - info$client_cert = f$info; - info$client_cert_fuid = f$id; + c$krb$client_cert = f$info; + c$krb$client_cert_fuid = f$id; } else { - info$server_cert = f$info; - info$server_cert_fuid = f$id; + c$krb$server_cert = f$info; + c$krb$server_cert_fuid = f$id; } - c$krb = info; - Files::add_analyzer(f, Files::ANALYZER_X509); # Always calculate hashes. They are not necessary for base scripts # but very useful for identification, and required for policy scripts @@ -111,7 +100,7 @@ event file_over_new_connection(f: fa_file, c: connection, is_orig: bool) &priori function fill_in_subjects(c: connection) { - if ( !c?$krb ) + if ( ! c?$krb ) return; if ( c$krb?$client_cert && c$krb$client_cert?$x509 && c$krb$client_cert$x509?$certificate ) diff --git a/scripts/base/protocols/krb/main.bro b/scripts/base/protocols/krb/main.bro index fc6abc5bff..02abced683 100644 --- a/scripts/base/protocols/krb/main.bro +++ b/scripts/base/protocols/krb/main.bro @@ -10,41 +10,41 @@ export { type Info: record { ## Timestamp for when the event happened. - ts: time &log; + ts: time &log; ## Unique ID for the connection. - uid: string &log; + uid: string &log; ## The connection's 4-tuple of endpoint addresses/ports. - id: conn_id &log; + id: conn_id &log; ## Request type - Authentication Service ("AS") or ## Ticket Granting Service ("TGS") - request_type: string &log &optional; + request_type: string &log &optional; ## Client - client: string &log &optional; + client: string &log &optional; ## Service - service: string &log; + service: string &log &optional; ## Request result - success: bool &log &optional; + success: bool &log &optional; ## Error code - error_code: count &optional; + error_code: count &optional; ## Error message - error_msg: string &log &optional; + error_msg: string &log &optional; ## Ticket valid from - from: time &log &optional; + from: time &log &optional; ## Ticket valid till - till: time &log &optional; + till: time &log &optional; ## Ticket encryption type - cipher: string &log &optional; + cipher: string &log &optional; ## Forwardable ticket requested - forwardable: bool &log &optional; + forwardable: bool &log &optional; ## Renewable ticket requested - renewable: bool &log &optional; + renewable: bool &log &optional; ## We've already logged this - logged: bool &default=F; + logged: bool &default=F; }; ## The server response error texts which are *not* logged. @@ -80,179 +80,140 @@ event bro_init() &priority=5 Log::create_stream(KRB::LOG, [$columns=Info, $ev=log_krb, $path="kerberos"]); } -event krb_error(c: connection, msg: Error_Msg) &priority=5 +function set_session(c: connection): bool { - local info: Info; - - if ( msg?$error_text && msg$error_text in ignored_errors ) + if ( ! c?$krb ) { - if ( c?$krb ) delete c$krb; - return; + c$krb = Info($ts = network_time(), + $uid = c$uid, + $id = c$id); } - - if ( c?$krb && c$krb$logged ) - return; - - if ( c?$krb ) - info = c$krb; - - if ( ! info?$ts ) - { - info$ts = network_time(); - info$uid = c$uid; - info$id = c$id; - } - - if ( ! info?$client && ( msg?$client_name || msg?$client_realm ) ) - info$client = fmt("%s%s", msg?$client_name ? msg$client_name + "/" : "", - msg?$client_realm ? msg$client_realm : ""); - - info$service = msg$service_name; - info$success = F; - - info$error_code = msg$error_code; - - if ( msg?$error_text ) info$error_msg = msg$error_text; - else if ( msg$error_code in error_msg ) info$error_msg = error_msg[msg$error_code]; - - c$krb = info; + + return c$krb$logged; } -event krb_error(c: connection, msg: Error_Msg) &priority=-5 +function do_log(c: connection) { - if ( c?$krb ) + if ( c?$krb && ! c$krb$logged ) { Log::write(KRB::LOG, c$krb); c$krb$logged = T; } } -event krb_as_request(c: connection, msg: KDC_Request) &priority=5 +event krb_error(c: connection, msg: Error_Msg) &priority=5 { - if ( c?$krb && c$krb$logged ) + if ( set_session(c) ) return; - local info: Info; - - if ( !c?$krb ) + if ( msg?$error_text && msg$error_text in ignored_errors ) { - info$ts = network_time(); - info$uid = c$uid; - info$id = c$id; + if ( c?$krb ) + delete c$krb; + + return; } - else - info = c$krb; - info$request_type = "AS"; - info$client = fmt("%s/%s", msg?$client_name ? msg$client_name : "", msg$service_realm); - info$service = msg$service_name; + if ( ! c$krb?$client && ( msg?$client_name || msg?$client_realm ) ) + c$krb$client = fmt("%s%s", msg?$client_name ? msg$client_name + "/" : "", + msg?$client_realm ? msg$client_realm : ""); - if ( msg?$from ) - info$from = msg$from; + c$krb$service = msg$service_name; + c$krb$success = F; + c$krb$error_code = msg$error_code; - info$till = msg$till; - - info$forwardable = msg$kdc_options$forwardable; - info$renewable = msg$kdc_options$renewable; - - c$krb = info; + if ( msg?$error_text ) + c$krb$error_msg = msg$error_text; + else if ( msg$error_code in error_msg ) + c$krb$error_msg = error_msg[msg$error_code]; } -event krb_tgs_request(c: connection, msg: KDC_Request) &priority=5 +event krb_error(c: connection, msg: Error_Msg) &priority=-5 { - if ( c?$krb && c$krb$logged ) + do_log(c); + } + +event krb_as_request(c: connection, msg: KDC_Request) &priority=5 + { + if ( set_session(c) ) return; - local info: Info; + c$krb$request_type = "AS"; + c$krb$client = fmt("%s/%s", msg?$client_name ? msg$client_name : "", msg$service_realm); + c$krb$service = msg$service_name; - if ( !c?$krb ) - { - info$ts = network_time(); - info$uid = c$uid; - info$id = c$id; - } - else - info = c$krb; + if ( msg?$from ) + c$krb$from = msg$from; + c$krb$till = msg$till; - info$request_type = "TGS"; - info$service = msg$service_name; - if ( msg?$from ) info$from = msg$from; - info$till = msg$till; - - info$forwardable = msg$kdc_options$forwardable; - info$renewable = msg$kdc_options$renewable; - - c$krb = info; + c$krb$forwardable = msg$kdc_options$forwardable; + c$krb$renewable = msg$kdc_options$renewable; } event krb_as_response(c: connection, msg: KDC_Response) &priority=5 { - local info: Info; - - if ( c?$krb && c$krb$logged ) + if ( set_session(c) ) return; - if ( c?$krb ) - info = c$krb; - - if ( ! info?$ts ) + if ( ! c$krb?$client && ( msg?$client_name || msg?$client_realm ) ) { - info$ts = network_time(); - info$uid = c$uid; - info$id = c$id; + c$krb$client = fmt("%s/%s", msg?$client_name ? msg$client_name : "", + msg?$client_realm ? msg$client_realm : ""); } - if ( ! info?$client && ( msg?$client_name || msg?$client_realm ) ) - info$client = fmt("%s/%s", msg?$client_name ? msg$client_name : "", msg?$client_realm ? msg$client_realm : ""); - - info$service = msg$ticket$service_name; - info$cipher = cipher_name[msg$ticket$cipher]; - info$success = T; - - c$krb = info; + c$krb$service = msg$ticket$service_name; + c$krb$cipher = cipher_name[msg$ticket$cipher]; + c$krb$success = T; } event krb_as_response(c: connection, msg: KDC_Response) &priority=-5 { - Log::write(KRB::LOG, c$krb); - c$krb$logged = T; + do_log(c); + } + +event krb_ap_request(c: connection, ticket: KRB::Ticket, opts: KRB::AP_Options) &priority=5 + { + if ( set_session(c) ) + return; + } + +event krb_tgs_request(c: connection, msg: KDC_Request) &priority=5 + { + if ( set_session(c) ) + return; + + c$krb$request_type = "TGS"; + c$krb$service = msg$service_name; + if ( msg?$from ) + c$krb$from = msg$from; + c$krb$till = msg$till; + + c$krb$forwardable = msg$kdc_options$forwardable; + c$krb$renewable = msg$kdc_options$renewable; } event krb_tgs_response(c: connection, msg: KDC_Response) &priority=5 { - local info: Info; - - if ( c?$krb && c$krb$logged ) + if ( set_session(c) ) return; - if ( c?$krb ) - info = c$krb; - - if ( ! info?$ts ) + if ( ! c$krb?$client && ( msg?$client_name || msg?$client_realm ) ) { - info$ts = network_time(); - info$uid = c$uid; - info$id = c$id; + c$krb$client = fmt("%s/%s", msg?$client_name ? msg$client_name : "", + msg?$client_realm ? msg$client_realm : ""); } - if ( ! info?$client && ( msg?$client_name || msg?$client_realm ) ) - info$client = fmt("%s/%s", msg?$client_name ? msg$client_name : "", msg?$client_realm ? msg$client_realm : ""); - - info$service = msg$ticket$service_name; - info$cipher = cipher_name[msg$ticket$cipher]; - info$success = T; - - c$krb = info; + c$krb$service = msg$ticket$service_name; + c$krb$cipher = cipher_name[msg$ticket$cipher]; + c$krb$success = T; } event krb_tgs_response(c: connection, msg: KDC_Response) &priority=-5 { - Log::write(KRB::LOG, c$krb); - c$krb$logged = T; + do_log(c); } event connection_state_remove(c: connection) &priority=-5 { - if ( c?$krb && ! c$krb$logged ) - Log::write(KRB::LOG, c$krb); + do_log(c); } diff --git a/scripts/policy/protocols/krb/ticket-logging.bro b/scripts/policy/protocols/krb/ticket-logging.bro index e254b6dc26..22fd3c810b 100644 --- a/scripts/policy/protocols/krb/ticket-logging.bro +++ b/scripts/policy/protocols/krb/ticket-logging.bro @@ -1,3 +1,7 @@ +##! Add Kerberos ticket hashes to the krb.log + +@load base/protocols/krb + module KRB; redef record Info += { @@ -9,25 +13,11 @@ redef record Info += { event krb_ap_request(c: connection, ticket: KRB::Ticket, opts: KRB::AP_Options) { - if ( c?$krb && c$krb$logged ) - return; - - local info: Info; + # Will be overwritten when request is a TGS + c$krb$request_type = "AP"; - if ( !c?$krb ) - { - info$ts = network_time(); - info$uid = c$uid; - info$id = c$id; - } - else - info = c$krb; - - info$request_type = "AP"; # Will be overwritten when request is a TGS if ( ticket?$ciphertext ) - info$auth_ticket = md5_hash(ticket$ciphertext); - - c$krb = info; + c$krb$auth_ticket = md5_hash(ticket$ciphertext); } event krb_as_response(c: connection, msg: KDC_Response) @@ -40,4 +30,4 @@ event krb_tgs_response(c: connection, msg: KDC_Response) { if ( msg$ticket?$ciphertext ) c$krb$new_ticket = md5_hash(msg$ticket$ciphertext); - } \ No newline at end of file + } diff --git a/scripts/test-all-policy.bro b/scripts/test-all-policy.bro index 8d1a9ff054..a022060cd4 100644 --- a/scripts/test-all-policy.bro +++ b/scripts/test-all-policy.bro @@ -72,6 +72,7 @@ @load protocols/http/software.bro @load protocols/http/var-extraction-cookies.bro @load protocols/http/var-extraction-uri.bro +@load protocols/krb/ticket-logging.bro @load protocols/modbus/known-masters-slaves.bro @load protocols/modbus/track-memmap.bro @load protocols/mysql/software.bro diff --git a/testing/btest/Baseline/plugins.hooks/output b/testing/btest/Baseline/plugins.hooks/output index 9b22c34b71..c291302748 100644 --- a/testing/btest/Baseline/plugins.hooks/output +++ b/testing/btest/Baseline/plugins.hooks/output @@ -247,7 +247,7 @@ 0.000000 MetaHookPost CallFunction(Log::__create_stream, , (Weird::LOG, [columns=, ev=Weird::log_weird, path=weird])) -> 0.000000 MetaHookPost CallFunction(Log::__create_stream, , (X509::LOG, [columns=, ev=X509::log_x509, path=x509])) -> 0.000000 MetaHookPost CallFunction(Log::__create_stream, , (mysql::LOG, [columns=, ev=MySQL::log_mysql, path=mysql])) -> -0.000000 MetaHookPost CallFunction(Log::__write, , (PacketFilter::LOG, [ts=1485327769.512366, node=bro, filter=ip or not ip, init=T, success=T])) -> +0.000000 MetaHookPost CallFunction(Log::__write, , (PacketFilter::LOG, [ts=1487443758.386684, node=bro, filter=ip or not ip, init=T, success=T])) -> 0.000000 MetaHookPost CallFunction(Log::add_default_filter, , (Cluster::LOG)) -> 0.000000 MetaHookPost CallFunction(Log::add_default_filter, , (Communication::LOG)) -> 0.000000 MetaHookPost CallFunction(Log::add_default_filter, , (Conn::LOG)) -> @@ -377,7 +377,7 @@ 0.000000 MetaHookPost CallFunction(Log::create_stream, , (Weird::LOG, [columns=, ev=Weird::log_weird, path=weird])) -> 0.000000 MetaHookPost CallFunction(Log::create_stream, , (X509::LOG, [columns=, ev=X509::log_x509, path=x509])) -> 0.000000 MetaHookPost CallFunction(Log::create_stream, , (mysql::LOG, [columns=, ev=MySQL::log_mysql, path=mysql])) -> -0.000000 MetaHookPost CallFunction(Log::write, , (PacketFilter::LOG, [ts=1485327769.512366, node=bro, filter=ip or not ip, init=T, success=T])) -> +0.000000 MetaHookPost CallFunction(Log::write, , (PacketFilter::LOG, [ts=1487443758.386684, node=bro, filter=ip or not ip, init=T, success=T])) -> 0.000000 MetaHookPost CallFunction(NetControl::check_plugins, , ()) -> 0.000000 MetaHookPost CallFunction(NetControl::init, , ()) -> 0.000000 MetaHookPost CallFunction(Notice::want_pp, , ()) -> @@ -968,7 +968,7 @@ 0.000000 MetaHookPre CallFunction(Log::__create_stream, , (Weird::LOG, [columns=, ev=Weird::log_weird, path=weird])) 0.000000 MetaHookPre CallFunction(Log::__create_stream, , (X509::LOG, [columns=, ev=X509::log_x509, path=x509])) 0.000000 MetaHookPre CallFunction(Log::__create_stream, , (mysql::LOG, [columns=, ev=MySQL::log_mysql, path=mysql])) -0.000000 MetaHookPre CallFunction(Log::__write, , (PacketFilter::LOG, [ts=1485327769.512366, node=bro, filter=ip or not ip, init=T, success=T])) +0.000000 MetaHookPre CallFunction(Log::__write, , (PacketFilter::LOG, [ts=1487443758.386684, node=bro, filter=ip or not ip, init=T, success=T])) 0.000000 MetaHookPre CallFunction(Log::add_default_filter, , (Cluster::LOG)) 0.000000 MetaHookPre CallFunction(Log::add_default_filter, , (Communication::LOG)) 0.000000 MetaHookPre CallFunction(Log::add_default_filter, , (Conn::LOG)) @@ -1098,7 +1098,7 @@ 0.000000 MetaHookPre CallFunction(Log::create_stream, , (Weird::LOG, [columns=, ev=Weird::log_weird, path=weird])) 0.000000 MetaHookPre CallFunction(Log::create_stream, , (X509::LOG, [columns=, ev=X509::log_x509, path=x509])) 0.000000 MetaHookPre CallFunction(Log::create_stream, , (mysql::LOG, [columns=, ev=MySQL::log_mysql, path=mysql])) -0.000000 MetaHookPre CallFunction(Log::write, , (PacketFilter::LOG, [ts=1485327769.512366, node=bro, filter=ip or not ip, init=T, success=T])) +0.000000 MetaHookPre CallFunction(Log::write, , (PacketFilter::LOG, [ts=1487443758.386684, node=bro, filter=ip or not ip, init=T, success=T])) 0.000000 MetaHookPre CallFunction(NetControl::check_plugins, , ()) 0.000000 MetaHookPre CallFunction(NetControl::init, , ()) 0.000000 MetaHookPre CallFunction(Notice::want_pp, , ()) @@ -1688,7 +1688,7 @@ 0.000000 | HookCallFunction Log::__create_stream(Weird::LOG, [columns=, ev=Weird::log_weird, path=weird]) 0.000000 | HookCallFunction Log::__create_stream(X509::LOG, [columns=, ev=X509::log_x509, path=x509]) 0.000000 | HookCallFunction Log::__create_stream(mysql::LOG, [columns=, ev=MySQL::log_mysql, path=mysql]) -0.000000 | HookCallFunction Log::__write(PacketFilter::LOG, [ts=1485327769.512366, node=bro, filter=ip or not ip, init=T, success=T]) +0.000000 | HookCallFunction Log::__write(PacketFilter::LOG, [ts=1487443758.386684, node=bro, filter=ip or not ip, init=T, success=T]) 0.000000 | HookCallFunction Log::add_default_filter(Cluster::LOG) 0.000000 | HookCallFunction Log::add_default_filter(Communication::LOG) 0.000000 | HookCallFunction Log::add_default_filter(Conn::LOG) @@ -1818,7 +1818,7 @@ 0.000000 | HookCallFunction Log::create_stream(Weird::LOG, [columns=, ev=Weird::log_weird, path=weird]) 0.000000 | HookCallFunction Log::create_stream(X509::LOG, [columns=, ev=X509::log_x509, path=x509]) 0.000000 | HookCallFunction Log::create_stream(mysql::LOG, [columns=, ev=MySQL::log_mysql, path=mysql]) -0.000000 | HookCallFunction Log::write(PacketFilter::LOG, [ts=1485327769.512366, node=bro, filter=ip or not ip, init=T, success=T]) +0.000000 | HookCallFunction Log::write(PacketFilter::LOG, [ts=1487443758.386684, node=bro, filter=ip or not ip, init=T, success=T]) 0.000000 | HookCallFunction NetControl::check_plugins() 0.000000 | HookCallFunction NetControl::init() 0.000000 | HookCallFunction Notice::want_pp() @@ -2297,6 +2297,7 @@ 1362692527.080972 MetaHookPost CallFunction(Conn::determine_service, , ([id=[orig_h=141.142.228.5, orig_p=59856<...>/plain], current_entity=, orig_mime_depth=1, resp_mime_depth=1], http_state=[pending={}, current_request=1, current_response=1, trans_depth=1], irc=, krb=, modbus=, mysql=, ntlm=, radius=, rdp=, rfb=, sip=, sip_state=, snmp=, smtp=, smtp_state=, socks=, ssh=, syslog=])) -> 1362692527.080972 MetaHookPost CallFunction(Conn::set_conn, , ([id=[orig_h=141.142.228.5, orig_p=59856<...>/plain], current_entity=, orig_mime_depth=1, resp_mime_depth=1], http_state=[pending={}, current_request=1, current_response=1, trans_depth=1], irc=, krb=, modbus=, mysql=, ntlm=, radius=, rdp=, rfb=, sip=, sip_state=, snmp=, smtp=, smtp_state=, socks=, ssh=, syslog=], T)) -> 1362692527.080972 MetaHookPost CallFunction(HTTP::get_file_handle, , ([id=[orig_h=141.142.228.5, orig_p=59856<...>/plain], current_entity=, orig_mime_depth=1, resp_mime_depth=1], http_state=[pending={}, current_request=1, current_response=1, trans_depth=1], irc=, krb=, modbus=, mysql=, ntlm=, radius=, rdp=, rfb=, sip=, sip_state=, snmp=, smtp=, smtp_state=, socks=, ssh=, syslog=], T)) -> +1362692527.080972 MetaHookPost CallFunction(KRB::do_log, , ([id=[orig_h=141.142.228.5, orig_p=59856<...>/plain], current_entity=, orig_mime_depth=1, resp_mime_depth=1], http_state=[pending={}, current_request=1, current_response=1, trans_depth=1], irc=, krb=, modbus=, mysql=, ntlm=, radius=, rdp=, rfb=, sip=, sip_state=, snmp=, smtp=, smtp_state=, socks=, ssh=, syslog=])) -> 1362692527.080972 MetaHookPost CallFunction(KRB::fill_in_subjects, , ([id=[orig_h=141.142.228.5, orig_p=59856<...>/plain], current_entity=, orig_mime_depth=1, resp_mime_depth=1], http_state=[pending={}, current_request=1, current_response=1, trans_depth=1], irc=, krb=, modbus=, mysql=, ntlm=, radius=, rdp=, rfb=, sip=, sip_state=, snmp=, smtp=, smtp_state=, socks=, ssh=, syslog=])) -> 1362692527.080972 MetaHookPost CallFunction(Log::__write, , (Conn::LOG, [ts=1362692526.869344, uid=CHhAvVGS1DHFjwGM9, id=[orig_h=141.142.228.5, orig_p=59856<...>/tcp], proto=tcp, service=http, duration=0.211484, orig_bytes=136, resp_bytes=5007, conn_state=SF, local_orig=, local_resp=, missed_bytes=0, history=ShADadFf, orig_pkts=7, orig_ip_bytes=512, resp_pkts=7, resp_ip_bytes=5379, tunnel_parents={}])) -> 1362692527.080972 MetaHookPost CallFunction(Log::write, , (Conn::LOG, [ts=1362692526.869344, uid=CHhAvVGS1DHFjwGM9, id=[orig_h=141.142.228.5, orig_p=59856<...>/tcp], proto=tcp, service=http, duration=0.211484, orig_bytes=136, resp_bytes=5007, conn_state=SF, local_orig=, local_resp=, missed_bytes=0, history=ShADadFf, orig_pkts=7, orig_ip_bytes=512, resp_pkts=7, resp_ip_bytes=5379, tunnel_parents={}])) -> @@ -2327,6 +2328,7 @@ 1362692527.080972 MetaHookPre CallFunction(Conn::determine_service, , ([id=[orig_h=141.142.228.5, orig_p=59856<...>/plain], current_entity=, orig_mime_depth=1, resp_mime_depth=1], http_state=[pending={}, current_request=1, current_response=1, trans_depth=1], irc=, krb=, modbus=, mysql=, ntlm=, radius=, rdp=, rfb=, sip=, sip_state=, snmp=, smtp=, smtp_state=, socks=, ssh=, syslog=])) 1362692527.080972 MetaHookPre CallFunction(Conn::set_conn, , ([id=[orig_h=141.142.228.5, orig_p=59856<...>/plain], current_entity=, orig_mime_depth=1, resp_mime_depth=1], http_state=[pending={}, current_request=1, current_response=1, trans_depth=1], irc=, krb=, modbus=, mysql=, ntlm=, radius=, rdp=, rfb=, sip=, sip_state=, snmp=, smtp=, smtp_state=, socks=, ssh=, syslog=], T)) 1362692527.080972 MetaHookPre CallFunction(HTTP::get_file_handle, , ([id=[orig_h=141.142.228.5, orig_p=59856<...>/plain], current_entity=, orig_mime_depth=1, resp_mime_depth=1], http_state=[pending={}, current_request=1, current_response=1, trans_depth=1], irc=, krb=, modbus=, mysql=, ntlm=, radius=, rdp=, rfb=, sip=, sip_state=, snmp=, smtp=, smtp_state=, socks=, ssh=, syslog=], T)) +1362692527.080972 MetaHookPre CallFunction(KRB::do_log, , ([id=[orig_h=141.142.228.5, orig_p=59856<...>/plain], current_entity=, orig_mime_depth=1, resp_mime_depth=1], http_state=[pending={}, current_request=1, current_response=1, trans_depth=1], irc=, krb=, modbus=, mysql=, ntlm=, radius=, rdp=, rfb=, sip=, sip_state=, snmp=, smtp=, smtp_state=, socks=, ssh=, syslog=])) 1362692527.080972 MetaHookPre CallFunction(KRB::fill_in_subjects, , ([id=[orig_h=141.142.228.5, orig_p=59856<...>/plain], current_entity=, orig_mime_depth=1, resp_mime_depth=1], http_state=[pending={}, current_request=1, current_response=1, trans_depth=1], irc=, krb=, modbus=, mysql=, ntlm=, radius=, rdp=, rfb=, sip=, sip_state=, snmp=, smtp=, smtp_state=, socks=, ssh=, syslog=])) 1362692527.080972 MetaHookPre CallFunction(Log::__write, , (Conn::LOG, [ts=1362692526.869344, uid=CHhAvVGS1DHFjwGM9, id=[orig_h=141.142.228.5, orig_p=59856<...>/tcp], proto=tcp, service=http, duration=0.211484, orig_bytes=136, resp_bytes=5007, conn_state=SF, local_orig=, local_resp=, missed_bytes=0, history=ShADadFf, orig_pkts=7, orig_ip_bytes=512, resp_pkts=7, resp_ip_bytes=5379, tunnel_parents={}])) 1362692527.080972 MetaHookPre CallFunction(Log::write, , (Conn::LOG, [ts=1362692526.869344, uid=CHhAvVGS1DHFjwGM9, id=[orig_h=141.142.228.5, orig_p=59856<...>/tcp], proto=tcp, service=http, duration=0.211484, orig_bytes=136, resp_bytes=5007, conn_state=SF, local_orig=, local_resp=, missed_bytes=0, history=ShADadFf, orig_pkts=7, orig_ip_bytes=512, resp_pkts=7, resp_ip_bytes=5379, tunnel_parents={}])) @@ -2358,6 +2360,7 @@ 1362692527.080972 | HookCallFunction Conn::determine_service([id=[orig_h=141.142.228.5, orig_p=59856<...>/plain], current_entity=, orig_mime_depth=1, resp_mime_depth=1], http_state=[pending={}, current_request=1, current_response=1, trans_depth=1], irc=, krb=, modbus=, mysql=, ntlm=, radius=, rdp=, rfb=, sip=, sip_state=, snmp=, smtp=, smtp_state=, socks=, ssh=, syslog=]) 1362692527.080972 | HookCallFunction Conn::set_conn([id=[orig_h=141.142.228.5, orig_p=59856<...>/plain], current_entity=, orig_mime_depth=1, resp_mime_depth=1], http_state=[pending={}, current_request=1, current_response=1, trans_depth=1], irc=, krb=, modbus=, mysql=, ntlm=, radius=, rdp=, rfb=, sip=, sip_state=, snmp=, smtp=, smtp_state=, socks=, ssh=, syslog=], T) 1362692527.080972 | HookCallFunction HTTP::get_file_handle([id=[orig_h=141.142.228.5, orig_p=59856<...>/plain], current_entity=, orig_mime_depth=1, resp_mime_depth=1], http_state=[pending={}, current_request=1, current_response=1, trans_depth=1], irc=, krb=, modbus=, mysql=, ntlm=, radius=, rdp=, rfb=, sip=, sip_state=, snmp=, smtp=, smtp_state=, socks=, ssh=, syslog=], T) +1362692527.080972 | HookCallFunction KRB::do_log([id=[orig_h=141.142.228.5, orig_p=59856<...>/plain], current_entity=, orig_mime_depth=1, resp_mime_depth=1], http_state=[pending={}, current_request=1, current_response=1, trans_depth=1], irc=, krb=, modbus=, mysql=, ntlm=, radius=, rdp=, rfb=, sip=, sip_state=, snmp=, smtp=, smtp_state=, socks=, ssh=, syslog=]) 1362692527.080972 | HookCallFunction KRB::fill_in_subjects([id=[orig_h=141.142.228.5, orig_p=59856<...>/plain], current_entity=, orig_mime_depth=1, resp_mime_depth=1], http_state=[pending={}, current_request=1, current_response=1, trans_depth=1], irc=, krb=, modbus=, mysql=, ntlm=, radius=, rdp=, rfb=, sip=, sip_state=, snmp=, smtp=, smtp_state=, socks=, ssh=, syslog=]) 1362692527.080972 | HookCallFunction Log::__write(Conn::LOG, [ts=1362692526.869344, uid=CHhAvVGS1DHFjwGM9, id=[orig_h=141.142.228.5, orig_p=59856<...>/tcp], proto=tcp, service=http, duration=0.211484, orig_bytes=136, resp_bytes=5007, conn_state=SF, local_orig=, local_resp=, missed_bytes=0, history=ShADadFf, orig_pkts=7, orig_ip_bytes=512, resp_pkts=7, resp_ip_bytes=5379, tunnel_parents={}]) 1362692527.080972 | HookCallFunction Log::write(Conn::LOG, [ts=1362692526.869344, uid=CHhAvVGS1DHFjwGM9, id=[orig_h=141.142.228.5, orig_p=59856<...>/tcp], proto=tcp, service=http, duration=0.211484, orig_bytes=136, resp_bytes=5007, conn_state=SF, local_orig=, local_resp=, missed_bytes=0, history=ShADadFf, orig_pkts=7, orig_ip_bytes=512, resp_pkts=7, resp_ip_bytes=5379, tunnel_parents={}]) diff --git a/testing/btest/Baseline/scripts.base.protocols.krb.kinit/output b/testing/btest/Baseline/scripts.base.protocols.krb.kinit/output index 0bec7ee13c..cf6a7a0616 100644 --- a/testing/btest/Baseline/scripts.base.protocols.krb.kinit/output +++ b/testing/btest/Baseline/scripts.base.protocols.krb.kinit/output @@ -1,3 +1,3 @@ KRB_AP_REQUEST -[pvno=5, realm=VLADG.NET, service_name=krbtgt/VLADG.NET, cipher=18] +[pvno=5, realm=VLADG.NET, service_name=krbtgt/VLADG.NET, cipher=18, ciphertext=\x04\x81\xfa{\x9fY\xd0f\x8dS\xf4I\x88\x04\xfa\xc1\xd8m\xa2\xb7+\xbb\x19\xcag\x0c\x13\xd1g*\xfc\x18\xd1\xb1\x80!\xbd\x85\xec\xf9\x9b\xfa-\x18\xb6\xf5h\x91\xe7\x99\xf4\xdb\x93\xa0\xc7\x90\x1e\xa9\x95v\xd3\x12\xfa,9\x1d\x0b\xd0\xa1\xd25\x0f\x1f[G\xdf\xd0\xbbd\x06$2\xd1\xae\x130qZiY\x07@\xe9\xf9\xff\xa4\x9a\xd4\x09\xf0\x0d\xc1R\x10M\xbdKOV\xfd\xf6\x13\xf6\x9a\x95N\xdf!\xf6x\x94\xd8j\xa5\xdcp\xa8\x04\x99\x02x\xdb$\xd8\xfa_o\x8dV\xc8\x0a\xfe\x00\xf3&c\x0c8\xd1\xd0\xe9\x8e\xab\xfe&\xfe\x00\x8d$\x98I\xe5\x8d\x94rM4%\xd8\xfe\xa9\x08\x06\xc6\x95H7\xf7HCq\xb9\x0d$\x95?\x83B\x82\xdd\xea\xc3f3\xcc\xbb\x09\x0d-\x09;\xa6i%\xcd\xba\x11\xd4\xe0\x12w\xd0G&\xdaj\x82\x7f;\xf3\x1d\x10\xa4l\x06\x16l\x1bc\xa1\xd1\x15!\x00\x8a\xff\x8a\x06\xe7U^: Date: Mon, 20 Feb 2017 00:07:14 -0500 Subject: [PATCH 15/44] Rework the RADIUS base script. - This fixes BIT-1769 by logging all requests even in the absence of a reply. The way that request and replying matching were being handled was restructured to mostly ignore the transaction ids because they aren't that helpful for network monitoring and it makes the script structure more complicated. - Add `framed_addr` field to the radius log to indicate if the radius server is hinting at an address for the client. - Add `ttl` field to indicate how quickly the radius server is replying to the network access server. - Fix a bunch of indentation inconsistencies. --- scripts/base/protocols/radius/main.bro | 151 ++++++++++-------- .../radius.log | 10 +- .../radius.log | 16 ++ .../Traces/radius/radius_localhost.pcapng | Bin 0 -> 2952 bytes .../radius/radius-multiple-attempts.test | 6 + 5 files changed, 113 insertions(+), 70 deletions(-) create mode 100644 testing/btest/Baseline/scripts.base.protocols.radius.radius-multiple-attempts/radius.log create mode 100644 testing/btest/Traces/radius/radius_localhost.pcapng create mode 100644 testing/btest/scripts/base/protocols/radius/radius-multiple-attempts.test diff --git a/scripts/base/protocols/radius/main.bro b/scripts/base/protocols/radius/main.bro index d9c2d08ca8..ea30b27911 100644 --- a/scripts/base/protocols/radius/main.bro +++ b/scripts/base/protocols/radius/main.bro @@ -10,52 +10,51 @@ export { type Info: record { ## Timestamp for when the event happened. - ts : time &log; + ts : time &log; ## Unique ID for the connection. - uid : string &log; + uid : string &log; ## The connection's 4-tuple of endpoint addresses/ports. - id : conn_id &log; + id : conn_id &log; ## The username, if present. - username : string &log &optional; + username : string &log &optional; ## MAC address, if present. - mac : string &log &optional; - ## Remote IP address, if present. - remote_ip : addr &log &optional; + mac : string &log &optional; + ## The address given to the network access server, if + ## present. This is only a hint from the RADIUS server + ## and the network access server is not required to honor + ## the address. + framed_addr : addr &log &optional; + ## Remote IP address, if present. This is collected + ## from the Tunnel-Client-Endpoint attribute. + remote_ip : addr &log &optional; ## Connect info, if present. - connect_info : string &log &optional; + connect_info : string &log &optional; + ## Reply message from the server challenge. This is + ## frequently shown to the user authenticating. + reply_msg : string &log &optional; ## Successful or failed authentication. - result : string &log &optional; - ## Whether this has already been logged and can be ignored. - logged : bool &optional; + result : string &log &optional; + ## The duration between the first request and + ## either the "Access-Accept" message or an error. + ## If the field is empty, it means that either + ## the request or response was not seen. + ttl : interval &log &optional; + ## Whether this has already been logged and can be ignored. + logged : bool &default=F; }; - ## The amount of time we wait for an authentication response before - ## expiring it. - const expiration_interval = 10secs &redef; - - ## Logs an authentication attempt if we didn't see a response in time. - ## - ## t: A table of Info records. - ## - ## idx: The index of the connection$radius table corresponding to the - ## radius authentication about to expire. - ## - ## Returns: 0secs, which when this function is used as an - ## :bro:attr:`&expire_func`, indicates to remove the element at - ## *idx* immediately. - global expire: function(t: table[count] of Info, idx: count): interval; - ## Event that can be handled to access the RADIUS record as it is sent on - ## to the loggin framework. + ## to the logging framework. global log_radius: event(rec: Info); } redef record connection += { - radius: table[count] of Info &optional &write_expire=expiration_interval &expire_func=expire; + radius: Info &optional; }; const ports = { 1812/udp }; +redef likely_server_ports += { ports }; event bro_init() &priority=5 { @@ -63,64 +62,86 @@ event bro_init() &priority=5 Analyzer::register_for_ports(Analyzer::ANALYZER_RADIUS, ports); } -event radius_message(c: connection, result: RADIUS::Message) +event radius_message(c: connection, result: RADIUS::Message) &priority=5 { - local info: Info; - - if ( c?$radius && result$trans_id in c$radius ) - info = c$radius[result$trans_id]; - else + if ( ! c?$radius ) { - c$radius = table(); - info$ts = network_time(); - info$uid = c$uid; - info$id = c$id; + c$radius = Info($ts = network_time(), + $uid = c$uid, + $id = c$id); } - switch ( RADIUS::msg_types[result$code] ) { + switch ( RADIUS::msg_types[result$code] ) + { case "Access-Request": - if ( result?$attributes ) { + if ( result?$attributes ) + { # User-Name - if ( ! info?$username && 1 in result$attributes ) - info$username = result$attributes[1][0]; + if ( ! c$radius?$username && 1 in result$attributes ) + c$radius$username = result$attributes[1][0]; # Calling-Station-Id (we expect this to be a MAC) - if ( ! info?$mac && 31 in result$attributes ) - info$mac = normalize_mac(result$attributes[31][0]); + if ( ! c$radius?$mac && 31 in result$attributes ) + c$radius$mac = normalize_mac(result$attributes[31][0]); # Tunnel-Client-EndPoint (useful for VPNs) - if ( ! info?$remote_ip && 66 in result$attributes ) - info$remote_ip = to_addr(result$attributes[66][0]); + if ( ! c$radius?$remote_ip && 66 in result$attributes ) + c$radius$remote_ip = to_addr(result$attributes[66][0]); # Connect-Info - if ( ! info?$connect_info && 77 in result$attributes ) - info$connect_info = result$attributes[77][0]; - } + if ( ! c$radius?$connect_info && 77 in result$attributes ) + c$radius$connect_info = result$attributes[77][0]; + } + break; + case "Access-Challenge": + if ( result?$attributes ) + { + # Framed-IP-Address + if ( ! c$radius?$framed_addr && 8 in result$attributes ) + c$radius$framed_addr = raw_bytes_to_v4_addr(result$attributes[8][0]); + + if ( ! c$radius?$reply_msg && 18 in result$attributes ) + c$radius$reply_msg = result$attributes[18][0]; + } break; case "Access-Accept": - info$result = "success"; + c$radius$result = "success"; break; case "Access-Reject": - info$result = "failed"; + c$radius$result = "failed"; break; - } - if ( info?$result && ! info?$logged ) - { - info$logged = T; - Log::write(RADIUS::LOG, info); + # TODO: Support RADIUS accounting. (add port 1813/udp above too) + #case "Accounting-Request": + # break; + # + #case "Accounting-Response": + # break; } - - c$radius[result$trans_id] = info; } +event radius_message(c: connection, result: RADIUS::Message) &priority=-5 + { + if ( c$radius?$result ) + { + local ttl = network_time() - c$radius$ts; + if ( ttl != 0secs ) + c$radius$ttl = ttl; -function expire(t: table[count] of Info, idx: count): interval - { - t[idx]$result = "unknown"; - Log::write(RADIUS::LOG, t[idx]); - return 0secs; - } + Log::write(RADIUS::LOG, c$radius); + + delete c$radius; + } + } + +event connection_state_remove(c: connection) &priority=-5 + { + if ( c?$radius && ! c$radius$logged ) + { + c$radius$result = "unknown"; + Log::write(RADIUS::LOG, c$radius); + } + } diff --git a/testing/btest/Baseline/scripts.base.protocols.radius.auth/radius.log b/testing/btest/Baseline/scripts.base.protocols.radius.auth/radius.log index 944b5a3ad3..bd536ecca2 100644 --- a/testing/btest/Baseline/scripts.base.protocols.radius.auth/radius.log +++ b/testing/btest/Baseline/scripts.base.protocols.radius.auth/radius.log @@ -3,8 +3,8 @@ #empty_field (empty) #unset_field - #path radius -#open 2016-07-13-16-16-47 -#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p username mac remote_ip connect_info result -#types time string addr port addr port string string addr string string -1217631137.916736 CHhAvVGS1DHFjwGM9 10.0.0.1 1645 10.0.0.100 1812 John.McGuirk 00:14:22:e9:54:5e - - success -#close 2016-07-13-16-16-47 +#open 2017-02-20-04-53-55 +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p username mac framed_addr remote_ip connect_info reply_msg result ttl +#types time string addr port addr port string string addr addr string string string interval +1217631137.872968 CHhAvVGS1DHFjwGM9 10.0.0.1 1645 10.0.0.100 1812 John.McGuirk 00:14:22:e9:54:5e 255.255.255.254 - - Hello, %u success 0.043882 +#close 2017-02-20-04-53-55 diff --git a/testing/btest/Baseline/scripts.base.protocols.radius.radius-multiple-attempts/radius.log b/testing/btest/Baseline/scripts.base.protocols.radius.radius-multiple-attempts/radius.log new file mode 100644 index 0000000000..8dac83de65 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.radius.radius-multiple-attempts/radius.log @@ -0,0 +1,16 @@ +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path radius +#open 2017-02-20-04-56-31 +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p username mac framed_addr remote_ip connect_info reply_msg result ttl +#types time string addr port addr port string string addr addr string string string interval +1440447766.440305 CHhAvVGS1DHFjwGM9 127.0.0.1 53031 127.0.0.1 1812 steve - 172.16.3.33 - - - failed 1.005906 +1440447839.947454 ClEkJM2Vm5giqnMf4h 127.0.0.1 65443 127.0.0.1 1812 steve - 172.16.3.33 - - - success 0.000779 +1440447848.196115 C4J4Th3PJpwUYZZ6gc 127.0.0.1 57717 127.0.0.1 1812 steve - - - - - success 0.000275 +1440447860.613743 CtPZjS20MLrsMUOJi2 127.0.0.1 64691 127.0.0.1 1812 steve - - - - - success 0.000273 +1440447880.931272 CUM0KZ3MLUfNB0cl11 127.0.0.1 52178 127.0.0.1 1812 steve - - - - - failed 1.001459 +1440447904.122012 CmES5u32sYpV7JYN 127.0.0.1 62956 127.0.0.1 1812 steve - - - - - unknown - +1440448190.335333 CP5puj4I8PtEU4qzYg 127.0.0.1 53127 127.0.0.1 1812 steve - - - - - success 0.000517 +#close 2017-02-20-04-56-31 diff --git a/testing/btest/Traces/radius/radius_localhost.pcapng b/testing/btest/Traces/radius/radius_localhost.pcapng new file mode 100644 index 0000000000000000000000000000000000000000..0de5c46dcd1d31202b4190668fa90d613ae14108 GIT binary patch literal 2952 zcmc(hdo+}39LJxTVMd11Gzm%5ML6UZQf<3ZF1a*rDVNf?&CoCxrcNd0-li6z>sA(< zky}MJIjlBYO2tUZs!gG*Hm#IB?94vT?7MTk)1Ljq*>n0GU!Lck@jRd3_xV1Ix~8VO zJ^;Y;3sXG;{3*iOb$dagD9u(6Roq*|SkaLS^Vph9jE1ASGr-!3m&$h35q7cD0qB-uG~#@n5& znHt1MpiYqguAB$GuNevz;wmoVv5Mdr3FA7QcO1_}p|=@FNRKBp88&#n!s z7@L2_VL>+;El8xG?*uh+xfW^O6f(3OsG}z&>Xf+naC1JNuK}m34>jd)W9q;LwN?+_|lBhBQ>4_DYtbS3v=h8JejTGtPLs=@0Ojv8^?k|nS#be3 zzc)`juX&Q{MGN@zcN;%<*ZViDpEn5g#GN{{p5FJTdvb=_7YMSR@fP<|{xyF!9+C*< z{4q)R9^~Gl{0V_*|GD%o^?`%}XP+|}TF)$hRe0O9;9yl^@v+X#K0{hTWzcoJw?9*7 z-E+SEv8mukS;O5x;oeW_VE*Kt*>$hTd*>)vmrvH75kB>949vnm`4hu2DwMw)o|1@k zI7Y(qmptZ;=azdPD@`H_LFBXMfDe=~dPan;cX)X~9(^+2lSu*;*(jNMnNXW^+t@H> zKonQkfLQTXRzvs>9vl!FR=??ttI+Gz1-rvp3AD$YwUC4NGGtKOA2l0`d0kQ0ZLWNis znJ^>SaEyepgk=QcSt#@;BEXb}5?R0{%fhd%-gL8lY>c5%mU|)wF5B);JAB7s zv(v5~HP)>quWtQcp0t!Wa+Grt3oqux|FNwdSWZlVJ5Tg@U+BK9?9LA|-AbnYm?9^7 zT&LMr#ZHNCUd#HpuB&VDmYm&mE3h_yY-^SNVA|X*2J;m;@q^x=LOH2_>&>x&VEJW7{i{?ffy{*ZBTg-oG4JYdltYwLa%# z^lFZWqQ8lE(xZ3i6BesCpNFMr<;rMfUvkqv4@<<2>Yd6gyGHRWZ5h}Zpg=>z48GXjf^m-@zNo6LEtQ^PH2ID|i=U(9_ Date: Tue, 21 Feb 2017 15:06:39 -0500 Subject: [PATCH 16/44] Updated Windows version detection to include Windows 10 Thanks to Fatema Bannatwala for finding it and Keith Lehigh and Mike Patterson for verifying. --- scripts/policy/frameworks/software/windows-version-detection.bro | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/policy/frameworks/software/windows-version-detection.bro b/scripts/policy/frameworks/software/windows-version-detection.bro index 7ed1ab359e..50177b2e9b 100644 --- a/scripts/policy/frameworks/software/windows-version-detection.bro +++ b/scripts/policy/frameworks/software/windows-version-detection.bro @@ -48,6 +48,7 @@ export { ["Microsoft-CryptoAPI/6.2"] = [$name="Windows", $version=[$major=6, $minor=2, $addl="8 or Server 2012"]], ["Microsoft-CryptoAPI/6.3"] = [$name="Windows", $version=[$major=6, $minor=3, $addl="8.1 or Server 2012 R2"]], ["Microsoft-CryptoAPI/6.4"] = [$name="Windows", $version=[$major=6, $minor=4, $addl="10 Technical Preview"]], + ["Microsoft-CryptoAPI/10.0"] = [$name="Windows", $version=[$major=10, $minor=0]], } &redef; } From b0d812812f7b8539bf2066683f8c4b6d4d827e6e Mon Sep 17 00:00:00 2001 From: Seth Hall Date: Tue, 21 Feb 2017 15:45:26 -0500 Subject: [PATCH 17/44] In progress on ascii writer behavior change. --- src/input/readers/ascii/Ascii.cc | 83 ++++++++++++++++++++++++-------- src/input/readers/ascii/Ascii.h | 5 ++ 2 files changed, 69 insertions(+), 19 deletions(-) diff --git a/src/input/readers/ascii/Ascii.cc b/src/input/readers/ascii/Ascii.cc index 8b609bda04..3d7d6415a5 100644 --- a/src/input/readers/ascii/Ascii.cc +++ b/src/input/readers/ascii/Ascii.cc @@ -61,6 +61,11 @@ void Ascii::DoClose() bool Ascii::DoInit(const ReaderInfo& info, int num_fields, const Field* const* fields) { + continue_on_failure = true; + is_failed = false; + + filename = info.source; + separator.assign( (const char*) BifConst::InputAscii::separator->Bytes(), BifConst::InputAscii::separator->Len()); @@ -98,18 +103,10 @@ bool Ascii::DoInit(const ReaderInfo& info, int num_fields, const Field* const* f formatter::Ascii::SeparatorInfo sep_info(separator, set_separator, unset_field, empty_field); formatter = unique_ptr(new formatter::Ascii(this, sep_info)); - file.open(info.source); - if ( ! file.is_open() ) + if ( ! OpenFile() ) { - Error(Fmt("Init: cannot open %s", info.source)); - return false; - } - - if ( ReadHeader(false) == false ) - { - Error(Fmt("Init: cannot open %s; headers are incorrect", info.source)); - file.close(); - return false; + is_failed = true; + return continue_on_failure; } DoUpdate(); @@ -117,6 +114,26 @@ bool Ascii::DoInit(const ReaderInfo& info, int num_fields, const Field* const* f return true; } +bool Ascii::OpenFile() + { + file.open(filename); + if ( ! file.is_open() ) + { + Error(Fmt("Init: cannot open %s", filename.c_str())); + is_failed = true; + return continue_on_failure; + } + + if ( ReadHeader(false) == false ) + { + Error(Fmt("Init: cannot open %s; headers are incorrect", filename.c_str())); + file.close(); + is_failed = true; + return continue_on_failure; + } + + return true; + } bool Ascii::ReadHeader(bool useCached) { @@ -129,7 +146,8 @@ bool Ascii::ReadHeader(bool useCached) if ( ! GetLine(line) ) { Error("could not read first line"); - return false; + is_failed = true; + return continue_on_failure; } headerline = line; @@ -172,7 +190,9 @@ bool Ascii::ReadHeader(bool useCached) Error(Fmt("Did not find requested field %s in input data file %s.", field->name, Info().source)); - return false; + + is_failed = true; + return continue_on_failure; } FieldMapping f(field->name, field->type, field->subtype, ifields[field->name]); @@ -184,7 +204,9 @@ bool Ascii::ReadHeader(bool useCached) { Error(Fmt("Could not find requested port type field %s in input data file.", field->secondary_name)); - return false; + + is_failed = true; + return continue_on_failure; } f.secondary_position = ifields[field->secondary_name]; @@ -224,6 +246,12 @@ bool Ascii::GetLine(string& str) // read the entire file and send appropriate thingies back to InputMgr bool Ascii::DoUpdate() { + if ( is_failed ) + if ( ! OpenFile() ) + { + printf("do updates after failure?!\n"); + } + switch ( Info().mode ) { case MODE_REREAD: { @@ -232,7 +260,8 @@ bool Ascii::DoUpdate() if ( stat(Info().source, &sb) == -1 ) { Error(Fmt("Could not get stat for %s", Info().source)); - return false; + is_failed = true; + return continue_on_failure; } if ( sb.st_mtime <= mtime ) // no change @@ -255,7 +284,10 @@ bool Ascii::DoUpdate() { file.clear(); // remove end of file evil bits if ( !ReadHeader(true) ) - return false; // header reading failed + { + is_failed = true; + return continue_on_failure; // header reading failed + } break; } @@ -267,12 +299,14 @@ bool Ascii::DoUpdate() if ( ! file.is_open() ) { Error(Fmt("cannot open %s", Info().source)); - return false; + is_failed = true; + return continue_on_failure; } if ( ReadHeader(false) == false ) { - return false; + is_failed = true; + return continue_on_failure; } break; @@ -334,7 +368,9 @@ bool Ascii::DoUpdate() delete fields[i]; delete [] fields; - return false; + + is_failed = true; + return continue_on_failure; } Value* val = formatter->ParseValue(stringfields[(*fit).position], (*fit).name, (*fit).type, (*fit).subtype); @@ -390,6 +426,15 @@ bool Ascii::DoUpdate() bool Ascii::DoHeartbeat(double network_time, double current_time) { + printf("heartbeat\n"); + is_failed = false; + + if ( ! file.is_open() ) + OpenFile(); + + //if ( is_failed ) + // return continue_on_failure; + switch ( Info().mode ) { case MODE_MANUAL: diff --git a/src/input/readers/ascii/Ascii.h b/src/input/readers/ascii/Ascii.h index 20a459968d..102736d24e 100644 --- a/src/input/readers/ascii/Ascii.h +++ b/src/input/readers/ascii/Ascii.h @@ -55,7 +55,9 @@ protected: private: bool ReadHeader(bool useCached); bool GetLine(string& str); + bool OpenFile(); + string filename; ifstream file; time_t mtime; @@ -71,6 +73,9 @@ private: string empty_field; string unset_field; + bool continue_on_failure; + bool is_failed; + std::unique_ptr formatter; }; From 2b15ec1069bf150ea29c2ff3b46edfba4acbb109 Mon Sep 17 00:00:00 2001 From: Seth Hall Date: Tue, 21 Feb 2017 23:35:29 -0500 Subject: [PATCH 18/44] Another resilient Ascii reader checkpoint. This works correctly now (as a prototype at least). If a file disappears, the thread complains once and once the file reappears the thread will once again begin watching it. --- src/input/readers/ascii/Ascii.cc | 63 ++++++++++---------------------- src/input/readers/ascii/Ascii.h | 1 - 2 files changed, 19 insertions(+), 45 deletions(-) diff --git a/src/input/readers/ascii/Ascii.cc b/src/input/readers/ascii/Ascii.cc index 3d7d6415a5..2f91ebe27a 100644 --- a/src/input/readers/ascii/Ascii.cc +++ b/src/input/readers/ascii/Ascii.cc @@ -64,8 +64,6 @@ bool Ascii::DoInit(const ReaderInfo& info, int num_fields, const Field* const* f continue_on_failure = true; is_failed = false; - filename = info.source; - separator.assign( (const char*) BifConst::InputAscii::separator->Bytes(), BifConst::InputAscii::separator->Len()); @@ -103,11 +101,10 @@ bool Ascii::DoInit(const ReaderInfo& info, int num_fields, const Field* const* f formatter::Ascii::SeparatorInfo sep_info(separator, set_separator, unset_field, empty_field); formatter = unique_ptr(new formatter::Ascii(this, sep_info)); - if ( ! OpenFile() ) - { - is_failed = true; + OpenFile(); + + if ( is_failed ) return continue_on_failure; - } DoUpdate(); @@ -116,22 +113,25 @@ bool Ascii::DoInit(const ReaderInfo& info, int num_fields, const Field* const* f bool Ascii::OpenFile() { - file.open(filename); + file.open(Info().source); if ( ! file.is_open() ) { - Error(Fmt("Init: cannot open %s", filename.c_str())); + if ( ! is_failed ) + Warning(Fmt("Init: cannot open %s", Info().source)); is_failed = true; return continue_on_failure; } if ( ReadHeader(false) == false ) { - Error(Fmt("Init: cannot open %s; headers are incorrect", filename.c_str())); + if ( ! is_failed ) + Warning(Fmt("Init: cannot open %s; headers are incorrect", Info().source)); file.close(); is_failed = true; return continue_on_failure; } + is_failed = false; return true; } @@ -141,14 +141,10 @@ bool Ascii::ReadHeader(bool useCached) string line; map ifields; - if ( ! useCached ) + if ( headerline == "" ) { if ( ! GetLine(line) ) - { - Error("could not read first line"); - is_failed = true; - return continue_on_failure; - } + return false; headerline = line; } @@ -188,7 +184,7 @@ bool Ascii::ReadHeader(bool useCached) continue; } - Error(Fmt("Did not find requested field %s in input data file %s.", + Warning(Fmt("Did not find requested field %s in input data file %s.", field->name, Info().source)); is_failed = true; @@ -202,7 +198,7 @@ bool Ascii::ReadHeader(bool useCached) map::iterator fit2 = ifields.find(field->secondary_name); if ( fit2 == ifields.end() ) { - Error(Fmt("Could not find requested port type field %s in input data file.", + Warning(Fmt("Could not find requested port type field %s in input data file.", field->secondary_name)); is_failed = true; @@ -215,7 +211,6 @@ bool Ascii::ReadHeader(bool useCached) columnMap.push_back(f); } - // well, that seems to have worked... return true; } @@ -246,12 +241,6 @@ bool Ascii::GetLine(string& str) // read the entire file and send appropriate thingies back to InputMgr bool Ascii::DoUpdate() { - if ( is_failed ) - if ( ! OpenFile() ) - { - printf("do updates after failure?!\n"); - } - switch ( Info().mode ) { case MODE_REREAD: { @@ -259,7 +248,8 @@ bool Ascii::DoUpdate() struct stat sb; if ( stat(Info().source, &sb) == -1 ) { - Error(Fmt("Could not get stat for %s", Info().source)); + Warning(Fmt("Could not get stat for %s", Info().source)); + file.close(); is_failed = true; return continue_on_failure; } @@ -295,19 +285,7 @@ bool Ascii::DoUpdate() file.close(); } - file.open(Info().source); - if ( ! file.is_open() ) - { - Error(Fmt("cannot open %s", Info().source)); - is_failed = true; - return continue_on_failure; - } - - if ( ReadHeader(false) == false ) - { - is_failed = true; - return continue_on_failure; - } + OpenFile(); break; } @@ -361,7 +339,7 @@ bool Ascii::DoUpdate() if ( (*fit).position > pos || (*fit).secondary_position > pos ) { - Error(Fmt("Not enough fields in line %s. Found %d fields, want positions %d and %d", + Warning(Fmt("Not enough fields in line %s. Found %d fields, want positions %d and %d", line.c_str(), pos, (*fit).position, (*fit).secondary_position)); for ( int i = 0; i < fpos; i++ ) @@ -426,14 +404,11 @@ bool Ascii::DoUpdate() bool Ascii::DoHeartbeat(double network_time, double current_time) { - printf("heartbeat\n"); - is_failed = false; - if ( ! file.is_open() ) OpenFile(); - //if ( is_failed ) - // return continue_on_failure; + if ( is_failed ) + return continue_on_failure; switch ( Info().mode ) { diff --git a/src/input/readers/ascii/Ascii.h b/src/input/readers/ascii/Ascii.h index 102736d24e..c9fd55f3d5 100644 --- a/src/input/readers/ascii/Ascii.h +++ b/src/input/readers/ascii/Ascii.h @@ -57,7 +57,6 @@ private: bool GetLine(string& str); bool OpenFile(); - string filename; ifstream file; time_t mtime; From 7bbaa911b0ba4e102a17fc8e0f882aeb6509c235 Mon Sep 17 00:00:00 2001 From: Seth Hall Date: Wed, 22 Feb 2017 00:02:51 -0500 Subject: [PATCH 19/44] I missed one test I needed to update for the kerberos commit that I just pushed. --- .../kerberos.log | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testing/btest/Baseline/scripts.policy.protocols.krb.ticket-logging/kerberos.log b/testing/btest/Baseline/scripts.policy.protocols.krb.ticket-logging/kerberos.log index aee00fbf43..5645378a7e 100644 --- a/testing/btest/Baseline/scripts.policy.protocols.krb.ticket-logging/kerberos.log +++ b/testing/btest/Baseline/scripts.policy.protocols.krb.ticket-logging/kerberos.log @@ -3,8 +3,8 @@ #empty_field (empty) #unset_field - #path kerberos -#open 2017-02-18-18-38-58 +#open 2017-02-22-05-02-14 #fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p request_type client service success error_msg from till cipher forwardable renewable client_cert_subject client_cert_fuid server_cert_subject server_cert_fuid auth_ticket new_ticket #types time string addr port addr port string string string bool string time time string bool bool string string string string string string -1429583645.478441 CHhAvVGS1DHFjwGM9 192.168.1.31 64889 192.168.1.32 88 TGS vladg/VLADG.NET krbtgt/VLADG.NET T - - 0.000000 aes256-cts-hmac-sha1-96 T F - - - - ea0f395c3823e8cc7216d82e2405b428 fa8064009b03606f84e475feca5dcd12 -#close 2017-02-18-18-38-58 +1429583645.478441 CHhAvVGS1DHFjwGM9 192.168.1.31 64889 192.168.1.32 88 TGS vladg/VLADG.NET krbtgt/VLADG.NET T - - 0.000000 aes256-cts-hmac-sha1-96 T F - - - - a09fbd89918320cc12a26d4f0c4e6aa2 396a9d9e8975cc5024a83c6e86101f06 +#close 2017-02-22-05-02-14 From ae6dbf17a23ecaf7ed2c38068d3fd8f3373928bb Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Thu, 23 Feb 2017 09:59:48 -0800 Subject: [PATCH 20/44] Input Manager: tiny error message fix. --- src/input/Manager.cc | 2 +- .../scripts.base.frameworks.input.missing-enum/bro..stderr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/input/Manager.cc b/src/input/Manager.cc index b84d822101..d029e38092 100644 --- a/src/input/Manager.cc +++ b/src/input/Manager.cc @@ -2380,7 +2380,7 @@ Val* Manager::ValueToVal(const Stream* i, const Value* val, BroType* request_typ bro_int_t index = request_type->AsEnumType()->Lookup(module, var.c_str()); if ( index == -1 ) { - Warning(i, "Value not '%s' for stream '%s' is not a valid enum.", + Warning(i, "Value '%s' for stream '%s' is not a valid enum.", enum_string.c_str(), i->name.c_str()); have_error = true; diff --git a/testing/btest/Baseline/scripts.base.frameworks.input.missing-enum/bro..stderr b/testing/btest/Baseline/scripts.base.frameworks.input.missing-enum/bro..stderr index 20207bcf94..8cd0c5ab6c 100644 --- a/testing/btest/Baseline/scripts.base.frameworks.input.missing-enum/bro..stderr +++ b/testing/btest/Baseline/scripts.base.frameworks.input.missing-enum/bro..stderr @@ -1,2 +1,2 @@ -warning: Value not 'IdoNot::Exist' for stream 'enum' is not a valid enum. +warning: Value 'IdoNot::Exist' for stream 'enum' is not a valid enum. received termination signal From e0a72b6e5c7bd9ad0b839d8a4b9297bb8a0211f7 Mon Sep 17 00:00:00 2001 From: Robin Sommer Date: Thu, 23 Feb 2017 10:18:57 -0800 Subject: [PATCH 21/44] Updating submodule. --- CHANGES | 8 ++++++++ VERSION | 2 +- aux/btest | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 94f310e6b8..b9cef9e78a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,12 @@ +2.5-76 | 2017-02-23 10:19:57 -0800 + + * Kerberos ciphertext had some additional ASN.1 content being lumped + in. (Vlad Grigorescu) + + * Updated Windows version detection to include Windows 10. (Fatema + Bannatwala, Keith Lehigh, Mike, Seth Hall). + 2.5-70 | 2017-02-20 00:20:02 -0500 * Rework the RADIUS base script. diff --git a/VERSION b/VERSION index b130560010..2087ec374d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.5-70 +2.5-76 diff --git a/aux/btest b/aux/btest index 9d5c7bcac9..2fb0e089b3 160000 --- a/aux/btest +++ b/aux/btest @@ -1 +1 @@ -Subproject commit 9d5c7bcac9b04710931bc8a42b545f0691561b2f +Subproject commit 2fb0e089b37cc62b4616c8d4309ee80fb7fc6a27 From 5b7636619984b5fd4a56e13f13bf973d014d2b59 Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Thu, 23 Feb 2017 15:00:13 -0800 Subject: [PATCH 22/44] Plugin: add/fix documentation for HookSetupAnalyzerTree --- src/plugin/Plugin.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/plugin/Plugin.h b/src/plugin/Plugin.h index 54451dcfb6..49fa7cdd84 100644 --- a/src/plugin/Plugin.h +++ b/src/plugin/Plugin.h @@ -39,7 +39,7 @@ enum HookType { HOOK_DRAIN_EVENTS, //< Activates Plugin::HookDrainEvents() HOOK_UPDATE_NETWORK_TIME, //< Activates Plugin::HookUpdateNetworkTime. HOOK_BRO_OBJ_DTOR, //< Activates Plugin::HookBroObjDtor. - HOOK_SETUP_ANALYZER_TREE, //< Activates Plugin::HookAddToAnalyzerTree + HOOK_SETUP_ANALYZER_TREE, //< Activates Plugin::HookSetupAnalyzerTree // Meta hooks. META_HOOK_PRE, //< Activates Plugin::MetaHookPre(). @@ -642,6 +642,13 @@ protected: */ virtual void HookUpdateNetworkTime(double network_time); + /** + * Hook that executes when a connection's initial analyzer tree + * has been fully set up. The hook can manipulate the tree at this time, + * for example by adding further analyzers. + * + * @param conn The connection. + */ virtual void HookSetupAnalyzerTree(Connection *conn); /** From 5cf7803e685eb147808934a372538c6301035f97 Mon Sep 17 00:00:00 2001 From: Robin Sommer Date: Thu, 23 Feb 2017 17:18:43 -0800 Subject: [PATCH 23/44] Fix some minor issues. From Daniel, thanks! --- aux/binpac | 2 +- aux/bro-aux | 2 +- aux/broccoli | 2 +- aux/broctl | 2 +- aux/broker | 2 +- aux/btest | 2 +- aux/plugins | 2 +- src/broker/Data.cc | 11 +++++------ src/broker/Data.h | 2 +- src/broker/Manager.cc | 11 ++++++----- src/broker/Manager.h | 10 +++++----- src/logging/Manager.h | 18 +++++++++--------- src/logging/WriterBackend.cc | 4 ++-- src/logging/WriterFrontend.h | 4 +--- 14 files changed, 36 insertions(+), 38 deletions(-) diff --git a/aux/binpac b/aux/binpac index a0990e61ad..0f1ecfa972 160000 --- a/aux/binpac +++ b/aux/binpac @@ -1 +1 @@ -Subproject commit a0990e61ad4a3705bda4cc5a20059af2d1bda4c3 +Subproject commit 0f1ecfa97236635fb93e013404e6b30d6c506ddd diff --git a/aux/bro-aux b/aux/bro-aux index 7660b5f4c5..b1e75f6a21 160000 --- a/aux/bro-aux +++ b/aux/bro-aux @@ -1 +1 @@ -Subproject commit 7660b5f4c5be40aa5f3a7c8746fdcf68331f9b93 +Subproject commit b1e75f6a212250b1730a438f27fc778618b67ec3 diff --git a/aux/broccoli b/aux/broccoli index 765eab50f7..ed52e3414b 160000 --- a/aux/broccoli +++ b/aux/broccoli @@ -1 +1 @@ -Subproject commit 765eab50f7796fdb3c308fe9232cd7891f098c67 +Subproject commit ed52e3414b31b05ec9abed627b4153c8e2243441 diff --git a/aux/broctl b/aux/broctl index f6d451520e..73dbc79ac2 160000 --- a/aux/broctl +++ b/aux/broctl @@ -1 +1 @@ -Subproject commit f6d451520eaaaae97aab6df2bb4e0aecb6b63e66 +Subproject commit 73dbc79ac24cdfef07d8574a4da5d43056ba5fa5 diff --git a/aux/broker b/aux/broker index 68a36ed814..23def70c44 160000 --- a/aux/broker +++ b/aux/broker @@ -1 +1 @@ -Subproject commit 68a36ed81480ba935268bcaf7b6f2249d23436da +Subproject commit 23def70c44128d19138029615dd154359286e111 diff --git a/aux/btest b/aux/btest index 32e582514a..2fb0e089b3 160000 --- a/aux/btest +++ b/aux/btest @@ -1 +1 @@ -Subproject commit 32e582514ae044befa8e0511083bf11a51408a1d +Subproject commit 2fb0e089b37cc62b4616c8d4309ee80fb7fc6a27 diff --git a/aux/plugins b/aux/plugins index 0a2f021527..2322840bcd 160000 --- a/aux/plugins +++ b/aux/plugins @@ -1 +1 @@ -Subproject commit 0a2f0215270e6ceaf9c1312f705b95d2cce1b530 +Subproject commit 2322840bcdbd618ae7bd24e22d874fb30ab89bbb diff --git a/src/broker/Data.cc b/src/broker/Data.cc index 35d815b9e8..5da6f5d4e0 100644 --- a/src/broker/Data.cc +++ b/src/broker/Data.cc @@ -776,7 +776,6 @@ static broker::util::optional threading_val_to_data_internal(TypeT auto s = broker::address(reinterpret_cast(&tmp), broker::address::family::ipv6, broker::address::byte_order::network); - fprintf(stderr, "%d\n", val.subnet_val.length); return {broker::subnet(s, length)}; } @@ -863,7 +862,7 @@ struct threading_val_converter { using result_type = bool; TypeTag type; - threading::Value::_val& val; + threading::Value::_val& val; result_type operator()(bool a) { @@ -1053,9 +1052,9 @@ struct threading_val_converter { if ( type == TYPE_VECTOR ) { val.vector_val.size = a.size(); - val.vector_val.vals = new threading::Value* [val.set_val.size]; + val.vector_val.vals = new threading::Value* [val.vector_val.size]; - auto p = val.set_val.vals; + auto p = val.vector_val.vals; for ( auto& i : a ) *p++ = bro_broker::data_to_threading_val(move(i)); @@ -1079,8 +1078,8 @@ threading::Value* bro_broker::data_to_threading_val(broker::data d) if ( ! r ) return nullptr; - auto type = broker::get(*r->get(0));; - auto present = broker::get(*r->get(1));; + auto type = broker::get(*r->get(0)); + auto present = broker::get(*r->get(1)); auto data = *r->get(2); if ( ! (type && present) ) diff --git a/src/broker/Data.h b/src/broker/Data.h index 9e0c8120de..526304b00c 100644 --- a/src/broker/Data.h +++ b/src/broker/Data.h @@ -70,7 +70,7 @@ broker::util::optional threading_val_to_data(const threading::Valu /** * Convert a Bro threading::Field to a Broker data value. - * @param v a Bro threading::Field. + * @param f a Bro threading::Field. * @return a Broker data value if the Bro threading::Field could be converted to one. */ broker::data threading_field_to_data(const threading::Field* f); diff --git a/src/broker/Manager.cc b/src/broker/Manager.cc index 76040b129c..eebcd2792f 100644 --- a/src/broker/Manager.cc +++ b/src/broker/Manager.cc @@ -214,9 +214,10 @@ bool bro_broker::Manager::Event(std::string topic, broker::message msg, int flag return true; } -bool bro_broker::Manager::CreateLog(EnumVal* stream, EnumVal* writer, const logging::WriterBackend::WriterInfo& info, - int num_fields, const threading::Field* const * fields, int flags, - const string& peer) +bool bro_broker::Manager::CreateLog(EnumVal* stream, EnumVal* writer, + const logging::WriterBackend::WriterInfo& info, + int num_fields, const threading::Field* const * fields, + int flags, const string& peer) { if ( ! Enabled() ) return false; @@ -1003,7 +1004,7 @@ bool bro_broker::Manager::ProcessCreateLog(broker::message msg) if ( ! broker::get(msg[idx]) ) { - reporter->Warning("got remote log create w/o stream id: %d", + reporter->Warning("got remote log create w/o stream id: %d", static_cast(broker::which(msg[idx]))); return false; } @@ -1023,7 +1024,7 @@ bool bro_broker::Manager::ProcessCreateLog(broker::message msg) if ( ! broker::get(msg[idx]) ) { - reporter->Warning("got remote log w/o writer id: %d", + reporter->Warning("got remote log create w/o writer id: %d", static_cast(broker::which(msg[idx]))); return false; } diff --git a/src/broker/Manager.h b/src/broker/Manager.h index 10d5019b74..d5701c56b1 100644 --- a/src/broker/Manager.h +++ b/src/broker/Manager.h @@ -169,20 +169,20 @@ public: const threading::Value* const * vals, int flags); /** - * Send a message to create a log stream to any interested peers. - * The log stream may or may not already exist on the receiving side. - * The topic name used is implicitly "bro/log/". + * Send a message to create a log stream to any interested peers. + * The log stream may or may not already exist on the receiving side. + * The topic name used is implicitly "bro/log/". * @param stream the stream to which the log entry belongs. * @param writer the writer to use for outputting this log entry. * @param info backend initialization information for the writer. * @param num_fields the number of fields the log has. - * @param fields the log's fields of size num_fields. + * @param fields the log's fields, of size num_fields. * @param flags tune the behavior of how the message is send. * See the Broker::SendFlags record type. * @param peer If given, send the message only to this peer. * @return true if the message is sent successfully. */ - bool CreateLog(EnumVal* id, EnumVal* writer, const logging::WriterBackend::WriterInfo& info, + bool CreateLog(EnumVal* stream, EnumVal* writer, const logging::WriterBackend::WriterInfo& info, int num_fields, const threading::Field* const * fields, int flags, const string& peer = ""); /** diff --git a/src/logging/Manager.h b/src/logging/Manager.h index 3334942a3a..56626f39d3 100644 --- a/src/logging/Manager.h +++ b/src/logging/Manager.h @@ -133,9 +133,9 @@ public: * Create a new log writer frontend. This is exposed so that the * communication system can recreate remote log streams locally. * - * @param stream The enum value corresponding the log stream. + * @param id The enum value corresponding to the log stream. * - * @param writer The enum value corresponding the desired log writer. + * @param writer The enum value corresponding to the desired log writer. * * @param info A fully initialized object defining the * characteristics of the backend writer instance. The method takes @@ -143,8 +143,8 @@ public: * * @param num_fields The number of log fields to write. * - * @param vals An arry of log fields to write, of size num_fields. - * The method takes ownership of the arry. + * @param vals An array of log fields to write, of size num_fields. + * The method takes ownership of the array. * * @return Returns true if the writer was successfully created. */ @@ -154,18 +154,18 @@ public: /** * Writes out log entries that have already passed through all * filters (and have raised any events). This is meant called for logs - * received alrready processed from remote. + * received already processed from remote. * - * @param stream The enum value corresponding the log stream. + * @param stream The enum value corresponding to the log stream. * - * @param writer The enum value corresponding the desired log writer. + * @param writer The enum value corresponding to the desired log writer. * * @param path The path of the target log stream to write to. * * @param num_fields The number of log values to write. * - * @param vals An arry of log values to write, of size num_fields. - * The method takes ownership of the arry. + * @param vals An array of log values to write, of size num_fields. + * The method takes ownership of the array. */ bool WriteFromRemote(EnumVal* stream, EnumVal* writer, string path, int num_fields, threading::Value** vals); diff --git a/src/logging/WriterBackend.cc b/src/logging/WriterBackend.cc index e2afd360e7..ebf5165169 100644 --- a/src/logging/WriterBackend.cc +++ b/src/logging/WriterBackend.cc @@ -130,11 +130,11 @@ broker::data WriterBackend::WriterInfo::ToBroker() const auto t = broker::table(); for ( config_map::const_iterator i = config.begin(); i != config.end(); ++i ) - { + { auto key = std::string(i->first); auto value = std::string(i->second); t.insert(std::make_pair(key, value)); - } + } auto bconfig = broker::record::field(move(t)); diff --git a/src/logging/WriterFrontend.h b/src/logging/WriterFrontend.h index c39d2bdf3c..ae37613261 100644 --- a/src/logging/WriterFrontend.h +++ b/src/logging/WriterFrontend.h @@ -34,8 +34,6 @@ public: * * info: The meta information struct for the writer. * - * writer_name: A descriptive name for the writer's type. - * * local: If true, the writer will instantiate a local backend. * * remote: If true, the writer will forward logs to remote @@ -46,7 +44,7 @@ public: * * Frontends must only be instantiated by the main thread. */ - WriterFrontend(const WriterBackend::WriterInfo& info, EnumVal* stream, EnumVal* writer, bool local, bool remote, int arg_remote_flags); + WriterFrontend(const WriterBackend::WriterInfo& info, EnumVal* stream, EnumVal* writer, bool local, bool remote, int remote_flags); /** * Destructor. From 75744d22bc1d556c3c9a42cb458af8ce181f1536 Mon Sep 17 00:00:00 2001 From: Seth Hall Date: Thu, 23 Feb 2017 23:13:12 -0500 Subject: [PATCH 24/44] Input's ascii reader is now more resilient. By default, the ASCII reader does not fail on errors anymore. If there is a problem parsing a line, a reporter warning is written and parsing continues. If the file is missing or can't be read, the input thread just tries again on the next heartbeat. Options have been added to recreate the previous behavior... const InputAscii::fail_on_invalid_lines: bool; and const InputAscii::fail_on_file_problem: bool; They are both set to `F` by default which makes the input readers resilient to failure. --- .../base/frameworks/input/readers/ascii.bro | 13 +++ src/input/readers/ascii/Ascii.cc | 80 ++++++++++++------- src/input/readers/ascii/Ascii.h | 7 +- src/input/readers/ascii/ascii.bif | 2 + .../bro..stderr | 4 + .../bro..stdout | 2 + .../bro..stderr | 4 +- .../input/missing-file-initially.bro | 47 +++++++++++ 8 files changed, 127 insertions(+), 32 deletions(-) create mode 100644 testing/btest/Baseline/scripts.base.frameworks.input.missing-file-initially/bro..stderr create mode 100644 testing/btest/Baseline/scripts.base.frameworks.input.missing-file-initially/bro..stdout create mode 100644 testing/btest/scripts/base/frameworks/input/missing-file-initially.bro diff --git a/scripts/base/frameworks/input/readers/ascii.bro b/scripts/base/frameworks/input/readers/ascii.bro index 1b486ddba0..521e8d4ca3 100644 --- a/scripts/base/frameworks/input/readers/ascii.bro +++ b/scripts/base/frameworks/input/readers/ascii.bro @@ -18,4 +18,17 @@ export { ## String to use for an unset &optional field. const unset_field = Input::unset_field &redef; + + ## Choose if the ascii input reader should globally + ## fail on invalid lines and continue parsing afterward. + ## Individual readers can use a different value. + const fail_on_invalid_lines = F &redef; + + ## Set to true if you would like the old behavior of the + ## ascii reader where the reader thread would die if any file + ## errors occur (like permissions problems or file missing). + ## The default behavior is to continue attempting to open and read + ## the file even in light of problems. + ## Individual readers can use a different value. + const fail_on_file_problem = F &redef; } diff --git a/src/input/readers/ascii/Ascii.cc b/src/input/readers/ascii/Ascii.cc index 2f91ebe27a..5621a87eb4 100644 --- a/src/input/readers/ascii/Ascii.cc +++ b/src/input/readers/ascii/Ascii.cc @@ -61,7 +61,6 @@ void Ascii::DoClose() bool Ascii::DoInit(const ReaderInfo& info, int num_fields, const Field* const* fields) { - continue_on_failure = true; is_failed = false; separator.assign( (const char*) BifConst::InputAscii::separator->Bytes(), @@ -76,6 +75,9 @@ bool Ascii::DoInit(const ReaderInfo& info, int num_fields, const Field* const* f unset_field.assign( (const char*) BifConst::InputAscii::unset_field->Bytes(), BifConst::InputAscii::unset_field->Len()); + fail_on_invalid_lines = BifConst::InputAscii::fail_on_invalid_lines; + fail_on_file_problem = BifConst::InputAscii::fail_on_file_problem; + // Set per-filter configuration options. for ( ReaderInfo::config_map::const_iterator i = info.config.begin(); i != info.config.end(); i++ ) { @@ -90,6 +92,12 @@ bool Ascii::DoInit(const ReaderInfo& info, int num_fields, const Field* const* f else if ( strcmp(i->first, "unset_field") == 0 ) unset_field.assign(i->second); + + else if ( strcmp(i->first, "fail_on_invalid_lines") == 0 ) + fail_on_invalid_lines = (strncmp(i->second, "T", 1) == 0); + + else if ( strcmp(i->first, "fail_on_file_problem") == 0 ) + fail_on_file_problem = (strncmp(i->second, "T", 1) == 0); } if ( separator.size() != 1 ) @@ -101,34 +109,40 @@ bool Ascii::DoInit(const ReaderInfo& info, int num_fields, const Field* const* f formatter::Ascii::SeparatorInfo sep_info(separator, set_separator, unset_field, empty_field); formatter = unique_ptr(new formatter::Ascii(this, sep_info)); - OpenFile(); - - if ( is_failed ) - return continue_on_failure; - DoUpdate(); return true; } +void Ascii::FailWarn(bool is_error, const char *msg) + { + if ( is_error ) + Error(msg); + else + Warning(msg); + } + bool Ascii::OpenFile() { + if ( file.is_open() && ! is_failed ) + return true; + file.open(Info().source); if ( ! file.is_open() ) { if ( ! is_failed ) - Warning(Fmt("Init: cannot open %s", Info().source)); + FailWarn(fail_on_file_problem, Fmt("Init: cannot open %s", Info().source)); is_failed = true; - return continue_on_failure; + return !fail_on_file_problem; } if ( ReadHeader(false) == false ) { if ( ! is_failed ) - Warning(Fmt("Init: cannot open %s; headers are incorrect", Info().source)); + FailWarn(fail_on_file_problem, Fmt("Init: cannot open %s; headers are incorrect", Info().source)); file.close(); is_failed = true; - return continue_on_failure; + return !fail_on_file_problem; } is_failed = false; @@ -141,7 +155,7 @@ bool Ascii::ReadHeader(bool useCached) string line; map ifields; - if ( headerline == "" ) + if ( ! useCached ) { if ( ! GetLine(line) ) return false; @@ -184,11 +198,12 @@ bool Ascii::ReadHeader(bool useCached) continue; } - Warning(Fmt("Did not find requested field %s in input data file %s.", - field->name, Info().source)); + if ( ! is_failed ) + FailWarn(fail_on_file_problem, Fmt("Did not find requested field %s in input data file %s.", + field->name, Info().source)); is_failed = true; - return continue_on_failure; + return !fail_on_file_problem; } FieldMapping f(field->name, field->type, field->subtype, ifields[field->name]); @@ -198,11 +213,12 @@ bool Ascii::ReadHeader(bool useCached) map::iterator fit2 = ifields.find(field->secondary_name); if ( fit2 == ifields.end() ) { - Warning(Fmt("Could not find requested port type field %s in input data file.", - field->secondary_name)); + if ( ! is_failed ) + FailWarn(fail_on_file_problem, Fmt("Could not find requested port type field %s in input data file.", + field->secondary_name)); is_failed = true; - return continue_on_failure; + return !fail_on_file_problem; } f.secondary_position = ifields[field->secondary_name]; @@ -241,6 +257,9 @@ bool Ascii::GetLine(string& str) // read the entire file and send appropriate thingies back to InputMgr bool Ascii::DoUpdate() { + if ( ! OpenFile() ) + return !fail_on_file_problem; + switch ( Info().mode ) { case MODE_REREAD: { @@ -248,10 +267,11 @@ bool Ascii::DoUpdate() struct stat sb; if ( stat(Info().source, &sb) == -1 ) { - Warning(Fmt("Could not get stat for %s", Info().source)); + if ( ! is_failed ) + FailWarn(fail_on_file_problem, Fmt("Could not get stat for %s", Info().source)); file.close(); is_failed = true; - return continue_on_failure; + return !fail_on_file_problem; } if ( sb.st_mtime <= mtime ) // no change @@ -276,7 +296,7 @@ bool Ascii::DoUpdate() if ( !ReadHeader(true) ) { is_failed = true; - return continue_on_failure; // header reading failed + return !fail_on_file_problem; // header reading failed } break; @@ -339,7 +359,7 @@ bool Ascii::DoUpdate() if ( (*fit).position > pos || (*fit).secondary_position > pos ) { - Warning(Fmt("Not enough fields in line %s. Found %d fields, want positions %d and %d", + FailWarn(fail_on_invalid_lines, Fmt("Not enough fields in line %s. Found %d fields, want positions %d and %d", line.c_str(), pos, (*fit).position, (*fit).secondary_position)); for ( int i = 0; i < fpos; i++ ) @@ -347,8 +367,15 @@ bool Ascii::DoUpdate() delete [] fields; - is_failed = true; - return continue_on_failure; + if ( fail_on_invalid_lines ) + { + return false; + } + else + { + error = true; + break; + } } Value* val = formatter->ParseValue(stringfields[(*fit).position], (*fit).name, (*fit).type, (*fit).subtype); @@ -404,11 +431,8 @@ bool Ascii::DoUpdate() bool Ascii::DoHeartbeat(double network_time, double current_time) { - if ( ! file.is_open() ) - OpenFile(); - - if ( is_failed ) - return continue_on_failure; + if ( ! OpenFile() ) + return !fail_on_file_problem; switch ( Info().mode ) { diff --git a/src/input/readers/ascii/Ascii.h b/src/input/readers/ascii/Ascii.h index c9fd55f3d5..9300382ca2 100644 --- a/src/input/readers/ascii/Ascii.h +++ b/src/input/readers/ascii/Ascii.h @@ -56,6 +56,8 @@ private: bool ReadHeader(bool useCached); bool GetLine(string& str); bool OpenFile(); + void FailWarn(bool is_error, const char *msg); + ifstream file; time_t mtime; @@ -71,8 +73,11 @@ private: string set_separator; string empty_field; string unset_field; + bool fail_on_invalid_lines; + bool fail_on_file_problem; - bool continue_on_failure; + // this is an internal indicator in case the read is currently in a failed state + // it's used by the options for continuing instead of failing and killing the reader. bool is_failed; std::unique_ptr formatter; diff --git a/src/input/readers/ascii/ascii.bif b/src/input/readers/ascii/ascii.bif index 8bb3a96492..80ff4611e7 100644 --- a/src/input/readers/ascii/ascii.bif +++ b/src/input/readers/ascii/ascii.bif @@ -5,3 +5,5 @@ const separator: string; const set_separator: string; const empty_field: string; const unset_field: string; +const fail_on_invalid_lines: bool; +const fail_on_file_problem: bool; diff --git a/testing/btest/Baseline/scripts.base.frameworks.input.missing-file-initially/bro..stderr b/testing/btest/Baseline/scripts.base.frameworks.input.missing-file-initially/bro..stderr new file mode 100644 index 0000000000..6c4d0f0194 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.frameworks.input.missing-file-initially/bro..stderr @@ -0,0 +1,4 @@ +warning: ../does-not-exist.dat/Input::READER_ASCII: Init: cannot open ../does-not-exist.dat +error: ../does-not-exist.dat/Input::READER_ASCII: Init: cannot open ../does-not-exist.dat +error: ../does-not-exist.dat/Input::READER_ASCII: terminating thread +received termination signal diff --git a/testing/btest/Baseline/scripts.base.frameworks.input.missing-file-initially/bro..stdout b/testing/btest/Baseline/scripts.base.frameworks.input.missing-file-initially/bro..stdout new file mode 100644 index 0000000000..d96c6d457d --- /dev/null +++ b/testing/btest/Baseline/scripts.base.frameworks.input.missing-file-initially/bro..stdout @@ -0,0 +1,2 @@ +now it does +and more! diff --git a/testing/btest/Baseline/scripts.base.frameworks.input.missing-file/bro..stderr b/testing/btest/Baseline/scripts.base.frameworks.input.missing-file/bro..stderr index 5093925d2d..f5d074fe7e 100644 --- a/testing/btest/Baseline/scripts.base.frameworks.input.missing-file/bro..stderr +++ b/testing/btest/Baseline/scripts.base.frameworks.input.missing-file/bro..stderr @@ -1,4 +1,2 @@ -error: does-not-exist.dat/Input::READER_ASCII: Init: cannot open does-not-exist.dat -error: does-not-exist.dat/Input::READER_ASCII: Init failed -error: does-not-exist.dat/Input::READER_ASCII: terminating thread +warning: does-not-exist.dat/Input::READER_ASCII: Init: cannot open does-not-exist.dat received termination signal diff --git a/testing/btest/scripts/base/frameworks/input/missing-file-initially.bro b/testing/btest/scripts/base/frameworks/input/missing-file-initially.bro new file mode 100644 index 0000000000..a7128a4455 --- /dev/null +++ b/testing/btest/scripts/base/frameworks/input/missing-file-initially.bro @@ -0,0 +1,47 @@ +# This tests files that don't exist initially and then do later during +# runtime to make sure the ascii reader is resilient to files missing. +# It does a second test at the same time which configures the old +# failing behavior. + +# @TEST-EXEC: btest-bg-run bro bro %INPUT +# @TEST-EXEC: btest-bg-wait -k 5 +# @TEST-EXEC: btest-diff bro/.stdout +# @TEST-EXEC: btest-diff bro/.stderr + +@TEST-START-FILE does-exist.dat +#separator \x09 +#fields line +#types string +now it does +and more! +@TEST-END-FILE + +redef exit_only_after_terminate = T; + +@load base/frameworks/input + +module A; + +type Val: record { + line: string; +}; + +event line(description: Input::EventDescription, tpe: Input::Event, v: Val) + { + print v$line; + } + +event line2(description: Input::EventDescription, tpe: Input::Event, v: Val) + { + print "DONT PRINT THIS LINE"; + } + + +event bro_init() + { + Input::add_event([$source="../does-not-exist.dat", $name="input", $reader=Input::READER_ASCII, $mode=Input::REREAD, $fields=Val, $ev=line, $want_record=T]); + Input::add_event([$source="../does-not-exist.dat", $name="input2", $reader=Input::READER_ASCII, $mode=Input::REREAD, $fields=Val, $ev=line2, $want_record=T, + $config=table(["fail_on_file_problem"] = "T")]); + + system("sleep 2; mv ../does-exist.dat ../does-not-exist.dat;"); + } From 50781590804ee0ba2e3d54b186478f4613fa61a0 Mon Sep 17 00:00:00 2001 From: Seth Hall Date: Thu, 23 Feb 2017 23:13:48 -0500 Subject: [PATCH 25/44] Tiny fix to correct a warning message. --- src/input/Manager.cc | 2 +- .../scripts.base.frameworks.input.missing-enum/bro..stderr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/input/Manager.cc b/src/input/Manager.cc index b84d822101..d029e38092 100644 --- a/src/input/Manager.cc +++ b/src/input/Manager.cc @@ -2380,7 +2380,7 @@ Val* Manager::ValueToVal(const Stream* i, const Value* val, BroType* request_typ bro_int_t index = request_type->AsEnumType()->Lookup(module, var.c_str()); if ( index == -1 ) { - Warning(i, "Value not '%s' for stream '%s' is not a valid enum.", + Warning(i, "Value '%s' for stream '%s' is not a valid enum.", enum_string.c_str(), i->name.c_str()); have_error = true; diff --git a/testing/btest/Baseline/scripts.base.frameworks.input.missing-enum/bro..stderr b/testing/btest/Baseline/scripts.base.frameworks.input.missing-enum/bro..stderr index 20207bcf94..8cd0c5ab6c 100644 --- a/testing/btest/Baseline/scripts.base.frameworks.input.missing-enum/bro..stderr +++ b/testing/btest/Baseline/scripts.base.frameworks.input.missing-enum/bro..stderr @@ -1,2 +1,2 @@ -warning: Value not 'IdoNot::Exist' for stream 'enum' is not a valid enum. +warning: Value 'IdoNot::Exist' for stream 'enum' is not a valid enum. received termination signal From 22c89a83f5cf95ed77dcde11898a7bc55df54499 Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Fri, 24 Feb 2017 09:02:16 -0800 Subject: [PATCH 26/44] Update submodule [nomail] --- aux/bro-aux | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aux/bro-aux b/aux/bro-aux index b1e75f6a21..9f33570d53 160000 --- a/aux/bro-aux +++ b/aux/bro-aux @@ -1 +1 @@ -Subproject commit b1e75f6a212250b1730a438f27fc778618b67ec3 +Subproject commit 9f33570d53e1b970d7905e305940fd55637c5c76 From 0f695a7316fe2586fb1010675f45d549ffd00568 Mon Sep 17 00:00:00 2001 From: Daniel Thayer Date: Sat, 25 Feb 2017 21:53:02 -0600 Subject: [PATCH 27/44] Fix a test that sometimes fails on FreeBSD --- testing/btest/istate/pybroccoli.py | 1 + 1 file changed, 1 insertion(+) diff --git a/testing/btest/istate/pybroccoli.py b/testing/btest/istate/pybroccoli.py index 7600c2b7d4..0d7106d592 100644 --- a/testing/btest/istate/pybroccoli.py +++ b/testing/btest/istate/pybroccoli.py @@ -4,6 +4,7 @@ # @TEST-REQUIRES: test -e $BUILD/aux/broccoli/bindings/broccoli-python/_broccoli_intern.so # # @TEST-EXEC: btest-bg-run bro bro %INPUT $DIST/aux/broccoli/bindings/broccoli-python/tests/test.bro +# @TEST-EXEC: sleep 2 # @TEST-EXEC: btest-bg-run python PYTHONPATH=$DIST/aux/broccoli/bindings/broccoli-python/:$BUILD/aux/broccoli/bindings/broccoli-python python $DIST/aux/broccoli/bindings/broccoli-python/tests/test.py # @TEST-EXEC: btest-bg-wait -k 20 # @TEST-EXEC: btest-diff bro/.stdout From 58a2d06c93189cea3f91efe2566fbe1703b9dc72 Mon Sep 17 00:00:00 2001 From: Robin Sommer Date: Mon, 27 Feb 2017 08:22:16 -0800 Subject: [PATCH 28/44] Another fix for the new Broker-based remote logging. --- src/broker/Data.cc | 7 ++-- .../Baseline/broker.remote_log/recv.test.log | 4 +-- .../Baseline/broker.remote_log/send.test.log | 4 +-- testing/btest/broker/remote_log.test | 33 ++++++++++--------- testing/btest/broker/remote_log_types.test | 3 +- 5 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/broker/Data.cc b/src/broker/Data.cc index 5da6f5d4e0..6420144193 100644 --- a/src/broker/Data.cc +++ b/src/broker/Data.cc @@ -1080,16 +1080,19 @@ threading::Value* bro_broker::data_to_threading_val(broker::data d) auto type = broker::get(*r->get(0)); auto present = broker::get(*r->get(1)); - auto data = *r->get(2); + auto data = r->get(2); if ( ! (type && present) ) return nullptr; + if ( *present && ! data ) + return nullptr; + auto tv = new threading::Value; tv->type = static_cast(*type); tv->present = *present; - if ( present && ! broker::visit(threading_val_converter{tv->type, tv->val}, data) ) + if ( *present && ! broker::visit(threading_val_converter{tv->type, tv->val}, *data) ) { delete tv; return nullptr; diff --git a/testing/btest/Baseline/broker.remote_log/recv.test.log b/testing/btest/Baseline/broker.remote_log/recv.test.log index 0d6dae756c..b79e1b53b9 100644 --- a/testing/btest/Baseline/broker.remote_log/recv.test.log +++ b/testing/btest/Baseline/broker.remote_log/recv.test.log @@ -3,7 +3,7 @@ #empty_field (empty) #unset_field - #path test -#open 2015-01-26-22-47-11 +#open 2017-02-27-16-21-20 #fields msg num #types string count ping 0 @@ -12,4 +12,4 @@ ping 2 ping 3 ping 4 ping 5 -#close 2015-01-26-22-47-11 +#close 2017-02-27-16-21-20 diff --git a/testing/btest/Baseline/broker.remote_log/send.test.log b/testing/btest/Baseline/broker.remote_log/send.test.log index 0d6dae756c..93862c656b 100644 --- a/testing/btest/Baseline/broker.remote_log/send.test.log +++ b/testing/btest/Baseline/broker.remote_log/send.test.log @@ -3,7 +3,7 @@ #empty_field (empty) #unset_field - #path test -#open 2015-01-26-22-47-11 +#open 2017-02-27-16-21-19 #fields msg num #types string count ping 0 @@ -12,4 +12,4 @@ ping 2 ping 3 ping 4 ping 5 -#close 2015-01-26-22-47-11 +#close 2017-02-27-16-21-20 diff --git a/testing/btest/broker/remote_log.test b/testing/btest/broker/remote_log.test index 598f3f6e46..df8f8d8d8a 100644 --- a/testing/btest/broker/remote_log.test +++ b/testing/btest/broker/remote_log.test @@ -45,14 +45,14 @@ redef exit_only_after_terminate = T; event bro_init() { Broker::subscribe_to_logs("bro/log/"); - Broker::subscribe_to_events("bro/event/"); + Broker::subscribe_to_events("bro/event/"); Broker::listen(broker_port, "127.0.0.1"); } event quit_receiver() - { - terminate(); - } + { + terminate(); + } @TEST-END-FILE @@ -63,21 +63,22 @@ const broker_port: port &redef; redef exit_only_after_terminate = T; event bro_init() - { - Broker::enable_remote_logs(Test::LOG); - Broker::connect("127.0.0.1", broker_port, 1secs); - } + { + Broker::enable_remote_logs(Test::LOG); + Broker::publish_topic("bro/event/"); + Broker::connect("127.0.0.1", broker_port, 1secs); + } global n = 0; event do_write() { if ( n == 6 ) - { - local args = Broker::event_args(quit_receiver); - Broker::send_event("bro/event/", args); - schedule 1sec { quit_sender() }; - } + { + local args = Broker::event_args(quit_receiver); + Broker::send_event("bro/event/", args); + schedule 1sec { quit_sender() }; + } else { Log::write(Test::LOG, [$msg = "ping", $num = n]); @@ -87,9 +88,9 @@ event do_write() } event quit_sender() - { - terminate(); - } + { + terminate(); + } event Broker::outgoing_connection_established(peer_address: string, peer_port: port, diff --git a/testing/btest/broker/remote_log_types.test b/testing/btest/broker/remote_log_types.test index 9089e087cb..48011a8c07 100644 --- a/testing/btest/broker/remote_log_types.test +++ b/testing/btest/broker/remote_log_types.test @@ -60,7 +60,7 @@ redef exit_only_after_terminate = T; event bro_init() { Broker::subscribe_to_logs("bro/log/"); - Broker::subscribe_to_events("bro/event/"); + Broker::subscribe_to_events("bro/event/"); Broker::listen(broker_port, "127.0.0.1"); } @@ -80,6 +80,7 @@ redef exit_only_after_terminate = T; event bro_init() { Broker::enable_remote_logs(Test::LOG); + Broker::publish_topic("bro/event/"); Broker::connect("127.0.0.1", broker_port, 1secs); } From 01a39436353d6df202b152e307b6453c990c7fd2 Mon Sep 17 00:00:00 2001 From: Seth Hall Date: Tue, 28 Feb 2017 12:40:01 -0500 Subject: [PATCH 29/44] Do some updates to remove build time warnings. The linker was complaining about linking files that didn't have any symbols. These were actually empty files so I just got rid of them and removed references to them. --- src/analyzer/protocol/ayiya/AYIYA.cc | 1 - src/analyzer/protocol/ayiya/CMakeLists.txt | 1 - src/analyzer/protocol/ayiya/ayiya.pac | 4 ---- src/analyzer/protocol/ayiya/events.bif | 0 src/analyzer/protocol/gssapi/CMakeLists.txt | 2 +- src/analyzer/protocol/gssapi/gssapi.pac | 1 - src/analyzer/protocol/gssapi/types.bif | 1 - src/analyzer/protocol/pia/CMakeLists.txt | 1 - src/analyzer/protocol/pia/PIA.cc | 2 -- src/analyzer/protocol/pia/events.bif | 0 src/analyzer/protocol/zip/CMakeLists.txt | 1 - src/analyzer/protocol/zip/ZIP.cc | 2 -- src/analyzer/protocol/zip/events.bif | 0 .../canonified_loaded_scripts.log | 8 ++------ .../canonified_loaded_scripts.log | 8 ++------ testing/btest/Baseline/plugins.hooks/output | 20 ++++++------------- 16 files changed, 11 insertions(+), 41 deletions(-) delete mode 100644 src/analyzer/protocol/ayiya/events.bif delete mode 100644 src/analyzer/protocol/gssapi/types.bif delete mode 100644 src/analyzer/protocol/pia/events.bif delete mode 100644 src/analyzer/protocol/zip/events.bif diff --git a/src/analyzer/protocol/ayiya/AYIYA.cc b/src/analyzer/protocol/ayiya/AYIYA.cc index a1e00e9b38..9c4ac237ab 100644 --- a/src/analyzer/protocol/ayiya/AYIYA.cc +++ b/src/analyzer/protocol/ayiya/AYIYA.cc @@ -1,7 +1,6 @@ #include "AYIYA.h" #include "Func.h" -#include "events.bif.h" using namespace analyzer::ayiya; diff --git a/src/analyzer/protocol/ayiya/CMakeLists.txt b/src/analyzer/protocol/ayiya/CMakeLists.txt index ae23c25e2d..50113b72d7 100644 --- a/src/analyzer/protocol/ayiya/CMakeLists.txt +++ b/src/analyzer/protocol/ayiya/CMakeLists.txt @@ -5,6 +5,5 @@ include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DI bro_plugin_begin(Bro AYIYA) bro_plugin_cc(AYIYA.cc Plugin.cc) -bro_plugin_bif(events.bif) bro_plugin_pac(ayiya.pac ayiya-protocol.pac ayiya-analyzer.pac) bro_plugin_end() diff --git a/src/analyzer/protocol/ayiya/ayiya.pac b/src/analyzer/protocol/ayiya/ayiya.pac index b1f3a6ef77..ff0af4d47c 100644 --- a/src/analyzer/protocol/ayiya/ayiya.pac +++ b/src/analyzer/protocol/ayiya/ayiya.pac @@ -2,10 +2,6 @@ %include binpac.pac %include bro.pac -%extern{ -#include "events.bif.h" -%} - analyzer AYIYA withcontext { connection: AYIYA_Conn; flow: AYIYA_Flow; diff --git a/src/analyzer/protocol/ayiya/events.bif b/src/analyzer/protocol/ayiya/events.bif deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/analyzer/protocol/gssapi/CMakeLists.txt b/src/analyzer/protocol/gssapi/CMakeLists.txt index 222c3cdf4e..d826d36bf7 100644 --- a/src/analyzer/protocol/gssapi/CMakeLists.txt +++ b/src/analyzer/protocol/gssapi/CMakeLists.txt @@ -5,7 +5,7 @@ include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DI bro_plugin_begin(Bro GSSAPI) bro_plugin_cc(GSSAPI.cc Plugin.cc) -bro_plugin_bif(types.bif events.bif) +bro_plugin_bif(events.bif) bro_plugin_pac( gssapi.pac gssapi-protocol.pac diff --git a/src/analyzer/protocol/gssapi/gssapi.pac b/src/analyzer/protocol/gssapi/gssapi.pac index 07759e8daa..55b7fe4255 100644 --- a/src/analyzer/protocol/gssapi/gssapi.pac +++ b/src/analyzer/protocol/gssapi/gssapi.pac @@ -5,7 +5,6 @@ #include "analyzer/Manager.h" #include "analyzer/Analyzer.h" -#include "types.bif.h" #include "events.bif.h" %} diff --git a/src/analyzer/protocol/gssapi/types.bif b/src/analyzer/protocol/gssapi/types.bif deleted file mode 100644 index 996cee9ad8..0000000000 --- a/src/analyzer/protocol/gssapi/types.bif +++ /dev/null @@ -1 +0,0 @@ -# Empty. diff --git a/src/analyzer/protocol/pia/CMakeLists.txt b/src/analyzer/protocol/pia/CMakeLists.txt index ff55bcf0aa..02397f7aff 100644 --- a/src/analyzer/protocol/pia/CMakeLists.txt +++ b/src/analyzer/protocol/pia/CMakeLists.txt @@ -5,5 +5,4 @@ include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DI bro_plugin_begin(Bro PIA) bro_plugin_cc(PIA.cc Plugin.cc) -bro_plugin_bif(events.bif) bro_plugin_end() diff --git a/src/analyzer/protocol/pia/PIA.cc b/src/analyzer/protocol/pia/PIA.cc index 7d73624dd0..8f5e23a1ce 100644 --- a/src/analyzer/protocol/pia/PIA.cc +++ b/src/analyzer/protocol/pia/PIA.cc @@ -3,8 +3,6 @@ #include "analyzer/protocol/tcp/TCP_Flags.h" #include "analyzer/protocol/tcp/TCP_Reassembler.h" -#include "events.bif.h" - using namespace analyzer::pia; PIA::PIA(analyzer::Analyzer* arg_as_analyzer) diff --git a/src/analyzer/protocol/pia/events.bif b/src/analyzer/protocol/pia/events.bif deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/analyzer/protocol/zip/CMakeLists.txt b/src/analyzer/protocol/zip/CMakeLists.txt index 814119f9f7..40c64afd6e 100644 --- a/src/analyzer/protocol/zip/CMakeLists.txt +++ b/src/analyzer/protocol/zip/CMakeLists.txt @@ -5,5 +5,4 @@ include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DI bro_plugin_begin(Bro ZIP) bro_plugin_cc(ZIP.cc Plugin.cc) -bro_plugin_bif(events.bif) bro_plugin_end() diff --git a/src/analyzer/protocol/zip/ZIP.cc b/src/analyzer/protocol/zip/ZIP.cc index d14df95673..d44c6353cd 100644 --- a/src/analyzer/protocol/zip/ZIP.cc +++ b/src/analyzer/protocol/zip/ZIP.cc @@ -2,8 +2,6 @@ #include "ZIP.h" -#include "events.bif.h" - using namespace analyzer::zip; ZIP_Analyzer::ZIP_Analyzer(Connection* conn, bool orig, Method arg_method) diff --git a/src/analyzer/protocol/zip/events.bif b/src/analyzer/protocol/zip/events.bif deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/testing/btest/Baseline/coverage.bare-load-baseline/canonified_loaded_scripts.log b/testing/btest/Baseline/coverage.bare-load-baseline/canonified_loaded_scripts.log index 6587112ef2..d53b14ce58 100644 --- a/testing/btest/Baseline/coverage.bare-load-baseline/canonified_loaded_scripts.log +++ b/testing/btest/Baseline/coverage.bare-load-baseline/canonified_loaded_scripts.log @@ -3,7 +3,7 @@ #empty_field (empty) #unset_field - #path loaded_scripts -#open 2016-11-02-17-25-26 +#open 2017-02-28-17-15-30 #fields name #types string scripts/base/init-bare.bro @@ -58,7 +58,6 @@ scripts/base/init-bare.bro build/scripts/base/bif/top-k.bif.bro build/scripts/base/bif/plugins/__load__.bro build/scripts/base/bif/plugins/Bro_ARP.events.bif.bro - build/scripts/base/bif/plugins/Bro_AYIYA.events.bif.bro build/scripts/base/bif/plugins/Bro_BackDoor.events.bif.bro build/scripts/base/bif/plugins/Bro_BitTorrent.events.bif.bro build/scripts/base/bif/plugins/Bro_ConnSize.events.bif.bro @@ -74,7 +73,6 @@ scripts/base/init-bare.bro build/scripts/base/bif/plugins/Bro_FTP.events.bif.bro build/scripts/base/bif/plugins/Bro_FTP.functions.bif.bro build/scripts/base/bif/plugins/Bro_Gnutella.events.bif.bro - build/scripts/base/bif/plugins/Bro_GSSAPI.types.bif.bro build/scripts/base/bif/plugins/Bro_GSSAPI.events.bif.bro build/scripts/base/bif/plugins/Bro_GTPv1.events.bif.bro build/scripts/base/bif/plugins/Bro_HTTP.events.bif.bro @@ -96,7 +94,6 @@ scripts/base/init-bare.bro build/scripts/base/bif/plugins/Bro_NTLM.types.bif.bro build/scripts/base/bif/plugins/Bro_NTLM.events.bif.bro build/scripts/base/bif/plugins/Bro_NTP.events.bif.bro - build/scripts/base/bif/plugins/Bro_PIA.events.bif.bro build/scripts/base/bif/plugins/Bro_POP3.events.bif.bro build/scripts/base/bif/plugins/Bro_RADIUS.events.bif.bro build/scripts/base/bif/plugins/Bro_RDP.events.bif.bro @@ -150,7 +147,6 @@ scripts/base/init-bare.bro build/scripts/base/bif/plugins/Bro_Teredo.events.bif.bro build/scripts/base/bif/plugins/Bro_UDP.events.bif.bro build/scripts/base/bif/plugins/Bro_XMPP.events.bif.bro - build/scripts/base/bif/plugins/Bro_ZIP.events.bif.bro build/scripts/base/bif/plugins/Bro_FileEntropy.events.bif.bro build/scripts/base/bif/plugins/Bro_FileExtract.events.bif.bro build/scripts/base/bif/plugins/Bro_FileExtract.functions.bif.bro @@ -171,4 +167,4 @@ scripts/base/init-bare.bro build/scripts/base/bif/plugins/Bro_SQLiteWriter.sqlite.bif.bro scripts/policy/misc/loaded-scripts.bro scripts/base/utils/paths.bro -#close 2016-11-02-17-25-26 +#close 2017-02-28-17-15-30 diff --git a/testing/btest/Baseline/coverage.default-load-baseline/canonified_loaded_scripts.log b/testing/btest/Baseline/coverage.default-load-baseline/canonified_loaded_scripts.log index 7a7b127752..e11edefe16 100644 --- a/testing/btest/Baseline/coverage.default-load-baseline/canonified_loaded_scripts.log +++ b/testing/btest/Baseline/coverage.default-load-baseline/canonified_loaded_scripts.log @@ -3,7 +3,7 @@ #empty_field (empty) #unset_field - #path loaded_scripts -#open 2016-11-02-17-25-18 +#open 2017-02-28-17-19-41 #fields name #types string scripts/base/init-bare.bro @@ -58,7 +58,6 @@ scripts/base/init-bare.bro build/scripts/base/bif/top-k.bif.bro build/scripts/base/bif/plugins/__load__.bro build/scripts/base/bif/plugins/Bro_ARP.events.bif.bro - build/scripts/base/bif/plugins/Bro_AYIYA.events.bif.bro build/scripts/base/bif/plugins/Bro_BackDoor.events.bif.bro build/scripts/base/bif/plugins/Bro_BitTorrent.events.bif.bro build/scripts/base/bif/plugins/Bro_ConnSize.events.bif.bro @@ -74,7 +73,6 @@ scripts/base/init-bare.bro build/scripts/base/bif/plugins/Bro_FTP.events.bif.bro build/scripts/base/bif/plugins/Bro_FTP.functions.bif.bro build/scripts/base/bif/plugins/Bro_Gnutella.events.bif.bro - build/scripts/base/bif/plugins/Bro_GSSAPI.types.bif.bro build/scripts/base/bif/plugins/Bro_GSSAPI.events.bif.bro build/scripts/base/bif/plugins/Bro_GTPv1.events.bif.bro build/scripts/base/bif/plugins/Bro_HTTP.events.bif.bro @@ -96,7 +94,6 @@ scripts/base/init-bare.bro build/scripts/base/bif/plugins/Bro_NTLM.types.bif.bro build/scripts/base/bif/plugins/Bro_NTLM.events.bif.bro build/scripts/base/bif/plugins/Bro_NTP.events.bif.bro - build/scripts/base/bif/plugins/Bro_PIA.events.bif.bro build/scripts/base/bif/plugins/Bro_POP3.events.bif.bro build/scripts/base/bif/plugins/Bro_RADIUS.events.bif.bro build/scripts/base/bif/plugins/Bro_RDP.events.bif.bro @@ -150,7 +147,6 @@ scripts/base/init-bare.bro build/scripts/base/bif/plugins/Bro_Teredo.events.bif.bro build/scripts/base/bif/plugins/Bro_UDP.events.bif.bro build/scripts/base/bif/plugins/Bro_XMPP.events.bif.bro - build/scripts/base/bif/plugins/Bro_ZIP.events.bif.bro build/scripts/base/bif/plugins/Bro_FileEntropy.events.bif.bro build/scripts/base/bif/plugins/Bro_FileExtract.events.bif.bro build/scripts/base/bif/plugins/Bro_FileExtract.functions.bif.bro @@ -359,4 +355,4 @@ scripts/base/init-default.bro scripts/base/misc/find-filtered-trace.bro scripts/base/misc/version.bro scripts/policy/misc/loaded-scripts.bro -#close 2016-11-02-17-25-18 +#close 2017-02-28-17-19-41 diff --git a/testing/btest/Baseline/plugins.hooks/output b/testing/btest/Baseline/plugins.hooks/output index c291302748..420d20ae12 100644 --- a/testing/btest/Baseline/plugins.hooks/output +++ b/testing/btest/Baseline/plugins.hooks/output @@ -247,7 +247,7 @@ 0.000000 MetaHookPost CallFunction(Log::__create_stream, , (Weird::LOG, [columns=, ev=Weird::log_weird, path=weird])) -> 0.000000 MetaHookPost CallFunction(Log::__create_stream, , (X509::LOG, [columns=, ev=X509::log_x509, path=x509])) -> 0.000000 MetaHookPost CallFunction(Log::__create_stream, , (mysql::LOG, [columns=, ev=MySQL::log_mysql, path=mysql])) -> -0.000000 MetaHookPost CallFunction(Log::__write, , (PacketFilter::LOG, [ts=1487443758.386684, node=bro, filter=ip or not ip, init=T, success=T])) -> +0.000000 MetaHookPost CallFunction(Log::__write, , (PacketFilter::LOG, [ts=1488302456.440387, node=bro, filter=ip or not ip, init=T, success=T])) -> 0.000000 MetaHookPost CallFunction(Log::add_default_filter, , (Cluster::LOG)) -> 0.000000 MetaHookPost CallFunction(Log::add_default_filter, , (Communication::LOG)) -> 0.000000 MetaHookPost CallFunction(Log::add_default_filter, , (Conn::LOG)) -> @@ -377,7 +377,7 @@ 0.000000 MetaHookPost CallFunction(Log::create_stream, , (Weird::LOG, [columns=, ev=Weird::log_weird, path=weird])) -> 0.000000 MetaHookPost CallFunction(Log::create_stream, , (X509::LOG, [columns=, ev=X509::log_x509, path=x509])) -> 0.000000 MetaHookPost CallFunction(Log::create_stream, , (mysql::LOG, [columns=, ev=MySQL::log_mysql, path=mysql])) -> -0.000000 MetaHookPost CallFunction(Log::write, , (PacketFilter::LOG, [ts=1487443758.386684, node=bro, filter=ip or not ip, init=T, success=T])) -> +0.000000 MetaHookPost CallFunction(Log::write, , (PacketFilter::LOG, [ts=1488302456.440387, node=bro, filter=ip or not ip, init=T, success=T])) -> 0.000000 MetaHookPost CallFunction(NetControl::check_plugins, , ()) -> 0.000000 MetaHookPost CallFunction(NetControl::init, , ()) -> 0.000000 MetaHookPost CallFunction(Notice::want_pp, , ()) -> @@ -416,7 +416,6 @@ 0.000000 MetaHookPost LoadFile(../main) -> -1 0.000000 MetaHookPost LoadFile(../plugin) -> -1 0.000000 MetaHookPost LoadFile(./Bro_ARP.events.bif.bro) -> -1 -0.000000 MetaHookPost LoadFile(./Bro_AYIYA.events.bif.bro) -> -1 0.000000 MetaHookPost LoadFile(./Bro_AsciiReader.ascii.bif.bro) -> -1 0.000000 MetaHookPost LoadFile(./Bro_AsciiWriter.ascii.bif.bro) -> -1 0.000000 MetaHookPost LoadFile(./Bro_BackDoor.events.bif.bro) -> -1 @@ -440,7 +439,6 @@ 0.000000 MetaHookPost LoadFile(./Bro_FileHash.events.bif.bro) -> -1 0.000000 MetaHookPost LoadFile(./Bro_Finger.events.bif.bro) -> -1 0.000000 MetaHookPost LoadFile(./Bro_GSSAPI.events.bif.bro) -> -1 -0.000000 MetaHookPost LoadFile(./Bro_GSSAPI.types.bif.bro) -> -1 0.000000 MetaHookPost LoadFile(./Bro_GTPv1.events.bif.bro) -> -1 0.000000 MetaHookPost LoadFile(./Bro_Gnutella.events.bif.bro) -> -1 0.000000 MetaHookPost LoadFile(./Bro_HTTP.events.bif.bro) -> -1 @@ -465,7 +463,6 @@ 0.000000 MetaHookPost LoadFile(./Bro_NetBIOS.functions.bif.bro) -> -1 0.000000 MetaHookPost LoadFile(./Bro_NoneWriter.none.bif.bro) -> -1 0.000000 MetaHookPost LoadFile(./Bro_PE.events.bif.bro) -> -1 -0.000000 MetaHookPost LoadFile(./Bro_PIA.events.bif.bro) -> -1 0.000000 MetaHookPost LoadFile(./Bro_POP3.events.bif.bro) -> -1 0.000000 MetaHookPost LoadFile(./Bro_RADIUS.events.bif.bro) -> -1 0.000000 MetaHookPost LoadFile(./Bro_RDP.events.bif.bro) -> -1 @@ -528,7 +525,6 @@ 0.000000 MetaHookPost LoadFile(./Bro_X509.functions.bif.bro) -> -1 0.000000 MetaHookPost LoadFile(./Bro_X509.types.bif.bro) -> -1 0.000000 MetaHookPost LoadFile(./Bro_XMPP.events.bif.bro) -> -1 -0.000000 MetaHookPost LoadFile(./Bro_ZIP.events.bif.bro) -> -1 0.000000 MetaHookPost LoadFile(./acld) -> -1 0.000000 MetaHookPost LoadFile(./addrs) -> -1 0.000000 MetaHookPost LoadFile(./analyzer.bif.bro) -> -1 @@ -968,7 +964,7 @@ 0.000000 MetaHookPre CallFunction(Log::__create_stream, , (Weird::LOG, [columns=, ev=Weird::log_weird, path=weird])) 0.000000 MetaHookPre CallFunction(Log::__create_stream, , (X509::LOG, [columns=, ev=X509::log_x509, path=x509])) 0.000000 MetaHookPre CallFunction(Log::__create_stream, , (mysql::LOG, [columns=, ev=MySQL::log_mysql, path=mysql])) -0.000000 MetaHookPre CallFunction(Log::__write, , (PacketFilter::LOG, [ts=1487443758.386684, node=bro, filter=ip or not ip, init=T, success=T])) +0.000000 MetaHookPre CallFunction(Log::__write, , (PacketFilter::LOG, [ts=1488302456.440387, node=bro, filter=ip or not ip, init=T, success=T])) 0.000000 MetaHookPre CallFunction(Log::add_default_filter, , (Cluster::LOG)) 0.000000 MetaHookPre CallFunction(Log::add_default_filter, , (Communication::LOG)) 0.000000 MetaHookPre CallFunction(Log::add_default_filter, , (Conn::LOG)) @@ -1098,7 +1094,7 @@ 0.000000 MetaHookPre CallFunction(Log::create_stream, , (Weird::LOG, [columns=, ev=Weird::log_weird, path=weird])) 0.000000 MetaHookPre CallFunction(Log::create_stream, , (X509::LOG, [columns=, ev=X509::log_x509, path=x509])) 0.000000 MetaHookPre CallFunction(Log::create_stream, , (mysql::LOG, [columns=, ev=MySQL::log_mysql, path=mysql])) -0.000000 MetaHookPre CallFunction(Log::write, , (PacketFilter::LOG, [ts=1487443758.386684, node=bro, filter=ip or not ip, init=T, success=T])) +0.000000 MetaHookPre CallFunction(Log::write, , (PacketFilter::LOG, [ts=1488302456.440387, node=bro, filter=ip or not ip, init=T, success=T])) 0.000000 MetaHookPre CallFunction(NetControl::check_plugins, , ()) 0.000000 MetaHookPre CallFunction(NetControl::init, , ()) 0.000000 MetaHookPre CallFunction(Notice::want_pp, , ()) @@ -1137,7 +1133,6 @@ 0.000000 MetaHookPre LoadFile(../main) 0.000000 MetaHookPre LoadFile(../plugin) 0.000000 MetaHookPre LoadFile(./Bro_ARP.events.bif.bro) -0.000000 MetaHookPre LoadFile(./Bro_AYIYA.events.bif.bro) 0.000000 MetaHookPre LoadFile(./Bro_AsciiReader.ascii.bif.bro) 0.000000 MetaHookPre LoadFile(./Bro_AsciiWriter.ascii.bif.bro) 0.000000 MetaHookPre LoadFile(./Bro_BackDoor.events.bif.bro) @@ -1161,7 +1156,6 @@ 0.000000 MetaHookPre LoadFile(./Bro_FileHash.events.bif.bro) 0.000000 MetaHookPre LoadFile(./Bro_Finger.events.bif.bro) 0.000000 MetaHookPre LoadFile(./Bro_GSSAPI.events.bif.bro) -0.000000 MetaHookPre LoadFile(./Bro_GSSAPI.types.bif.bro) 0.000000 MetaHookPre LoadFile(./Bro_GTPv1.events.bif.bro) 0.000000 MetaHookPre LoadFile(./Bro_Gnutella.events.bif.bro) 0.000000 MetaHookPre LoadFile(./Bro_HTTP.events.bif.bro) @@ -1186,7 +1180,6 @@ 0.000000 MetaHookPre LoadFile(./Bro_NetBIOS.functions.bif.bro) 0.000000 MetaHookPre LoadFile(./Bro_NoneWriter.none.bif.bro) 0.000000 MetaHookPre LoadFile(./Bro_PE.events.bif.bro) -0.000000 MetaHookPre LoadFile(./Bro_PIA.events.bif.bro) 0.000000 MetaHookPre LoadFile(./Bro_POP3.events.bif.bro) 0.000000 MetaHookPre LoadFile(./Bro_RADIUS.events.bif.bro) 0.000000 MetaHookPre LoadFile(./Bro_RDP.events.bif.bro) @@ -1249,7 +1242,6 @@ 0.000000 MetaHookPre LoadFile(./Bro_X509.functions.bif.bro) 0.000000 MetaHookPre LoadFile(./Bro_X509.types.bif.bro) 0.000000 MetaHookPre LoadFile(./Bro_XMPP.events.bif.bro) -0.000000 MetaHookPre LoadFile(./Bro_ZIP.events.bif.bro) 0.000000 MetaHookPre LoadFile(./acld) 0.000000 MetaHookPre LoadFile(./addrs) 0.000000 MetaHookPre LoadFile(./analyzer.bif.bro) @@ -1688,7 +1680,7 @@ 0.000000 | HookCallFunction Log::__create_stream(Weird::LOG, [columns=, ev=Weird::log_weird, path=weird]) 0.000000 | HookCallFunction Log::__create_stream(X509::LOG, [columns=, ev=X509::log_x509, path=x509]) 0.000000 | HookCallFunction Log::__create_stream(mysql::LOG, [columns=, ev=MySQL::log_mysql, path=mysql]) -0.000000 | HookCallFunction Log::__write(PacketFilter::LOG, [ts=1487443758.386684, node=bro, filter=ip or not ip, init=T, success=T]) +0.000000 | HookCallFunction Log::__write(PacketFilter::LOG, [ts=1488302456.440387, node=bro, filter=ip or not ip, init=T, success=T]) 0.000000 | HookCallFunction Log::add_default_filter(Cluster::LOG) 0.000000 | HookCallFunction Log::add_default_filter(Communication::LOG) 0.000000 | HookCallFunction Log::add_default_filter(Conn::LOG) @@ -1818,7 +1810,7 @@ 0.000000 | HookCallFunction Log::create_stream(Weird::LOG, [columns=, ev=Weird::log_weird, path=weird]) 0.000000 | HookCallFunction Log::create_stream(X509::LOG, [columns=, ev=X509::log_x509, path=x509]) 0.000000 | HookCallFunction Log::create_stream(mysql::LOG, [columns=, ev=MySQL::log_mysql, path=mysql]) -0.000000 | HookCallFunction Log::write(PacketFilter::LOG, [ts=1487443758.386684, node=bro, filter=ip or not ip, init=T, success=T]) +0.000000 | HookCallFunction Log::write(PacketFilter::LOG, [ts=1488302456.440387, node=bro, filter=ip or not ip, init=T, success=T]) 0.000000 | HookCallFunction NetControl::check_plugins() 0.000000 | HookCallFunction NetControl::init() 0.000000 | HookCallFunction Notice::want_pp() From 9341ff801c2bc0445553305a67d5c78442c0dbf3 Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Thu, 2 Mar 2017 08:53:38 -0800 Subject: [PATCH 30/44] Move threading to c++11 primitives (mostly). This moves all threading code in Bro from pthreads to the c++11 primitives, which make for shorter, easier to use, and less error-prone code. pthreads is still used in 2 places in Bro currently. BasicThread uses two bits of functionality that are not available using the c++ API (setting thread names & setting signal masks). Since all c++ implementations that I am aware of still use an underlying pthreads implementation, we just use native_handle to access the underlying pthreads implementation for these cases. I do not expect this to lead to problems in the forseable future. If we ever encounter a platform where a different thread architecture is used, we might have to change that around. This code is guarded by static_asserts, so we will notice if a platform uses a different implementation. sqlite also uses pthreads directly. --- src/input/readers/raw/Plugin.cc | 12 +--- src/input/readers/raw/Plugin.h | 8 +-- src/input/readers/raw/Raw.cc | 30 +++------ src/input/readers/raw/Raw.h | 6 +- src/threading/BasicThread.cc | 21 ++++--- src/threading/BasicThread.h | 10 +-- src/threading/MsgThread.h | 2 - src/threading/Queue.h | 104 +++++++++++--------------------- 8 files changed, 70 insertions(+), 123 deletions(-) diff --git a/src/input/readers/raw/Plugin.cc b/src/input/readers/raw/Plugin.cc index c7af84e34e..e16a233fe6 100644 --- a/src/input/readers/raw/Plugin.cc +++ b/src/input/readers/raw/Plugin.cc @@ -8,7 +8,6 @@ using namespace plugin::Bro_RawReader; Plugin::Plugin() { - init = false; } plugin::Configuration Plugin::Configure() @@ -23,21 +22,14 @@ plugin::Configuration Plugin::Configure() void Plugin::InitPreScript() { - if ( pthread_mutex_init(&fork_mutex, 0) != 0 ) - reporter->FatalError("cannot initialize raw reader's mutex"); - - init = true; } void Plugin::Done() { - pthread_mutex_destroy(&fork_mutex); - init = false; } -pthread_mutex_t* Plugin::ForkMutex() +std::unique_lock Plugin::ForkMutex() { - assert(init); - return &fork_mutex; + return std::unique_lock(fork_mutex, std::defer_lock); } diff --git a/src/input/readers/raw/Plugin.h b/src/input/readers/raw/Plugin.h index 59a5dfd2be..7b621c9440 100644 --- a/src/input/readers/raw/Plugin.h +++ b/src/input/readers/raw/Plugin.h @@ -1,8 +1,9 @@ -// See the file in the main distribution directory for copyright. +// See the file in the main distribution directory for copyright. #include "plugin/Plugin.h" #include "Raw.h" +#include namespace plugin { namespace Bro_RawReader { @@ -16,11 +17,10 @@ public: virtual void InitPreScript(); virtual void Done(); - pthread_mutex_t * ForkMutex(); + std::unique_lock ForkMutex(); private: - bool init; - pthread_mutex_t fork_mutex; + std::mutex fork_mutex; }; diff --git a/src/input/readers/raw/Raw.cc b/src/input/readers/raw/Raw.cc index cfa7b72602..c892c207cc 100644 --- a/src/input/readers/raw/Raw.cc +++ b/src/input/readers/raw/Raw.cc @@ -96,24 +96,16 @@ bool Raw::SetFDFlags(int fd, int cmd, int flags) } -bool Raw::LockForkMutex() +std::unique_lock Raw::AcquireForkMutex() { - int res = pthread_mutex_lock(plugin::Bro_RawReader::plugin.ForkMutex()); - if ( res == 0 ) - return true; - - Error(Fmt("cannot lock fork mutex: %d", res)); - return false; + auto lock = plugin::Bro_RawReader::plugin.ForkMutex(); + try { + lock.lock(); + } catch(const std::system_error& e) { + reporter->FatalErrorWithCore("cannot lock fork mutex: %s", e.what()); } -bool Raw::UnlockForkMutex() - { - int res = pthread_mutex_unlock(plugin::Bro_RawReader::plugin.ForkMutex()); - if ( res == 0 ) - return true; - - Error(Fmt("cannot unlock fork mutex: %d", res)); - return false; + return lock; } bool Raw::Execute() @@ -126,12 +118,10 @@ bool Raw::Execute() // never crops up... ("never" meaning I haven't seen in it in // hundreds of tests using 50+ threads where before I'd see the issue // w/ just 2 threads ~33% of the time). - if ( ! LockForkMutex() ) - return false; + auto lock = AcquireForkMutex(); if ( pipe(pipes) != 0 || pipe(pipes+2) || pipe(pipes+4) ) { - UnlockForkMutex(); Error(Fmt("Could not open pipe: %d", errno)); return false; } @@ -139,7 +129,6 @@ bool Raw::Execute() childpid = fork(); if ( childpid < 0 ) { - UnlockForkMutex(); Error(Fmt("Could not create child process: %d", errno)); return false; } @@ -208,8 +197,7 @@ bool Raw::Execute() } } - if ( ! UnlockForkMutex() ) - return false; + lock.unlock(); ClosePipeEnd(stdout_out); diff --git a/src/input/readers/raw/Raw.h b/src/input/readers/raw/Raw.h index 2a166ae322..ec50ade8fe 100644 --- a/src/input/readers/raw/Raw.h +++ b/src/input/readers/raw/Raw.h @@ -4,8 +4,8 @@ #define INPUT_READERS_RAW_H #include -#include #include +#include #include "input/ReaderBackend.h" @@ -37,8 +37,7 @@ protected: private: void ClosePipeEnd(int i); bool SetFDFlags(int fd, int cmd, int flags); - bool LockForkMutex(); - bool UnlockForkMutex(); + std::unique_lock AcquireForkMutex(); bool OpenInput(); bool CloseInput(); @@ -87,7 +86,6 @@ private: }; static const int block_size; - static pthread_mutex_t fork_mutex; }; } diff --git a/src/threading/BasicThread.cc b/src/threading/BasicThread.cc index 86d7d7b560..ea6c8c433b 100644 --- a/src/threading/BasicThread.cc +++ b/src/threading/BasicThread.cc @@ -5,6 +5,7 @@ #include "bro-config.h" #include "BasicThread.h" #include "Manager.h" +#include "pthread.h" #ifdef HAVE_LINUX #include @@ -21,7 +22,6 @@ BasicThread::BasicThread() started = false; terminating = false; killed = false; - pthread = 0; buf_len = STD_FMT_BUF_LEN; buf = (char*) safe_malloc(buf_len); @@ -50,6 +50,7 @@ void BasicThread::SetName(const char* arg_name) void BasicThread::SetOSName(const char* arg_name) { + static_assert(std::is_same::value, "libstdc++ doesn't use pthread_t"); #ifdef HAVE_LINUX prctl(PR_SET_NAME, arg_name, 0, 0, 0); @@ -60,7 +61,7 @@ void BasicThread::SetOSName(const char* arg_name) #endif #ifdef FREEBSD - pthread_set_name_np(pthread_self(), arg_name, arg_name); + pthread_set_name_np(thread.native_handle(), arg_name, arg_name); #endif } @@ -108,9 +109,7 @@ void BasicThread::Start() started = true; - int err = pthread_create(&pthread, 0, BasicThread::launcher, this); - if ( err != 0 ) - reporter->FatalError("Cannot create thread %s: %s", name, Strerror(err)); + thread = std::thread(&BasicThread::launcher, this); DBG_LOG(DBG_THREADING, "Started thread %s", name); @@ -147,17 +146,18 @@ void BasicThread::Join() if ( ! started ) return; - if ( ! pthread ) + if ( ! thread.joinable() ) return; assert(terminating); - if ( pthread_join(pthread, 0) != 0 ) - reporter->FatalError("Failure joining thread %s", name); + try { + thread.join(); + } catch(const std::system_error& e) { + reporter->FatalError("Failure joining thread %s with error %s", name, e.what()); + } DBG_LOG(DBG_THREADING, "Joined with thread %s", name); - - pthread = 0; } void BasicThread::Kill() @@ -180,6 +180,7 @@ void BasicThread::Done() void* BasicThread::launcher(void *arg) { + static_assert(std::is_same::value, "libstdc++ doesn't use pthread_t"); BasicThread* thread = (BasicThread *)arg; // Block signals in thread. We handle signals only in the main diff --git a/src/threading/BasicThread.h b/src/threading/BasicThread.h index 6386e5ae66..ea829fce54 100644 --- a/src/threading/BasicThread.h +++ b/src/threading/BasicThread.h @@ -2,8 +2,7 @@ #ifndef THREADING_BASICTHREAD_H #define THREADING_BASICTHREAD_H -#include -#include +#include #include "util.h" @@ -35,6 +34,9 @@ public: */ BasicThread(); + BasicThread(BasicThread const&) = delete; + BasicThread& operator =(BasicThread const&) = delete; + /** * Returns a descriptive name for the thread. If not set via * SetName(). If not set, a default name is choosen automatically. @@ -192,11 +194,11 @@ protected: void Done(); private: - // pthread entry function. + // thread entry function. static void* launcher(void *arg); const char* name; - pthread_t pthread; + std::thread thread; bool started; // Set to to true once running. bool terminating; // Set to to true to signal termination. bool killed; // Set to true once forcefully killed. diff --git a/src/threading/MsgThread.h b/src/threading/MsgThread.h index 96da68e1d0..480ab2974c 100644 --- a/src/threading/MsgThread.h +++ b/src/threading/MsgThread.h @@ -2,8 +2,6 @@ #ifndef THREADING_MSGTHREAD_H #define THREADING_MSGTHREAD_H -#include - #include "DebugLogger.h" #include "BasicThread.h" diff --git a/src/threading/Queue.h b/src/threading/Queue.h index 6d21bfd998..48b41ad81a 100644 --- a/src/threading/Queue.h +++ b/src/threading/Queue.h @@ -1,7 +1,8 @@ #ifndef THREADING_QUEUE_H #define THREADING_QUEUE_H -#include +#include +#include #include #include #include @@ -22,7 +23,7 @@ namespace threading { * * All Queue instances must be instantiated by Bro's main thread. * - * TODO: Unclear how critical performance is for this qeueue. We could like;y + * TODO: Unclear how critical performance is for this qeueue. We could likely * optimize it further if helpful. */ template @@ -71,9 +72,10 @@ public: */ bool MaybeReady() { return (num_reads != num_writes); } - /** Wake up the reader if it's currently blocked for input. This is - primarily to give it a chance to check termination quickly. - **/ + /** + * Wake up the reader if it's currently blocked for input. This is + * primarily to give it a chance to check termination quickly. + */ void WakeUp(); /** @@ -94,14 +96,15 @@ public: * Returns statistics about the queue's usage. * * @param stats A pointer to a structure that will be filled with - * current numbers. */ + * current numbers. + */ void GetStats(Stats* stats); private: static const int NUM_QUEUES = 8; - pthread_mutex_t mutex[NUM_QUEUES]; // Mutex protected shared accesses. - pthread_cond_t has_data[NUM_QUEUES]; // Signals when data becomes available + std::mutex mutex[NUM_QUEUES]; // Mutex protected shared accesses. + std::condition_variable has_data[NUM_QUEUES]; // Signals when data becomes available std::queue messages[NUM_QUEUES]; // Actually holds the queued messages int read_ptr; // Where the next operation will read from @@ -115,17 +118,17 @@ private: uint64_t num_writes; }; -inline static void safe_lock(pthread_mutex_t* mutex) +inline static std::unique_lock safe_lock(std::mutex& m) { - int res = pthread_mutex_lock(mutex); - if ( res != 0 ) - reporter->FatalErrorWithCore("cannot lock mutex: %d(%s)", res, strerror(res)); + std::unique_lock lock(m, std::defer_lock); + + try { + lock.lock(); + } catch(const std::system_error& e) { + reporter->FatalErrorWithCore("cannot lock mutex: %s", e.what()); } -inline static void safe_unlock(pthread_mutex_t* mutex) - { - if ( pthread_mutex_unlock(mutex) != 0 ) - reporter->FatalErrorWithCore("cannot unlock mutex"); + return lock; } template @@ -136,50 +139,28 @@ inline Queue::Queue(BasicThread* arg_reader, BasicThread* arg_writer) num_reads = num_writes = 0; reader = arg_reader; writer = arg_writer; - - for( int i = 0; i < NUM_QUEUES; ++i ) - { - if ( pthread_cond_init(&has_data[i], 0) != 0 ) - reporter->FatalError("cannot init queue condition variable"); - - if ( pthread_mutex_init(&mutex[i], 0) != 0 ) - reporter->FatalError("cannot init queue mutex"); - } } template inline Queue::~Queue() { - for( int i = 0; i < NUM_QUEUES; ++i ) - { - pthread_cond_destroy(&has_data[i]); - pthread_mutex_destroy(&mutex[i]); - } } template inline T Queue::Get() { - safe_lock(&mutex[read_ptr]); + auto lock = safe_lock(mutex[read_ptr]); int old_read_ptr = read_ptr; if ( messages[read_ptr].empty() && ! ((reader && reader->Killed()) || (writer && writer->Killed())) ) { - struct timespec ts; - ts.tv_sec = time(0) + 5; - ts.tv_nsec = 0; - - pthread_cond_timedwait(&has_data[read_ptr], &mutex[read_ptr], &ts); - safe_unlock(&mutex[read_ptr]); - return 0; + if ( has_data[read_ptr].wait_for(lock, std::chrono::seconds(5)) == std::cv_status::timeout ) + return nullptr; } - else if ( messages[read_ptr].empty() ) - { - safe_unlock(&mutex[read_ptr]); - return 0; - } + if ( messages[read_ptr].empty() ) + return nullptr; T data = messages[read_ptr].front(); messages[read_ptr].pop(); @@ -187,15 +168,13 @@ inline T Queue::Get() read_ptr = (read_ptr + 1) % NUM_QUEUES; ++num_reads; - safe_unlock(&mutex[old_read_ptr]); - return data; } template inline void Queue::Put(T data) { - safe_lock(&mutex[write_ptr]); + auto lock = safe_lock(mutex[write_ptr]); int old_write_ptr = write_ptr; @@ -203,25 +182,24 @@ inline void Queue::Put(T data) messages[write_ptr].push(data); - if ( need_signal ) - pthread_cond_signal(&has_data[write_ptr]); - write_ptr = (write_ptr + 1) % NUM_QUEUES; ++num_writes; - safe_unlock(&mutex[old_write_ptr]); + if ( need_signal ) + { + lock.unlock(); + has_data[old_write_ptr].notify_one(); + } } template inline bool Queue::Ready() { - safe_lock(&mutex[read_ptr]); + auto lock = safe_lock(mutex[read_ptr]); bool ret = (messages[read_ptr].size()); - safe_unlock(&mutex[read_ptr]); - return ret; } @@ -229,17 +207,15 @@ template inline uint64_t Queue::Size() { // Need to lock all queues. + std::vector> locks; for ( int i = 0; i < NUM_QUEUES; i++ ) - safe_lock(&mutex[i]); + locks.push_back(safe_lock(mutex[i])); uint64_t size = 0; for ( int i = 0; i < NUM_QUEUES; i++ ) size += messages[i].size(); - for ( int i = 0; i < NUM_QUEUES; i++ ) - safe_unlock(&mutex[i]); - return size; } @@ -248,29 +224,21 @@ inline void Queue::GetStats(Stats* stats) { // To be safe, we look all queues. That's probably unneccessary, but // doesn't really hurt. + std::vector> locks; for ( int i = 0; i < NUM_QUEUES; i++ ) - safe_lock(&mutex[i]); + locks.push_back(safe_lock(mutex[i])); stats->num_reads = num_reads; stats->num_writes = num_writes; - - for ( int i = 0; i < NUM_QUEUES; i++ ) - safe_unlock(&mutex[i]); } template inline void Queue::WakeUp() { for ( int i = 0; i < NUM_QUEUES; i++ ) - { - safe_lock(&mutex[i]); - pthread_cond_signal(&has_data[i]); - safe_unlock(&mutex[i]); - } + has_data[i].notify_all(); } } - #endif - From 766bab077144e9341f4f29bdedebdf621ccee584 Mon Sep 17 00:00:00 2001 From: Robin Sommer Date: Thu, 2 Mar 2017 16:45:07 -0800 Subject: [PATCH 31/44] Updating submodule. --- aux/btest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aux/btest b/aux/btest index 2fb0e089b3..14c18508bb 160000 --- a/aux/btest +++ b/aux/btest @@ -1 +1 @@ -Subproject commit 2fb0e089b37cc62b4616c8d4309ee80fb7fc6a27 +Subproject commit 14c18508bb385c8f55cd79738ee87ca2b85eba27 From f616903e5ffc1b28142bb6f4e94424993d772801 Mon Sep 17 00:00:00 2001 From: Robin Sommer Date: Fri, 3 Mar 2017 10:44:14 -0800 Subject: [PATCH 32/44] Updating submodule(s). [nomail] --- CHANGES | 2 +- VERSION | 2 +- aux/plugins | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 9c58d9338f..862174c068 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,5 @@ -2.5-91 | 2017-03-03 10:30:12 -0800 +2.5-92 | 2017-03-03 10:44:14 -0800 * Move most threading to C++11 primitives (mostly). (Johanna Amann) diff --git a/VERSION b/VERSION index 2cca69f0f3..540a520883 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.5-91 +2.5-92 diff --git a/aux/plugins b/aux/plugins index 2322840bcd..c4b5df3aa8 160000 --- a/aux/plugins +++ b/aux/plugins @@ -1 +1 @@ -Subproject commit 2322840bcdbd618ae7bd24e22d874fb30ab89bbb +Subproject commit c4b5df3aa8e5c58a2dc5e5040c7da8369894f24d From b6e6302b4031d36d6e297b25aba444b0dc26f1e8 Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Fri, 3 Mar 2017 12:40:37 -0800 Subject: [PATCH 33/44] Ascii reader error changes - fix small bugs The changes are now a bit more succinct with less code changes required. Behavior is tested a little bit more thoroughly and a memory problem when reading incomplete lines was fixed. ReadHeader also always directly returns if header reading failed. Error messages now are back to what they were before the change, if the new behavior is not used. I also tweaked the documentation text a bit. --- .../base/frameworks/input/readers/ascii.bro | 34 ++++++-- src/input/readers/ascii/Ascii.cc | 85 ++++++++++--------- src/input/readers/ascii/Ascii.h | 10 ++- .../out | 26 ++++++ .../bro..stderr | 4 + .../bro..stdout | 3 + .../bro..stderr | 4 +- .../base/frameworks/input/invalid-lines.bro | 67 +++++++++++++++ .../base/frameworks/input/invalidtext.bro | 1 + .../input/missing-file-initially.bro | 12 +-- .../base/frameworks/input/missing-file.bro | 1 + 11 files changed, 187 insertions(+), 60 deletions(-) create mode 100644 testing/btest/Baseline/scripts.base.frameworks.input.invalid-lines/out create mode 100644 testing/btest/scripts/base/frameworks/input/invalid-lines.bro diff --git a/scripts/base/frameworks/input/readers/ascii.bro b/scripts/base/frameworks/input/readers/ascii.bro index 521e8d4ca3..83d8ced94b 100644 --- a/scripts/base/frameworks/input/readers/ascii.bro +++ b/scripts/base/frameworks/input/readers/ascii.bro @@ -19,16 +19,32 @@ export { ## String to use for an unset &optional field. const unset_field = Input::unset_field &redef; - ## Choose if the ascii input reader should globally - ## fail on invalid lines and continue parsing afterward. - ## Individual readers can use a different value. + ## Fail on invalid lines. If set to false, the ascii + ## input reader will jump over invalid lines, reporting + ## warnings in reporter.log. If set to true, errors in + ## input lines will be handled as fatal errors for the + ## reader thread; reading will abort immediately and + ## an error will be logged to reporter.log. + ## Invidivual readers can use a different value using + ## the $config table. + ## fail_on_invalid_lines = T was the default behavior + ## untill Bro 2.5. const fail_on_invalid_lines = F &redef; - ## Set to true if you would like the old behavior of the - ## ascii reader where the reader thread would die if any file - ## errors occur (like permissions problems or file missing). - ## The default behavior is to continue attempting to open and read - ## the file even in light of problems. - ## Individual readers can use a different value. + ## Fail on file read problems. If set to true, the ascii + ## input reader will fail when encountering any problems + ## while reading a file different from invalid lines. + ## Examples fur such problems are permission problems, or + ## missing files. + ## When set to false, these problems will be ignored. This + ## has an especially big effect for the REREAD mode, which will + ## seamlessly recover from read errors when a file is + ## only temporarily inaccessible. For MANUAL or STREAM files, + ## errors will most likely still be fatal since no automatic + ## re-reading of the file is attempted. + ## Invidivual readers can use a different value using + ## the $config table. + ## fail_on_file_problem = T was the default behavior + ## untill Bro 2.5. const fail_on_file_problem = F &redef; } diff --git a/src/input/readers/ascii/Ascii.cc b/src/input/readers/ascii/Ascii.cc index 5621a87eb4..bedddce075 100644 --- a/src/input/readers/ascii/Ascii.cc +++ b/src/input/readers/ascii/Ascii.cc @@ -61,7 +61,7 @@ void Ascii::DoClose() bool Ascii::DoInit(const ReaderInfo& info, int num_fields, const Field* const* fields) { - is_failed = false; + suppress_warnings = false; separator.assign( (const char*) BifConst::InputAscii::separator->Bytes(), BifConst::InputAscii::separator->Len()); @@ -109,43 +109,48 @@ bool Ascii::DoInit(const ReaderInfo& info, int num_fields, const Field* const* f formatter::Ascii::SeparatorInfo sep_info(separator, set_separator, unset_field, empty_field); formatter = unique_ptr(new formatter::Ascii(this, sep_info)); - DoUpdate(); - - return true; + return DoUpdate(); } -void Ascii::FailWarn(bool is_error, const char *msg) +void Ascii::FailWarn(bool is_error, const char *msg, bool suppress_future) { if ( is_error ) Error(msg); else - Warning(msg); + { + // suppress error message when we are already in error mode. + // There is no reason to repeat it every second. + if ( ! suppress_warnings ) + Warning(msg); + + if ( suppress_future ) + suppress_warnings = true; + } } bool Ascii::OpenFile() { - if ( file.is_open() && ! is_failed ) + if ( file.is_open() ) return true; file.open(Info().source); + if ( ! file.is_open() ) { - if ( ! is_failed ) - FailWarn(fail_on_file_problem, Fmt("Init: cannot open %s", Info().source)); - is_failed = true; - return !fail_on_file_problem; + FailWarn(fail_on_file_problem, Fmt("Init: cannot open %s", Info().source), true); + + return ! fail_on_file_problem; } if ( ReadHeader(false) == false ) { - if ( ! is_failed ) - FailWarn(fail_on_file_problem, Fmt("Init: cannot open %s; headers are incorrect", Info().source)); + FailWarn(fail_on_file_problem, Fmt("Init: cannot open %s; problem reading file header", Info().source), true); + file.close(); - is_failed = true; - return !fail_on_file_problem; + return ! fail_on_file_problem; } - is_failed = false; + suppress_warnings = false; return true; } @@ -158,7 +163,11 @@ bool Ascii::ReadHeader(bool useCached) if ( ! useCached ) { if ( ! GetLine(line) ) + { + FailWarn(fail_on_file_problem, Fmt("Could not read input data file %s; first line could not be read", + Info().source), true); return false; + } headerline = line; } @@ -198,12 +207,10 @@ bool Ascii::ReadHeader(bool useCached) continue; } - if ( ! is_failed ) - FailWarn(fail_on_file_problem, Fmt("Did not find requested field %s in input data file %s.", - field->name, Info().source)); + FailWarn(fail_on_file_problem, Fmt("Did not find requested field %s in input data file %s.", + field->name, Info().source), true); - is_failed = true; - return !fail_on_file_problem; + return false; } FieldMapping f(field->name, field->type, field->subtype, ifields[field->name]); @@ -213,12 +220,10 @@ bool Ascii::ReadHeader(bool useCached) map::iterator fit2 = ifields.find(field->secondary_name); if ( fit2 == ifields.end() ) { - if ( ! is_failed ) - FailWarn(fail_on_file_problem, Fmt("Could not find requested port type field %s in input data file.", - field->secondary_name)); + FailWarn(fail_on_file_problem, Fmt("Could not find requested port type field %s in input data file.", + field->secondary_name), true); - is_failed = true; - return !fail_on_file_problem; + return false; } f.secondary_position = ifields[field->secondary_name]; @@ -258,7 +263,7 @@ bool Ascii::GetLine(string& str) bool Ascii::DoUpdate() { if ( ! OpenFile() ) - return !fail_on_file_problem; + return ! fail_on_file_problem; switch ( Info().mode ) { case MODE_REREAD: @@ -267,11 +272,10 @@ bool Ascii::DoUpdate() struct stat sb; if ( stat(Info().source, &sb) == -1 ) { - if ( ! is_failed ) - FailWarn(fail_on_file_problem, Fmt("Could not get stat for %s", Info().source)); + FailWarn(fail_on_file_problem, Fmt("Could not get stat for %s", Info().source), true); + file.close(); - is_failed = true; - return !fail_on_file_problem; + return ! fail_on_file_problem; } if ( sb.st_mtime <= mtime ) // no change @@ -293,10 +297,9 @@ bool Ascii::DoUpdate() if ( Info().mode == MODE_STREAM ) { file.clear(); // remove end of file evil bits - if ( !ReadHeader(true) ) + if ( ! ReadHeader(true) ) { - is_failed = true; - return !fail_on_file_problem; // header reading failed + return ! fail_on_file_problem; // header reading failed } break; @@ -360,15 +363,15 @@ bool Ascii::DoUpdate() if ( (*fit).position > pos || (*fit).secondary_position > pos ) { FailWarn(fail_on_invalid_lines, Fmt("Not enough fields in line %s. Found %d fields, want positions %d and %d", - line.c_str(), pos, (*fit).position, (*fit).secondary_position)); - - for ( int i = 0; i < fpos; i++ ) - delete fields[i]; - - delete [] fields; + line.c_str(), pos, (*fit).position, (*fit).secondary_position)); if ( fail_on_invalid_lines ) { + for ( int i = 0; i < fpos; i++ ) + delete fields[i]; + + delete [] fields; + return false; } else @@ -432,7 +435,7 @@ bool Ascii::DoUpdate() bool Ascii::DoHeartbeat(double network_time, double current_time) { if ( ! OpenFile() ) - return !fail_on_file_problem; + return ! fail_on_file_problem; switch ( Info().mode ) { diff --git a/src/input/readers/ascii/Ascii.h b/src/input/readers/ascii/Ascii.h index 9300382ca2..7a7fa52590 100644 --- a/src/input/readers/ascii/Ascii.h +++ b/src/input/readers/ascii/Ascii.h @@ -56,8 +56,10 @@ private: bool ReadHeader(bool useCached); bool GetLine(string& str); bool OpenFile(); - void FailWarn(bool is_error, const char *msg); - + // Call Warning or Error, depending on the is_error boolean. + // In case of a warning, setting suppress_future to true will suppress all future warnings + // (by setting suppress_warnings to true, until suppress_warnings is set back to false) + void FailWarn(bool is_error, const char *msg, bool suppress_future = false); ifstream file; time_t mtime; @@ -77,8 +79,8 @@ private: bool fail_on_file_problem; // this is an internal indicator in case the read is currently in a failed state - // it's used by the options for continuing instead of failing and killing the reader. - bool is_failed; + // it's used to suppress duplicate error messages. + bool suppress_warnings; std::unique_ptr formatter; }; diff --git a/testing/btest/Baseline/scripts.base.frameworks.input.invalid-lines/out b/testing/btest/Baseline/scripts.base.frameworks.input.invalid-lines/out new file mode 100644 index 0000000000..3406639d29 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.frameworks.input.invalid-lines/out @@ -0,0 +1,26 @@ +{ +[-43] = [b=T, e=SSH::LOG, c=21, p=123/unknown, sn=10.0.0.0/24, a=1.2.3.4, d=3.14, t=1315801931.273616, iv=100.0, s=hurz, ns=4242 HOHOHO, sc={ +2, +4, +1, +3 +}, ss={ +BB, +AA, +CC +}, se={ + +}, vc=[10, 20, 30], ve=[]], +[-42] = [b=T, e=SSH::LOG, c=21, p=123/unknown, sn=10.0.0.0/24, a=1.2.3.4, d=3.14, t=1315801931.273616, iv=100.0, s=hurz, ns=4242, sc={ +2, +4, +1, +3 +}, ss={ +BB, +AA, +CC +}, se={ + +}, vc=[10, 20, 30], ve=[]] +} diff --git a/testing/btest/Baseline/scripts.base.frameworks.input.missing-file-initially/bro..stderr b/testing/btest/Baseline/scripts.base.frameworks.input.missing-file-initially/bro..stderr index 6c4d0f0194..337cdcda87 100644 --- a/testing/btest/Baseline/scripts.base.frameworks.input.missing-file-initially/bro..stderr +++ b/testing/btest/Baseline/scripts.base.frameworks.input.missing-file-initially/bro..stderr @@ -1,4 +1,8 @@ warning: ../does-not-exist.dat/Input::READER_ASCII: Init: cannot open ../does-not-exist.dat +warning: ../does-not-exist.dat/Input::READER_ASCII: Init: cannot open ../does-not-exist.dat +warning: ../does-not-exist.dat/Input::READER_ASCII: Init: cannot open ../does-not-exist.dat error: ../does-not-exist.dat/Input::READER_ASCII: Init: cannot open ../does-not-exist.dat +error: ../does-not-exist.dat/Input::READER_ASCII: Init failed error: ../does-not-exist.dat/Input::READER_ASCII: terminating thread +warning: ../does-not-exist.dat/Input::READER_ASCII: Could not get stat for ../does-not-exist.dat received termination signal diff --git a/testing/btest/Baseline/scripts.base.frameworks.input.missing-file-initially/bro..stdout b/testing/btest/Baseline/scripts.base.frameworks.input.missing-file-initially/bro..stdout index d96c6d457d..39309bc8cf 100644 --- a/testing/btest/Baseline/scripts.base.frameworks.input.missing-file-initially/bro..stdout +++ b/testing/btest/Baseline/scripts.base.frameworks.input.missing-file-initially/bro..stdout @@ -1,2 +1,5 @@ now it does and more! +now it does +and more! +Streaming still works diff --git a/testing/btest/Baseline/scripts.base.frameworks.input.missing-file/bro..stderr b/testing/btest/Baseline/scripts.base.frameworks.input.missing-file/bro..stderr index f5d074fe7e..5093925d2d 100644 --- a/testing/btest/Baseline/scripts.base.frameworks.input.missing-file/bro..stderr +++ b/testing/btest/Baseline/scripts.base.frameworks.input.missing-file/bro..stderr @@ -1,2 +1,4 @@ -warning: does-not-exist.dat/Input::READER_ASCII: Init: cannot open does-not-exist.dat +error: does-not-exist.dat/Input::READER_ASCII: Init: cannot open does-not-exist.dat +error: does-not-exist.dat/Input::READER_ASCII: Init failed +error: does-not-exist.dat/Input::READER_ASCII: terminating thread received termination signal diff --git a/testing/btest/scripts/base/frameworks/input/invalid-lines.bro b/testing/btest/scripts/base/frameworks/input/invalid-lines.bro new file mode 100644 index 0000000000..83be1efd09 --- /dev/null +++ b/testing/btest/scripts/base/frameworks/input/invalid-lines.bro @@ -0,0 +1,67 @@ +# @TEST-EXEC: btest-bg-run bro bro -b %INPUT +# @TEST-EXEC: btest-bg-wait 10 +# @TEST-EXEC: btest-diff out + +redef exit_only_after_terminate = T; +redef InputAscii::fail_on_invalid_lines = F; + +@TEST-START-FILE input.log +#separator \x09 +#path ssh +#fields b i e c p sn a d t iv s sc ss se vc ve ns +#types bool int enum count port subnet addr double time interval string table table table vector vector string +T -42 SSH::LOG 21 123 10.0.0.0/24 1.2.3.4 3.14 1315801931.273616 100.000000 hurz 2,4,1,3 CC,AA,BB EMPTY 10,20,30 +T -42 SSH::LOG 21 123 10.0.0.0/24 1.2.3.4 3.14 1315801931.273616 100.000000 hurz 2,4,1,3 CC,AA,BB EMPTY 10,20,30 EMPTY 4242 +T -43 SSH::LOG 21 123 10.0.0.0/24 1.2.3.4 3.14 1315801931.273616 100.000000 hurz 2,4,1,3 CC,AA,BB EMPTY 10,20,30 EMPTY 4242 HOHOHO +T -41 +@TEST-END-FILE + +@load base/protocols/ssh + +global outfile: file; + +redef InputAscii::empty_field = "EMPTY"; + +module A; + +type Idx: record { + i: int; +}; + +type Val: record { + b: bool; + e: Log::ID; + c: count; + p: port; + sn: subnet; + a: addr; + d: double; + t: time; + iv: interval; + s: string; + ns: string; + sc: set[count]; + ss: set[string]; + se: set[string]; + vc: vector of int; + ve: vector of int; +}; + +global servers: table[int] of Val = table(); +global servers2: table[int] of Val = table(); + +event bro_init() + { + outfile = open("../out"); + # first read in the old stuff into the table... + Input::add_table([$source="../input.log", $name="ssh", $idx=Idx, $val=Val, $destination=servers]); + Input::add_table([$source="../input.log", $name="ssh2", $idx=Idx, $val=Val, $destination=servers2, $config=table(["fail_on_invalid_lines"] = "T")]); + } + +event Input::end_of_data(name: string, source:string) + { + print outfile, servers; + Input::remove("ssh"); + close(outfile); + terminate(); + } diff --git a/testing/btest/scripts/base/frameworks/input/invalidtext.bro b/testing/btest/scripts/base/frameworks/input/invalidtext.bro index 1de4e96671..3f5b590dec 100644 --- a/testing/btest/scripts/base/frameworks/input/invalidtext.bro +++ b/testing/btest/scripts/base/frameworks/input/invalidtext.bro @@ -13,6 +13,7 @@ @TEST-END-FILE redef exit_only_after_terminate = T; +redef InputAscii::fail_on_invalid_lines = T; global outfile: file; diff --git a/testing/btest/scripts/base/frameworks/input/missing-file-initially.bro b/testing/btest/scripts/base/frameworks/input/missing-file-initially.bro index a7128a4455..73fd57284e 100644 --- a/testing/btest/scripts/base/frameworks/input/missing-file-initially.bro +++ b/testing/btest/scripts/base/frameworks/input/missing-file-initially.bro @@ -1,10 +1,12 @@ # This tests files that don't exist initially and then do later during # runtime to make sure the ascii reader is resilient to files missing. -# It does a second test at the same time which configures the old +# It does a second test at the same time which configures the old # failing behavior. # @TEST-EXEC: btest-bg-run bro bro %INPUT -# @TEST-EXEC: btest-bg-wait -k 5 +# @TEST-EXEC: sleep 2; cp does-exist.dat does-not-exist.dat +# @TEST-EXEC: sleep 2; mv does-not-exist.dat does-not-exist-again.dat; echo "Streaming still works" >> does-not-exist-again.dat +# @TEST-EXEC: btest-bg-wait -k 3 # @TEST-EXEC: btest-diff bro/.stdout # @TEST-EXEC: btest-diff bro/.stderr @@ -40,8 +42,8 @@ event line2(description: Input::EventDescription, tpe: Input::Event, v: Val) event bro_init() { Input::add_event([$source="../does-not-exist.dat", $name="input", $reader=Input::READER_ASCII, $mode=Input::REREAD, $fields=Val, $ev=line, $want_record=T]); - Input::add_event([$source="../does-not-exist.dat", $name="input2", $reader=Input::READER_ASCII, $mode=Input::REREAD, $fields=Val, $ev=line2, $want_record=T, + Input::add_event([$source="../does-not-exist.dat", $name="inputstream", $reader=Input::READER_ASCII, $mode=Input::STREAM, $fields=Val, $ev=line, $want_record=T]); + Input::add_event([$source="../does-not-exist.dat", $name="inputmanual", $reader=Input::READER_ASCII, $mode=Input::MANUAL, $fields=Val, $ev=line, $want_record=T]); + Input::add_event([$source="../does-not-exist.dat", $name="input2", $reader=Input::READER_ASCII, $mode=Input::REREAD, $fields=Val, $ev=line2, $want_record=T, $config=table(["fail_on_file_problem"] = "T")]); - - system("sleep 2; mv ../does-exist.dat ../does-not-exist.dat;"); } diff --git a/testing/btest/scripts/base/frameworks/input/missing-file.bro b/testing/btest/scripts/base/frameworks/input/missing-file.bro index 08adfe2150..2ec3bb937f 100644 --- a/testing/btest/scripts/base/frameworks/input/missing-file.bro +++ b/testing/btest/scripts/base/frameworks/input/missing-file.bro @@ -3,6 +3,7 @@ # @TEST-EXEC: btest-diff bro/.stderr redef exit_only_after_terminate = T; +redef InputAscii::fail_on_file_problem = T; global outfile: file; global try: count; From dc2cfd8a1071ec3b30bd130d859758b3353f4b11 Mon Sep 17 00:00:00 2001 From: Robin Sommer Date: Fri, 3 Mar 2017 12:51:54 -0800 Subject: [PATCH 34/44] Updating submodule(s). [nomail] --- aux/btest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aux/btest b/aux/btest index 14c18508bb..dceda16935 160000 --- a/aux/btest +++ b/aux/btest @@ -1 +1 @@ -Subproject commit 14c18508bb385c8f55cd79738ee87ca2b85eba27 +Subproject commit dceda169351ddd0c7fe7a5ae5496be1d7af2367b From 5ec4e00fcd802445ed176258684aecbaf768f5a1 Mon Sep 17 00:00:00 2001 From: Daniel Thayer Date: Wed, 8 Mar 2017 14:19:31 -0600 Subject: [PATCH 35/44] Fix some Coverity warnings Fixed some Coverity warnings in RemoteSerializer::ProcessLogCreateWriter(). Upon failure, CreateWriterForRemoteLog() frees the "info" and "fields" pointers, so they are now set to null in order to avoid freeing them a second time. --- src/RemoteSerializer.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/RemoteSerializer.cc b/src/RemoteSerializer.cc index 2adecfc89a..7d8899d8b9 100644 --- a/src/RemoteSerializer.cc +++ b/src/RemoteSerializer.cc @@ -2732,7 +2732,8 @@ bool RemoteSerializer::ProcessLogCreateWriter() if ( ! log_mgr->CreateWriterForRemoteLog(id_val, writer_val, info, num_fields, fields) ) { - delete_fields_up_to = num_fields; + info = 0; + fields = 0; goto error; } From ff4d624ebe4bee14211ae49beb789b7221fe8e27 Mon Sep 17 00:00:00 2001 From: Seth Hall Date: Thu, 9 Mar 2017 12:18:35 -0500 Subject: [PATCH 36/44] Minor documentation fixes. --- scripts/base/frameworks/input/readers/ascii.bro | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/base/frameworks/input/readers/ascii.bro b/scripts/base/frameworks/input/readers/ascii.bro index 83d8ced94b..1d4072e118 100644 --- a/scripts/base/frameworks/input/readers/ascii.bro +++ b/scripts/base/frameworks/input/readers/ascii.bro @@ -25,16 +25,16 @@ export { ## input lines will be handled as fatal errors for the ## reader thread; reading will abort immediately and ## an error will be logged to reporter.log. - ## Invidivual readers can use a different value using + ## Individual readers can use a different value using ## the $config table. ## fail_on_invalid_lines = T was the default behavior - ## untill Bro 2.5. + ## until Bro 2.6. const fail_on_invalid_lines = F &redef; ## Fail on file read problems. If set to true, the ascii ## input reader will fail when encountering any problems ## while reading a file different from invalid lines. - ## Examples fur such problems are permission problems, or + ## Examples of such problems are permission problems, or ## missing files. ## When set to false, these problems will be ignored. This ## has an especially big effect for the REREAD mode, which will @@ -42,9 +42,9 @@ export { ## only temporarily inaccessible. For MANUAL or STREAM files, ## errors will most likely still be fatal since no automatic ## re-reading of the file is attempted. - ## Invidivual readers can use a different value using + ## Individual readers can use a different value using ## the $config table. ## fail_on_file_problem = T was the default behavior - ## untill Bro 2.5. + ## until Bro 2.6. const fail_on_file_problem = F &redef; } From d505670f599c09f21eed275b0d79f910eaa3bf2e Mon Sep 17 00:00:00 2001 From: Seth Hall Date: Thu, 9 Mar 2017 12:43:41 -0500 Subject: [PATCH 37/44] Updating NEWS --- NEWS | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/NEWS b/NEWS index 9aa1e35c7c..7fbc7cfd4f 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,19 @@ release. For an exhaustive list of changes, see the ``CHANGES`` file (note that submodules, such as BroControl and Broccoli, come with their own ``CHANGES``.) +Bro 2.6 +======= + +Changed Functionality +--------------------- + +- The input framework's Ascii reader is now more resilient. If an input + is marked to reread a file when it changes and the file didn't exist + during a check Bro would stop watching the file in previous versions. + The same could happen with bad data in a line of a file. These + situations do not cause Bro to stop watching input files anymore. The + old behavior is available through settings in the Ascii reader. + Bro 2.5 ======= From 05746ab7fcfdbd966bb4e39403dd004e42a75fc4 Mon Sep 17 00:00:00 2001 From: Pete Date: Sun, 12 Mar 2017 12:53:12 -0400 Subject: [PATCH 38/44] print version string to stdout on --version When running a *nix command from the prompt, and output is expected, that output should be sent to stdout, not stderr. The --version option is such a case. The outputted version string is not an indication of error or a diagnostic output; it is the expected output, thus should follow standard conventions and be output to stdout.. --- src/main.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cc b/src/main.cc index 55636a9496..d9f8ff5b0b 100644 --- a/src/main.cc +++ b/src/main.cc @@ -595,7 +595,7 @@ int main(int argc, char** argv) break; case 'v': - fprintf(stderr, "%s version %s\n", prog, bro_version()); + fprintf(stdout, "%s version %s\n", prog, bro_version()); exit(0); break; From a38f44b1fddac4c7fd3e8bdb59a64ce952c7703b Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Mon, 13 Mar 2017 11:16:15 -0700 Subject: [PATCH 39/44] Fix coverity warning in Ascii reader. --- src/input/readers/ascii/Ascii.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/input/readers/ascii/Ascii.cc b/src/input/readers/ascii/Ascii.cc index bedddce075..3440d9565d 100644 --- a/src/input/readers/ascii/Ascii.cc +++ b/src/input/readers/ascii/Ascii.cc @@ -49,6 +49,7 @@ FieldMapping FieldMapping::subType() Ascii::Ascii(ReaderFrontend *frontend) : ReaderBackend(frontend) { mtime = 0; + suppress_warnings = false; } Ascii::~Ascii() From 17fa1b6fede10765a8f483727358c98aa324e656 Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Mon, 13 Mar 2017 11:16:19 -0700 Subject: [PATCH 40/44] Fix compiler warnings raised by llvm 8.0. Warning was: warning: moving a temporary object prevents copy elision [-Wpessimizing-move] --- src/input/readers/raw/Plugin.cc | 2 +- src/threading/Queue.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/input/readers/raw/Plugin.cc b/src/input/readers/raw/Plugin.cc index 0dd1b47fc7..e16a233fe6 100644 --- a/src/input/readers/raw/Plugin.cc +++ b/src/input/readers/raw/Plugin.cc @@ -30,6 +30,6 @@ void Plugin::Done() std::unique_lock Plugin::ForkMutex() { - return std::move(std::unique_lock(fork_mutex, std::defer_lock)); + return std::unique_lock(fork_mutex, std::defer_lock); } diff --git a/src/threading/Queue.h b/src/threading/Queue.h index bf0f0db82c..9ac9268a7a 100644 --- a/src/threading/Queue.h +++ b/src/threading/Queue.h @@ -124,7 +124,7 @@ inline static std::unique_lock acquire_lock(std::mutex& m) { try { - return std::move(std::unique_lock(m)); + return std::unique_lock(m); } catch ( const std::system_error& e ) { @@ -224,7 +224,7 @@ inline std::vector> Queue::LocksForAllQueues() throw std::exception(); } - return std::move(locks); + return locks; } template From 7180c704f649d7aaa66a826daebb02c6efd10384 Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Mon, 13 Mar 2017 11:56:55 -0700 Subject: [PATCH 41/44] Update submodule [nomail] --- aux/broctl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aux/broctl b/aux/broctl index 73dbc79ac2..58e9df0694 160000 --- a/aux/broctl +++ b/aux/broctl @@ -1 +1 @@ -Subproject commit 73dbc79ac24cdfef07d8574a4da5d43056ba5fa5 +Subproject commit 58e9df0694cc900b20decf26fbd7add6ee91212f From 2d7c84956c0df80763f7b1eb4c5f9d15c21f3a4b Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Mon, 13 Mar 2017 16:08:14 -0700 Subject: [PATCH 42/44] Update submodule [nomail] --- aux/broctl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aux/broctl b/aux/broctl index 58e9df0694..96583ab378 160000 --- a/aux/broctl +++ b/aux/broctl @@ -1 +1 @@ -Subproject commit 58e9df0694cc900b20decf26fbd7add6ee91212f +Subproject commit 96583ab378b1de32ac9804246e1b0e2845fc8b3e From 750e3e358f5c6e7ab80a6309bdf9d5a6854518ed Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Tue, 14 Mar 2017 06:58:42 -0700 Subject: [PATCH 43/44] Update submodule [nomail] --- aux/bro-aux | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aux/bro-aux b/aux/bro-aux index 9f33570d53..51bf79d3fc 160000 --- a/aux/bro-aux +++ b/aux/bro-aux @@ -1 +1 @@ -Subproject commit 9f33570d53e1b970d7905e305940fd55637c5c76 +Subproject commit 51bf79d3fc78b5e86c554afe7c24c44b025aa67f From 6544e365645b75d85c3836894a20c2339e79c399 Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Wed, 15 Mar 2017 08:00:36 -0700 Subject: [PATCH 44/44] Remove cluster catch and release. This test keeps failing intermittently because of timing issues that are surprisingly hard to fix. --- .../manager-1.netcontrol.log | 23 --- .../manager-1.netcontrol_catch_release.log | 18 --- .../worker-2..stdout | 9 -- .../netcontrol/catch-and-release-cluster.bro | 132 ------------------ 4 files changed, 182 deletions(-) delete mode 100644 testing/btest/Baseline/scripts.base.frameworks.netcontrol.catch-and-release-cluster/manager-1.netcontrol.log delete mode 100644 testing/btest/Baseline/scripts.base.frameworks.netcontrol.catch-and-release-cluster/manager-1.netcontrol_catch_release.log delete mode 100644 testing/btest/Baseline/scripts.base.frameworks.netcontrol.catch-and-release-cluster/worker-2..stdout delete mode 100644 testing/btest/scripts/base/frameworks/netcontrol/catch-and-release-cluster.bro diff --git a/testing/btest/Baseline/scripts.base.frameworks.netcontrol.catch-and-release-cluster/manager-1.netcontrol.log b/testing/btest/Baseline/scripts.base.frameworks.netcontrol.catch-and-release-cluster/manager-1.netcontrol.log deleted file mode 100644 index 974349e229..0000000000 --- a/testing/btest/Baseline/scripts.base.frameworks.netcontrol.catch-and-release-cluster/manager-1.netcontrol.log +++ /dev/null @@ -1,23 +0,0 @@ -#separator \x09 -#set_separator , -#empty_field (empty) -#unset_field - -#path netcontrol -#open 2016-08-12-17-38-49 -#fields ts rule_id category cmd state action target entity_type entity mod msg priority expire location plugin -#types time string enum string enum string enum string string string string int interval string string -1471023529.752740 - NetControl::MESSAGE - - - - - - - activating plugin with priority 0 - - - Debug-All -1471023529.752740 - NetControl::MESSAGE - - - - - - - activation finished - - - Debug-All -1471023529.752740 - NetControl::MESSAGE - - - - - - - plugin initialization done - - - - -1471023532.819263 2 NetControl::RULE ADD NetControl::REQUESTED NetControl::DROP NetControl::FORWARD NetControl::ADDRESS 192.168.18.50/32 - - 0 600.000000 connection drop worker-1 Debug-All -1471023532.819263 worker-1:2 NetControl::RULE ADD NetControl::REQUESTED NetControl::DROP NetControl::FORWARD NetControl::ADDRESS 8.8.8.8/32 - - 0 0.100000 direct drop worker-1 Debug-All -1471023532.819263 2 NetControl::RULE ADD NetControl::SUCCEEDED NetControl::DROP NetControl::FORWARD NetControl::ADDRESS 192.168.18.50/32 - - 0 600.000000 connection drop worker-1 Debug-All -1471023532.819263 worker-1:2 NetControl::RULE ADD NetControl::SUCCEEDED NetControl::DROP NetControl::FORWARD NetControl::ADDRESS 8.8.8.8/32 - - 0 0.100000 direct drop worker-1 Debug-All -1471023532.920126 worker-1:2 NetControl::RULE EXPIRE NetControl::TIMEOUT NetControl::DROP NetControl::FORWARD NetControl::ADDRESS 8.8.8.8/32 - - 0 0.100000 direct drop worker-1 Debug-All -1471023532.920126 worker-1:2 NetControl::RULE REMOVE NetControl::REQUESTED NetControl::DROP NetControl::FORWARD NetControl::ADDRESS 8.8.8.8/32 - - 0 0.100000 direct drop worker-1 Debug-All -1471023532.921768 worker-1:2 NetControl::RULE REMOVE NetControl::SUCCEEDED NetControl::DROP NetControl::FORWARD NetControl::ADDRESS 8.8.8.8/32 - - 0 0.100000 direct drop worker-1 Debug-All -1471023534.308087 2 NetControl::RULE REMOVE NetControl::REQUESTED NetControl::DROP NetControl::FORWARD NetControl::ADDRESS 192.168.18.50/32 - worker-2 0 600.000000 connection drop worker-1 Debug-All -1471023534.308087 2 NetControl::RULE REMOVE NetControl::SUCCEEDED NetControl::DROP NetControl::FORWARD NetControl::ADDRESS 192.168.18.50/32 - - 0 600.000000 connection drop worker-1 Debug-All -1471023534.308087 4 NetControl::RULE ADD NetControl::REQUESTED NetControl::DROP NetControl::FORWARD NetControl::ADDRESS 8.8.8.8/32 - - 0 3600.000000 Re-drop by catch-and-release: direct cr worker-1 Debug-All -1471023534.308087 4 NetControl::RULE ADD NetControl::SUCCEEDED NetControl::DROP NetControl::FORWARD NetControl::ADDRESS 8.8.8.8/32 - - 0 3600.000000 Re-drop by catch-and-release: direct cr worker-1 Debug-All -#close 2016-08-12-17-38-54 diff --git a/testing/btest/Baseline/scripts.base.frameworks.netcontrol.catch-and-release-cluster/manager-1.netcontrol_catch_release.log b/testing/btest/Baseline/scripts.base.frameworks.netcontrol.catch-and-release-cluster/manager-1.netcontrol_catch_release.log deleted file mode 100644 index 54202fffbe..0000000000 --- a/testing/btest/Baseline/scripts.base.frameworks.netcontrol.catch-and-release-cluster/manager-1.netcontrol_catch_release.log +++ /dev/null @@ -1,18 +0,0 @@ -#separator \x09 -#set_separator , -#empty_field (empty) -#unset_field - -#path netcontrol_catch_release -#open 2016-08-12-17-38-52 -#fields ts rule_id ip action block_interval watch_interval blocked_until watched_until num_blocked location message -#types time string addr enum interval interval time time count string string -1471023532.819263 2 192.168.18.50 NetControl::DROP 600.000000 3600.000000 1471024132.819263 1471027132.819263 1 connection drop worker-1 - -1471023532.819263 2 192.168.18.50 NetControl::DROPPED 600.000000 3600.000000 1471024132.819263 1471027132.819263 1 connection drop worker-1 - -1471023532.819263 worker-1:2 8.8.8.8 NetControl::ADDED 600.000000 3600.000000 - 1471027132.819263 1 direct cr worker-1 Address already blocked outside of catch-and-release. Catch and release will monitor and only actively block if it appears in network traffic. -1471023532.920126 worker-1:2 8.8.8.8 NetControl::UNBLOCK 600.000000 3600.000000 - 1471027132.819263 1 direct cr worker-1 - -1471023534.308087 2 192.168.18.50 NetControl::INFO 600.000000 3600.000000 1471024132.819263 1471027132.819263 1 connection drop worker-1 Block seen while in rule_entities. No action taken. -1471023534.308087 2 192.168.18.50 NetControl::UNBLOCK 600.000000 3600.000000 1471024132.819263 1471027132.819263 1 connection drop worker-1 worker-2 -1471023534.308087 4 8.8.8.8 NetControl::SEEN_AGAIN 3600.000000 86400.000000 1471027134.308087 1471109934.308087 2 direct cr worker-1 - -1471023534.308087 4 8.8.8.8 NetControl::DROPPED 3600.000000 86400.000000 1471027134.308087 1471109934.308087 2 direct cr worker-1 - -1471023532.239980 2 192.168.18.50 NetControl::INFO 600.000000 3600.000000 1471024132.819263 1471027132.819263 1 connection drop worker-1 Already blocked using catch-and-release - ignoring duplicate -#close 2016-08-12-17-38-54 diff --git a/testing/btest/Baseline/scripts.base.frameworks.netcontrol.catch-and-release-cluster/worker-2..stdout b/testing/btest/Baseline/scripts.base.frameworks.netcontrol.catch-and-release-cluster/worker-2..stdout deleted file mode 100644 index f61fe92474..0000000000 --- a/testing/btest/Baseline/scripts.base.frameworks.netcontrol.catch-and-release-cluster/worker-2..stdout +++ /dev/null @@ -1,9 +0,0 @@ -Suspend, worker-2 -New block, 192.168.18.50, [block_until=1471027194.791177, watch_until=1471030194.791177, num_reblocked=0, current_interval=0, current_block_id=2, location=connection drop worker-1] -New block, 8.8.8.8, [block_until=, watch_until=1471030194.791177, num_reblocked=0, current_interval=0, current_block_id=worker-1:2, location=direct cr worker-1] -Resume, worker-2 -Connection established -Info, [block_until=1471027194.791177, watch_until=1471030194.791177, num_reblocked=0, current_interval=0, current_block_id=2, location=connection drop worker-1] -Delete block, 192.168.18.50 -New block, 8.8.8.8, [block_until=1471030196.295249, watch_until=1471112996.295249, num_reblocked=1, current_interval=1, current_block_id=4, location=direct cr worker-1] -remote connection closed diff --git a/testing/btest/scripts/base/frameworks/netcontrol/catch-and-release-cluster.bro b/testing/btest/scripts/base/frameworks/netcontrol/catch-and-release-cluster.bro deleted file mode 100644 index fd7de7e442..0000000000 --- a/testing/btest/scripts/base/frameworks/netcontrol/catch-and-release-cluster.bro +++ /dev/null @@ -1,132 +0,0 @@ -# @TEST-SERIALIZE: comm -# -# @TEST-EXEC: btest-bg-run manager-1 "cp ../cluster-layout.bro . && CLUSTER_NODE=manager-1 bro %INPUT" -# @TEST-EXEC: sleep 1 -# @TEST-EXEC: btest-bg-run worker-1 "cp ../cluster-layout.bro . && CLUSTER_NODE=worker-1 bro --pseudo-realtime -C -r $TRACES/tls/ecdhe.pcap %INPUT" -# @TEST-EXEC: btest-bg-run worker-2 "cp ../cluster-layout.bro . && CLUSTER_NODE=worker-2 bro --pseudo-realtime -C -r $TRACES/tls/ecdhe.pcap %INPUT" -# @TEST-EXEC: btest-bg-wait 20 -# @TEST-EXEC: TEST_DIFF_CANONIFIER='grep -v ^# | $SCRIPTS/diff-remove-timestamps' btest-diff manager-1/netcontrol.log -# @TEST-EXEC: btest-diff manager-1/netcontrol_catch_release.log -# @TEST-EXEC: btest-diff worker-2/.stdout - -@TEST-START-FILE cluster-layout.bro -redef Cluster::nodes = { - ["manager-1"] = [$node_type=Cluster::MANAGER, $ip=127.0.0.1, $p=37757/tcp, $workers=set("worker-1", "worker-2")], - ["worker-1"] = [$node_type=Cluster::WORKER, $ip=127.0.0.1, $p=37760/tcp, $manager="manager-1", $interface="eth0"], - ["worker-2"] = [$node_type=Cluster::WORKER, $ip=127.0.0.1, $p=37761/tcp, $manager="manager-1", $interface="eth0"], -}; -@TEST-END-FILE - -redef exit_only_after_terminate = T; - -redef Log::default_rotation_interval = 0secs; - -@load base/frameworks/netcontrol -redef NetControl::catch_release_warn_blocked_ip_encountered = T; - -global ready_for_data_1: event(); -global ready_for_data_2: event(); -redef Cluster::manager2worker_events += /^ready_for_data_(1|2)$/; - -@if ( Cluster::local_node_type() == Cluster::MANAGER ) - -global peer_count = 0; -event remote_connection_handshake_done(p: event_peer) &priority=-5 - { - ++peer_count; - print "remote_connection_handshake_done", peer_count; - if ( peer_count == 2 ) - { - event ready_for_data_1(); - schedule 1.5sec { ready_for_data_2() }; - } - } - -@endif - -@if ( Cluster::local_node_type() == Cluster::WORKER ) -event bro_init() - { - print "Suspend", Cluster::node; - suspend_processing(); - } - -event remote_connection_closed(p: event_peer) { - print "remote connection closed"; - terminate(); -} -@endif - -@if ( Cluster::node == "worker-1" ) -event ready_for_data_1() - { - print "Resume", Cluster::node; - continue_processing(); - } -@endif - -@if ( Cluster::node == "worker-2" ) -event ready_for_data_2() - { - print "Resume", Cluster::node; - continue_processing(); - } -@endif - -event NetControl::init() - { - local netcontrol_debug = NetControl::create_debug(T); - NetControl::activate(netcontrol_debug, 0); - } - -global i: count = 0; - -event connection_established(c: connection) - { - print "Connection established"; - local id = c$id; - local info = NetControl::get_catch_release_info(id$orig_h); - print "Info", info; - NetControl::drop_address_catch_release(id$orig_h, cat("connection drop ", Cluster::node)); - if ( info$current_block_id != "" ) - { - NetControl::unblock_address_catch_release(id$orig_h, Cluster::node); - } - } - -@if ( Cluster::node == "worker-1" ) -event connection_established(c: connection) - { - NetControl::drop_address(8.8.8.8, 0.1secs, cat("direct drop ", Cluster::node)); - NetControl::drop_address_catch_release(8.8.8.8, cat("direct cr ", Cluster::node)); - } -@endif - -@if ( Cluster::node == "worker-2" ) -event connection_established(c: connection) - { - NetControl::catch_release_seen(8.8.8.8); - } -@endif - -event NetControl::catch_release_block_new(a: addr, b: NetControl::BlockInfo) - { - print "New block", a, b; - } - -event NetControl::catch_release_block_delete(a: addr) - { - print "Delete block", a; - } - -event terminate_me() { - terminate(); -} - -@if ( Cluster::local_node_type() == Cluster::MANAGER ) -event NetControl::rule_added(r: NetControl::Rule, p: NetControl::PluginState, msg: string) - { - print "Scheduling terminate"; - schedule 3sec { terminate_me() }; - } -@endif